Skip to content

Enforce read-only-ness of package directory even when it's inside writable directories #3996

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conversation

abertelrud
Copy link
Contributor

Enforce read-only-ness of package directory even when it's inside /tmp and other writable directories to the number of reduce special cases.

Motivation

The package directory should always be read-only unless specifically requested to be writable, even if it happens to be in /tmp or other implicitly writable locations.

Details

The sandbox supports a list of writableDirectories, which are treated as path component prefixes after package resolution. The are applied to the sandbox rules after the default-deny and everything-is-read-only rules, carving out cones of writability in specific areas of the file system.

But if the package directory is under one of these directories that is allowed by default, then it is implicitly writable even in the absence --allow-writing-to-package-directory. It also makes unit tests awkward, since they usually create temporary test fixtures inside the temporary directory.

Modifications:

  • extend Sandbox with a list of read-only locations
  • add function and parameter documentation to Sandbox
  • thread through the list of read only directories
  • pass the package directory as a specific read-only directory unless it's covered by an explicit writable-directory parameter
  • switch back to using strictness for temporary-directory writability (cleaner)
  • remove the flag to make temporary directory read-only that was added mostly for unit tests

Result:

There are fewer edge cases with respect to when the package directory is writable.

…p and other writable directories

The sandbox supports a list of `writableDirectories`, which are treated as path component prefixes after package resolution.  The are applied to the sandbox rules after the default-deny and everything-is-read-only rules, carving out cones of writability in specific areas of the file system.

But if the package directory is under one of these directories that is allowed by default, then it is implicitly writable even in the absence `--allow-writing-to-package-directory`.  It also makes unit tests awkward, since they usually create temporary test fixtures inside the temporary directory.

This adds a set of `readOnlyDirectories` in addition to the writable ones, so that the package directory remains unwritable regardless of where it is located.

It also adds some documentation in the Sandbox source file, and it removes a somewhat confusing `--block-writing-to-package-directory` flag that was added mostly for unit tests but that is no longer needed.
@abertelrud abertelrud self-assigned this Jan 8, 2022
@abertelrud abertelrud added the ready Author believes the PR is ready to be merged & any feedback has been addressed label Jan 8, 2022
@@ -895,10 +895,6 @@ extension SwiftPackageTool {
help: "Allow the plugin to write to an additional directory")
var additionalAllowedWritableDirectories: [String] = []

@Flag(name: .customLong("block-writing-to-temporary-directory"),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This flag was just added, so nobody is depending on it yet, and it was mostly for unit tests — it seems much clearer to always make the package directory read-only, even for packages checked out to /tmp or other implicitly writable places.

@@ -979,13 +975,6 @@ extension SwiftPackageTool {
if allowWritingToPackageDirectory {
writableDirectories.append(package.path)
}
if !blockWritingToTemporaryDirectory {
writableDirectories.append(AbsolutePath("/tmp"))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The temporary directories are now again handled by the strictness.

Comment on lines -986 to -988
if allowWritingToPackageDirectory {
writableDirectories.append(package.path)
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was actually redundant with lines 975-977 above.

Comment on lines +991 to +993
// Make sure that the package path is read-only unless it's covered by any of the explicitly writable directories.
let readOnlyDirectories = writableDirectories.contains{ package.path.isDescendantOfOrEqual(to: $0) } ? [] : [package.path]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one is subject to debate. It avoids marking the package path as readonly if it's covered by any of the directories marked as explicitly writable. Maybe what should be done here in the long term is to have a single list of directory-specific permissions, each of which could be either block or allow and then the caller can craft their sandbox directories as they want to. For example, it might make sense to allow a plugin to modify only a subdirectory of the package. We can extend this in the future — probably better to keep it simple for now.

Copy link
Contributor

@tomerd tomerd Jan 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

starting simple makes sense to me. having the sandbox API accepts a list of allow/deny and having the call site define that make sense.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

// swift-tools-version: 999.0
// swift-tools-version: 5.6
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an incidental fix that will be needed once we mark the 5.6 branch as no longer a development branch (so it stops accepting 999). The PR that added this was still open when all the plugins were changed from 999 to 5.6 after evolution proposal acceptance.

@abertelrud
Copy link
Contributor Author

@swift-ci please smoke test

case manifest_pre_53 // backwards compatibility for manifests
/// Like `default`, but also makes temporary-files directories (such as `/tmp`) on the platform writable.
case writableTemporaryDirectory
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would it better to have the call site pass the temp dirs as writable locations instead of defining a new strictness option for that? alternatively, should this be an argument on "default" strictness instead of it's own case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had that (passing in the temporary directories from the client) initially actually, but that pushes the platform-specific nature of these paths onto the client. In addition to the paths, there could actually be a varying number of them on different platforms. But I agree that this shouldn't really be part of the strictness, but rather an option on the default strictness as you suggest. I think there are more of those in the future, such as whether or not to allow network, etc.

So yes, in retrospect I think it was a mistake to add the temporary directory handling as a separate strictness level, and it's something to be unwound in the future.

Really what I'd like to do is to change the Sandbox struct to a SandboxProfile or some other name that suggests that it's a set of options, and then that profile could be configured once and passed around so it can be applied to multiple calls later without having to thread through all the individual parameters (and perhaps also cached using operating system calls based on the contents of that profile). Then as we add more parameters, none of the intermediate code has to change. The code that calls the subprocess could just apply the same profile to one command after another. I'm hoping to get to that improvement soonish, in particular since I'm hopeful that it could allow compilation of the profile on macOS just once and the reuse of the compiled profile, saving time when starting sandboxed subprocesses.

At that point it would make sense to get rid of strictness, I think, and just have a set of individually controllable options, and then a convenience initializer to create a SandboxProfile that matches the SwiftPM 5.3 semantics. That special case could then exist outside of the SandboxProfile code itself as an extension, which seems more correct than leaking it into the sandbox API.

All of course for a future PR, but hopefully not too long in the future.

@abertelrud abertelrud merged commit c117c86 into swiftlang:main Jan 9, 2022
@abertelrud abertelrud deleted the eng/enforce-read-only-ness-of-package-directory-in-a-better-way branch January 9, 2022 17:41
abertelrud added a commit to abertelrud/swift-package-manager that referenced this pull request Jan 9, 2022
…table directories (swiftlang#3996)

* Enforce read-only-ness of package directory even when it's inside /tmp and other writable directories

The sandbox supports a list of `writableDirectories`, which are treated as path component prefixes after package resolution.  The are applied to the sandbox rules after the default-deny and everything-is-read-only rules, carving out cones of writability in specific areas of the file system.

But if the package directory is under one of these directories that is allowed by default, then it is implicitly writable even in the absence `--allow-writing-to-package-directory`.  It also makes unit tests awkward, since they usually create temporary test fixtures inside the temporary directory.

This adds a set of `readOnlyDirectories` in addition to the writable ones, so that the package directory remains unwritable regardless of where it is located.

It also adds some documentation in the Sandbox source file, and it removes a somewhat confusing `--block-writing-to-package-directory` flag that was added mostly for unit tests but that is no longer needed.

(cherry picked from commit c117c86)
abertelrud added a commit that referenced this pull request Jan 9, 2022
…table directories (#3996) (#3999)

* Enforce read-only-ness of package directory even when it's inside /tmp and other writable directories

The sandbox supports a list of `writableDirectories`, which are treated as path component prefixes after package resolution.  The are applied to the sandbox rules after the default-deny and everything-is-read-only rules, carving out cones of writability in specific areas of the file system.

But if the package directory is under one of these directories that is allowed by default, then it is implicitly writable even in the absence `--allow-writing-to-package-directory`.  It also makes unit tests awkward, since they usually create temporary test fixtures inside the temporary directory.

This adds a set of `readOnlyDirectories` in addition to the writable ones, so that the package directory remains unwritable regardless of where it is located.

It also adds some documentation in the Sandbox source file, and it removes a somewhat confusing `--block-writing-to-package-directory` flag that was added mostly for unit tests but that is no longer needed.

(cherry picked from commit c117c86)
abertelrud added a commit to abertelrud/swift-package-manager that referenced this pull request Jan 12, 2022
…der `<pkgdir>/.build`

The sandbox rules introduced in swiftlang#3996 made that the entire package directory read-only, but that isn't appropriate when `.build` is inside the package directory.  This changes the order of the operations so that the `allow` comes after the `deny`, but still after the temporary directories.

In order to make this more flexible this coalesces the lists of writable and read-only directories into a single `pathRules` list of a new enum type that allows writing or read-only paths, and allows the caller to control the order.  Also, writability of temporary directories is pulled into an option on the `default` strictness.

rdar://87417780
abertelrud added a commit to abertelrud/swift-package-manager that referenced this pull request Jan 12, 2022
…der `<pkgdir>/.build`

The sandbox rules introduced in swiftlang#3996 made the entire package directory read-only, but that isn't appropriate when `.build` is inside the package directory.

This change turns the stateless Sandbox enum into a SandboxProfile struct that can be configured and passed around well before being applied, and it makes the configuration more flexible in several regards:
- there is a list of path rules allowing a mixed order of `allow` and `deny` rules
- the choice of allowing writing to temporary directories is independent of anything other choice
- the defaults are built into the initializer rather than a separate `strictness` parameter — this means that they can be queried once the SandboxProfile has been created and are expressed in terms of the choices available to all sandbox profiles.

Having the sandbox profile be a struct that generates the platform specifics as needed also provides a place with which to associate any cached/compiled representation for profiles that are largely static.

As cleanup, this commit also removes the pre-SwiftPM 5.3 specialities which were specific to running the package manifest in an interpreter rather than compiling and executing it.  This functionality is no longer relevant since it isn't possible to run any manifest in the interpreter.

In this commit, the call sites have been adjusted so that they use the modified SandboxProfile API, but still construct the profiles on-the-fly as before.  A future change could make them instead be configured at an early point and then applied later when the sandboxed process is actually launched.  Another future change could add the sandbox profile to Process as a property so that there is no API assumption that applying a sandbox necessarily involves just modifying the command line.

rdar://87417780
abertelrud added a commit to abertelrud/swift-package-manager that referenced this pull request Jan 12, 2022
…der `<pkgdir>/.build`

The sandbox rules introduced in swiftlang#3996 made the entire package directory read-only, but that isn't appropriate when `.build` is inside the package directory.

This change turns the stateless Sandbox enum into a SandboxProfile struct that can be configured and passed around well before being applied, and it makes the configuration more flexible in several regards:
- there is a list of path rules allowing a mixed order of `allow` and `deny` rules
- the choice of allowing writing to temporary directories is independent of anything other choice
- the defaults are built into the initializer rather than a separate `strictness` parameter — this means that they can be queried once the SandboxProfile has been created and are expressed in terms of the choices available to all sandbox profiles.

Having the sandbox profile be a struct that generates the platform specifics as needed also provides a place with which to associate any cached/compiled representation for profiles that are largely static.

As cleanup, this commit also removes the pre-SwiftPM 5.3 specialities which were specific to running the package manifest in an interpreter rather than compiling and executing it.  This functionality is no longer relevant since it isn't possible to run any manifest in the interpreter.

In this commit, the call sites have been adjusted so that they use the modified SandboxProfile API, but still construct the profiles on-the-fly as before.  A future change could make them instead be configured at an early point and then applied later when the sandboxed process is actually launched.  Another future change could add the sandbox profile to Process as a property so that there is no API assumption that applying a sandbox necessarily involves just modifying the command line.

rdar://87417780
abertelrud added a commit to abertelrud/swift-package-manager that referenced this pull request Jan 13, 2022
…der `<pkgdir>/.build`

The sandbox rules introduced in swiftlang#3996 made the entire package directory read-only, but that isn't appropriate when `.build` is inside the package directory.

The rules for applying read-only directories were intended to shadow the implicit rules added by specifying a writable temporary directory, not to block the ones specified by explicit writable directories.  This fixes that ordering, and adds the missing unit test that would have caught the problem.

A future change reworks Sandbox completely so that it becomes a struct that can carry around the sandbox profile configuration until it is applied — this change is intended to be small enough to be nominatable

rdar://87417780
abertelrud added a commit to abertelrud/swift-package-manager that referenced this pull request Jan 13, 2022
…der `<pkgdir>/.build`

The sandbox rules introduced in swiftlang#3996 made the entire package directory read-only, but that isn't appropriate when `.build` is inside the package directory.

The rules for applying read-only directories were intended to shadow the implicit rules added by specifying a writable temporary directory, not to block the ones specified by explicit writable directories.  This fixes that ordering, and adds the missing unit test that would have caught the problem.

A future change reworks Sandbox completely so that it becomes a struct that can carry around the sandbox profile configuration until it is applied — this change is intended to be small enough to be nominatable

rdar://87417780
abertelrud added a commit that referenced this pull request Jan 13, 2022
…der `<pkgdir>/.build` (#4009)

The sandbox rules introduced in #3996 made the entire package directory read-only, but that isn't appropriate when `.build` is inside the package directory.

The rules for applying read-only directories were intended to shadow the implicit rules added by specifying a writable temporary directory, not to block the ones specified by explicit writable directories.  This fixes that ordering, and adds the missing unit test that would have caught the problem.

A future change reworks Sandbox completely so that it becomes a struct that can carry around the sandbox profile configuration until it is applied — this change is intended to be small enough to be nominatable

rdar://87417780
abertelrud added a commit to abertelrud/swift-package-manager that referenced this pull request Jan 13, 2022
…t's under `<pkgdir>/.build`

The sandbox rules introduced in swiftlang#3996 made the entire package directory read-only, but that isn't appropriate when `.build` is inside the package directory.

The rules for applying read-only directories were intended to shadow the implicit rules added by specifying a writable temporary directory, not to block the ones specified by explicit writable directories.  This fixes that ordering, and adds the missing unit test that would have caught the problem.

rdar://87417780

(cherry picked from commit c3f5261)
abertelrud added a commit that referenced this pull request Jan 15, 2022
…t's under `<pkgdir>/.build` (#4018)

The sandbox rules introduced in #3996 made the entire package directory read-only, but that isn't appropriate when `.build` is inside the package directory.

The rules for applying read-only directories were intended to shadow the implicit rules added by specifying a writable temporary directory, not to block the ones specified by explicit writable directories.  This fixes that ordering, and adds the missing unit test that would have caught the problem.

rdar://87417780

(cherry picked from commit c3f5261)
abertelrud added a commit to abertelrud/swift-package-manager that referenced this pull request Apr 19, 2022
…der `<pkgdir>/.build`

The sandbox rules introduced in swiftlang#3996 made the entire package directory read-only, but that isn't appropriate when `.build` is inside the package directory.

This change turns the stateless Sandbox enum into a SandboxProfile struct that can be configured and passed around well before being applied, and it makes the configuration more flexible in several regards:
- there is a list of path rules allowing a mixed order of `allow` and `deny` rules
- the choice of allowing writing to temporary directories is independent of anything other choice
- the defaults are built into the initializer rather than a separate `strictness` parameter — this means that they can be queried once the SandboxProfile has been created and are expressed in terms of the choices available to all sandbox profiles.

Having the sandbox profile be a struct that generates the platform specifics as needed also provides a place with which to associate any cached/compiled representation for profiles that are largely static.

As cleanup, this commit also removes the pre-SwiftPM 5.3 specialities which were specific to running the package manifest in an interpreter rather than compiling and executing it.  This functionality is no longer relevant since it isn't possible to run any manifest in the interpreter.

In this commit, the call sites have been adjusted so that they use the modified SandboxProfile API, but still construct the profiles on-the-fly as before.  A future change could make them instead be configured at an early point and then applied later when the sandboxed process is actually launched.  Another future change could add the sandbox profile to Process as a property so that there is no API assumption that applying a sandbox necessarily involves just modifying the command line.

rdar://87417780
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ready Author believes the PR is ready to be merged & any feedback has been addressed
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants