summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSam Thursfield <sam.thursfield@codethink.co.uk>2018-01-18 14:51:48 +0000
committerSam Thursfield <sam.thursfield@codethink.co.uk>2018-01-19 15:15:30 +0000
commitc31d5a651529512dbf44e598f2de3c34039ff887 (patch)
tree57f297b53566f0104c7baf5cfb0869e7c2693581
parentdcb1506f7c6b8ee646688db23ca7f8711a00c752 (diff)
downloadbuildstream-sam/symlink-tests.tar.gz
Add integration tests for edge cases involving symlinks and overlapssam/symlinks-optimizationsam/symlink-tests
-rw-r--r--tests/integration/project/elements/symlinks/dangling-symlink-overlap.bst26
-rw-r--r--tests/integration/project/elements/symlinks/dangling-symlink.bst14
-rw-r--r--tests/integration/project/elements/symlinks/symlink-to-outside-sandbox-overlap.bst27
-rw-r--r--tests/integration/project/elements/symlinks/symlink-to-outside-sandbox.bst11
-rw-r--r--tests/integration/symlinks.py74
5 files changed, 152 insertions, 0 deletions
diff --git a/tests/integration/project/elements/symlinks/dangling-symlink-overlap.bst b/tests/integration/project/elements/symlinks/dangling-symlink-overlap.bst
new file mode 100644
index 000000000..09db5a8a1
--- /dev/null
+++ b/tests/integration/project/elements/symlinks/dangling-symlink-overlap.bst
@@ -0,0 +1,26 @@
+kind: manual
+
+depends:
+ - base.bst
+ - symlinks/dangling-symlink.bst
+
+config:
+ install-commands:
+ # The element that we depend on installs a symlink at `/opt/orgname`,
+ # which points to a non-existant target of `/usr/orgs/orgname`.
+ # BuildStream converts absolute symlink targets into relative ones so it
+ # ends up pointing to ../usr/orgs/orgname, but this resolves to the same
+ # place.
+
+ # This element creates a directory at `/opt/orgname` and installs files
+ # inside it. When this element is staged on top of the dependency this
+ # directory will be ignored as the symlink will already be there;
+ # BuildStream will then process the files that should be /in/ the
+ # directory. The expected behaviour when installing files within a symlink
+ # is to install them within the symlink's target, so the file
+ # `/opt/orgname/etc/org.conf` should end up at
+ # `/usr/orgs/orgname/etc/org.conf`. And since that directory doesn't exist
+ # BuildStream will also need to create it before installing anything there.
+ #
+ - mkdir -p "%{install-root}"/opt/orgname/etc/
+ - echo "example" > "%{install-root}"/opt/orgname/etc/org.conf
diff --git a/tests/integration/project/elements/symlinks/dangling-symlink.bst b/tests/integration/project/elements/symlinks/dangling-symlink.bst
new file mode 100644
index 000000000..879702d30
--- /dev/null
+++ b/tests/integration/project/elements/symlinks/dangling-symlink.bst
@@ -0,0 +1,14 @@
+kind: manual
+
+depends:
+ - base.bst
+
+config:
+ install-commands:
+ # The installed file `/opt/orgname` will be a symlink to a directory that
+ # doesn't exist (`/usr/orgs/orgname`). BuildStream should store this as a
+ # relative symlink; among other reasons, if we ever stage an absolute
+ # symlinks then we risk subsequent operations trying to write outside the
+ # sandbox to paths on the host.
+ - mkdir -p "%{install-root}"/opt/
+ - ln -s /usr/orgs/orgname "%{install-root}"/opt/orgname
diff --git a/tests/integration/project/elements/symlinks/symlink-to-outside-sandbox-overlap.bst b/tests/integration/project/elements/symlinks/symlink-to-outside-sandbox-overlap.bst
new file mode 100644
index 000000000..66d42ea2c
--- /dev/null
+++ b/tests/integration/project/elements/symlinks/symlink-to-outside-sandbox-overlap.bst
@@ -0,0 +1,27 @@
+kind: manual
+
+depends:
+ - base.bst
+ - symlinks/symlink-to-outside-sandbox.bst
+
+config:
+ install-commands:
+ # The element we depend on has installed a relative symlink to
+ # `/opt/escape-hatch` which uses `../` path sections so that its
+ # target points outside of the sandbox.
+ #
+ # This element installs a directory to the same `/opt/escape-hatch`
+ # location and installs a file inside the directory.
+ #
+ # When this element is staged on top of its dependency, the directory will
+ # overlap with the symlink and will thus be ignored. BuildStream will then
+ # try to install the `etc/org.conf` file inside the symlinks target and
+ # will end up with a path like `../../usr/etc/org.conf`.
+ #
+ # This could in theory overwrite something on the host system. In practice
+ # the normal UNIX permissions model should prevent any damage, but we
+ # should still detect this happening and raise an error as it is a sure
+ # sign that something is wrong.
+ #
+ - mkdir -p "%{install-root}"/opt/escape-hatch/etc/
+ - echo "example" > "%{install-root}"/opt/escape-hatch/etc/org.conf
diff --git a/tests/integration/project/elements/symlinks/symlink-to-outside-sandbox.bst b/tests/integration/project/elements/symlinks/symlink-to-outside-sandbox.bst
new file mode 100644
index 000000000..85f6776d0
--- /dev/null
+++ b/tests/integration/project/elements/symlinks/symlink-to-outside-sandbox.bst
@@ -0,0 +1,11 @@
+kind: manual
+
+depends:
+ - base.bst
+
+config:
+ install-commands:
+ # This symlink could be used by a dependent element to trick BuildStream into
+ # trying to create files outside of the sandbox.
+ - mkdir "%{install-root}/opt/"
+ - ln -s ../../usr "%{install-root}"/opt/escape-hatch
diff --git a/tests/integration/symlinks.py b/tests/integration/symlinks.py
new file mode 100644
index 000000000..18bd724c1
--- /dev/null
+++ b/tests/integration/symlinks.py
@@ -0,0 +1,74 @@
+import os
+import shlex
+import pytest
+
+from buildstream import _yaml
+
+from tests.testutils import cli_integration as cli
+from tests.testutils.integration import assert_contains
+
+
+pytestmark = pytest.mark.integration
+
+
+DATA_DIR = os.path.join(
+ os.path.dirname(os.path.realpath(__file__)),
+ "project"
+)
+
+
+@pytest.mark.datafiles(DATA_DIR)
+def test_absolute_symlinks_made_relative(cli, tmpdir, datafiles):
+ project = os.path.join(datafiles.dirname, datafiles.basename)
+ checkout = os.path.join(cli.directory, 'checkout')
+ element_name = 'symlinks/dangling-symlink.bst'
+
+ result = cli.run(project=project, args=['build', element_name])
+ assert result.exit_code == 0
+
+ result = cli.run(project=project, args=['checkout', element_name, checkout])
+ assert result.exit_code == 0
+
+ symlink = os.path.join(checkout, 'opt', 'orgname')
+ assert os.path.islink(symlink)
+
+ # The symlink is created to point to /usr/orgs/orgname, but BuildStream
+ # should make all symlink target relative when assembling the artifact.
+ # This is done so that nothing points outside the sandbox and so that
+ # staging artifacts in locations other than / doesn't cause the links to
+ # all break.
+ assert os.readlink(symlink) == '../usr/orgs/orgname'
+
+
+@pytest.mark.datafiles(DATA_DIR)
+def test_allow_overlaps_inside_symlink_with_dangling_target(cli, tmpdir, datafiles):
+ project = os.path.join(datafiles.dirname, datafiles.basename)
+ checkout = os.path.join(cli.directory, 'checkout')
+ element_name = 'symlinks/dangling-symlink-overlap.bst'
+
+ result = cli.run(project=project, args=['build', element_name])
+ assert result.exit_code == 0
+
+ result = cli.run(project=project, args=['checkout', element_name, checkout])
+ assert result.exit_code == 0
+
+ # See the dangling-symlink*.bst elements for details on what we are testing.
+ assert_contains(checkout, ['/usr/orgs/orgname/etc/org.conf'])
+
+
+@pytest.mark.datafiles(DATA_DIR)
+def test_detect_symlink_overlaps_pointing_outside_sandbox(cli, tmpdir, datafiles):
+ project = os.path.join(datafiles.dirname, datafiles.basename)
+ checkout = os.path.join(cli.directory, 'checkout')
+ element_name = 'symlinks/symlink-to-outside-sandbox-overlap.bst'
+
+ # Building the two elements should succeed...
+ result = cli.run(project=project, args=['build', element_name])
+ assert result.exit_code == 0
+
+ # ...but when we compose them together, the overlaps create paths that
+ # point outside the sandbox which BuildStream needs to detect before it
+ # tries to actually write there.
+ result = cli.run(project=project, args=['checkout', element_name, checkout])
+ assert result.exit_code == -1
+ assert "Destination path resolves to a path outside of the staging area" in result.stderr