diff options
author | Sam Thursfield <sam.thursfield@codethink.co.uk> | 2018-01-18 14:51:48 +0000 |
---|---|---|
committer | Jürg Billeter <j@bitron.ch> | 2018-02-12 10:33:03 +0100 |
commit | 5e8dab02a7267aa60e574460a2a52c31a4e766f1 (patch) | |
tree | 11cfaa0085ab4c71f6645add30cac9b3c4f06404 | |
parent | dbed35e657fe3ddcffa8c1ded8544da786218a41 (diff) | |
download | buildstream-5e8dab02a7267aa60e574460a2a52c31a4e766f1.tar.gz |
Add integration tests for edge cases involving symlinks and overlaps
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 |