Skip to content

Add basic Apple sandbox support for "chroot" builds on darwin [WIP]#434

Closed
copumpkin wants to merge 1 commit intoNixOS:masterfrom
copumpkin:darwin-sandbox
Closed

Add basic Apple sandbox support for "chroot" builds on darwin [WIP]#434
copumpkin wants to merge 1 commit intoNixOS:masterfrom
copumpkin:darwin-sandbox

Conversation

@copumpkin
Copy link
Copy Markdown
Member

This uses Apple's sandbox-exec tool (mostly undocumented beyond its man page, but there's a bit of information on it floating around the web) to give build children very precise and limited capabilities when building. They can't talk to the network, see other processes on the system, or see any paths outside of what we explicitly include. This works through a lispy sandbox specification language. My change just teaches nix to construct such a specification on the fly and then feeds it into the existing Apple sandbox mechanism.

I've bundled this with a new attribute on derivation to allow for per-derivation impurities. This is crucial for darwin, since in a partially closed system, we're going to need to call out to many of the system libraries we can't build ourselves at times. The sandbox and precise instructions to pierce it allow us to keep that impurity to a minimum.

The attribute is called __impureHostDeps for now, but I'm open to better names or designs. My current view is that it could even replace some of the functionality in current linux chroot builds. In my pure darwin nixpkgs, for example, I have /bin/sh in that list, and that makes sense on linux, too, along with some of the ubiquitous /dev paths.

Looking for comments, criticism, and the like. I have no automated tests for the functionality, but I'm already using the code in this branch on my pure darwin branch and it seems to work.

Relevant to #317 and #361.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I'm not sure what the conventions are in this code base, but why is a foreach macro being used here, when a ranged-based for loop appears 12 lines above?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Yeah, foreach is obsolete now that we're requiring C++11.

@edolstra
Copy link
Copy Markdown
Member

edolstra commented Jan 7, 2015

Is __impureHostDeps still needed if the Apple sandbox support also implements build-chroot-dirs?

@shlevy
Copy link
Copy Markdown
Member

shlevy commented Jan 7, 2015

@edolstra The idea is that it's not really a global system-wide thing, different derivations will need different accesses. In fact, while I haven't looked through this impl yet to see if @copumpkin has done it already, the plan is to have __impureHostDeps supercede build-chroot-dirs on Linux too, since it's really a property of a derivation, not the system, which files are needed. The idea is that __impureHostDeps will have basically the same syntax as build-chroot-dirs and the darwin impl will bail out when a foo=bar where foo is not the same as bar is specified.

@shlevy
Copy link
Copy Markdown
Member

shlevy commented Jan 7, 2015

I see that indeed __impureHostDeps is supported on Linux too. @copumpkin Before this is done we should probably update the documentation on that and possibly deprecate build-chroot-dirs

@edolstra
Copy link
Copy Markdown
Member

edolstra commented Jan 7, 2015

The idea that derivations can bind-mount arbitrary parts of the host filesystem sounds very scary to me, and not something we should have on Linux.

@shlevy
Copy link
Copy Markdown
Member

shlevy commented Jan 7, 2015

Why? It's a private mount namespace...

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This gives a compile error on Linux:

src/libstore/build.cc: In member function 'void nix::DerivationGoal::runChild()':
src/libstore/build.cc:54:25: error: '__APPLE__' was not declared in this scope

@edolstra
Copy link
Copy Markdown
Member

edolstra commented Jan 7, 2015

For example, you can set

__impureHostDeps = "/root/.config";

and get access to that directory, even if /root is not world-readable.

@shlevy
Copy link
Copy Markdown
Member

shlevy commented Jan 7, 2015

Ah OK, fair enough. So what can we do about this issue? Perhaps set a set of acceptable parent dirs, like /usr/bin, in the config? There simply is not a good set of paths we can specify system-wide on darwin (and I still stand by my point, that even on Linux this is more a property of a derivation).

@edolstra
Copy link
Copy Markdown
Member

edolstra commented Jan 7, 2015

Well, on Linux we don't really need it because we have a pure stdenv.

But a set of permissible parent directories sounds good.

@shlevy
Copy link
Copy Markdown
Member

shlevy commented Jan 7, 2015

Well... Except for /bin/sh, which currently needs a full list of transitive deps but if it were specified in a derivation attr that'd be taken care of by inputDrvs. And except for the cases where some people have (private) build scripts that use /usr/bin/env enough that they've had to add it to their config.

@copumpkin Can you add a setting to limit the parent dirs allowed for the per-drv attr? This will probably mean that stuff in /dev will have to remain hard-coded in the code or config file

@copumpkin
Copy link
Copy Markdown
Member Author

That all makes sense. I think we can limit parents on darwin to /System/Library/Frameworks and /usr/lib, since I think those are the only impurities we'll ever need. I'll just make it be a canonicalized prefix of the path, so that if we do need very specific files from elsewhere (only example I can think of for now is dsymutil, which we currently have a no-op shim for but could change that) we can enable them.

I'll make the changes tonight. @edolstra, apart from that, does this seem like a reasonable direction to take things?

@copumpkin
Copy link
Copy Markdown
Member Author

Never mind the dsymutil part actually, since there's a purer solution.

@edolstra
Copy link
Copy Markdown
Member

edolstra commented Jan 7, 2015

Yes, looks good to me.

@copumpkin
Copy link
Copy Markdown
Member Author

Great, I'm really excited. I think this will be a key ingredient to accelerate a quality pure darwin stdenv (which is already pretty close to being usable). So far I've been doing all my development with an ad-hoc sandbox wrapped around my calls to nix-build to catch/prevent impurities, but getting this in will speed things up a fair amount and help other developers pick up the pure-darwin work more easily.

@shlevy
Copy link
Copy Markdown
Member

shlevy commented Jan 7, 2015

@copumpkin FWIW, even with those reasonable defaults I'd like the set of allowable prefixes to be configurable.

@copumpkin
Copy link
Copy Markdown
Member Author

@shlevy definitely! I was going to make it another configurable field with a sensible default. Can I ship a different default conf file based on target platform?

@shlevy
Copy link
Copy Markdown
Member

shlevy commented Jan 7, 2015

@copumpkin You can wrap the default logic (which is in c++, not a default config file) in ifdefery. I'm not sure another way.

@copumpkin
Copy link
Copy Markdown
Member Author

Fair enough 😄

@copumpkin copumpkin force-pushed the darwin-sandbox branch 6 times, most recently from bf4b744 to 5327c1c Compare January 8, 2015 06:50
@copumpkin
Copy link
Copy Markdown
Member Author

Updated again and also fixed a slight bug that let pure-darwin's cmake see too much and thus fail.

I don't think the resulting sandbox profile is the best just yet (or that the code to generate it is the best factored), but I really want to get a MVP for this working so I can get back to the more difficult pure-darwin nixpkgs work. Thus I'm not trying to make this PR as good as it can get, but do plan on revisiting/cleaning up this code once I have pure-darwin in a place where more people can work on it.

Main things I'd like to do better:

  • factor the c++ better, and allow nix to abstract over the sandboxing mechanism (so perhaps we can eventually do lxc containerized builds on linux, jails on bsd, and who knows what on windows someday)
  • factor the sandbox spec better, to allow the kernel to cache precompiled bytecode of unchanging parts of it across runs, and for us to generate less boilerplatey code

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@edolstra /bin and /usr/bin OK here?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

(for /bin/sh and /usr/bin/env)

@copumpkin
Copy link
Copy Markdown
Member Author

@edolstra actually, I feel a little awkward about keeping "dirs" in the name because neither the configuration key nor the derivation attribute are necessarily directories. In practice, the per-derivation one will usually be individual files, and the configuration one contains one individual file. It seems a little icky to have "/bin/sh" as one of my build-allowed-chroot-dirs, right?

Anyway, I'm not going to bikeshed this too much, since as I said earlier my main goal is to get the basic functionality in (which we can refine before the next big release) so I can accelerate the stdenv development. If you feel strongly about the "dirs" name, I'll put it in for now and move forward.

@copumpkin
Copy link
Copy Markdown
Member Author

Also, does anyone other than @edolstra have opinions on this? A quick recap:

  • __impureHostDeps: a per-derivation attribute that allows it to specify holes in the sandbox/chroot
  • allowed-impure-host-deps: a global configuration setting (non-empty defaults in darwin, empty elsewhere) that specifies which path prefixes are allowed in __impureHostDeps. For example, if your derivation specifies __impureHostDeps = [ "/Users/you/mySecretFile" ] and allowed-impure-host-deps doesn't contain /, /Users/, /Users/you/, or /Users/you/mySecretFile, you will get an error when building your derivation.

@shlevy
Copy link
Copy Markdown
Member

shlevy commented Jan 9, 2015

Hey, I have an opinion too! 👅

@copumpkin
Copy link
Copy Markdown
Member Author

@shlevy you're not @edolstra (as far as I know, at least!), so you were included in that! You hadn't expressed an opinion on the deps-vs-dirs naming question 😄

@shlevy
Copy link
Copy Markdown
Member

shlevy commented Jan 9, 2015

Oh, I thought you were asking for opinions about the work overall 😀 Yeah, I'm in favor of deps or files over dirs.

@copumpkin
Copy link
Copy Markdown
Member Author

(also, my goal is to make "chroot" builds the default on darwin after the pure stdenv gets fully merged)

@edolstra
Copy link
Copy Markdown
Member

This patch still has a root information leak, namely, you can determine whether files in inaccessible directories exist. For instance, if /root/bla exists, then building

with import <nixpkgs> { }; runCommand "foo" { __impureHostDeps = "/root/bla"; } "ls -l /root/bla"

will print

error: derivation '/nix/store/8vxyw8jcsc41sflf4b2fwi28z9hik31h-foo.drv' requested impure path ‘/root/bla’, but it was not in allowed-impure-host-deps (‘’): No such file or directory

otherwise it prints

error: getting status of ‘/root/bla’: No such file or directory

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This should be Error, not SysError.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Will fix tonight!

On Jan 12, 2015, at 08:31, Eelco Dolstra [email protected] wrote:

In src/libstore/build.cc:

  •    /\* This works like the above, except on a per-derivation level */
    
  •    Strings impurePaths = tokenizeString<Strings>(get(drv.env, "__impureHostDeps"));
    
  •    for (auto & i : impurePaths) {
    
  •        bool found = false;
    
  •        Path canonI = canonPath(i, true);
    
  •        /\* If only we had a trie to do this more efficiently :) luckily, these are generally going to be pretty small */
    
  •        for (auto & a : allowedPaths) {
    
  •            Path canonA = canonPath(a, true);
    
  •            if (canonI == canonA || isInDir(canonI, canonA)) {
    
  •                found = true;
    
  •                break;
    
  •            }
    
  •        }
    
  •        if (!found)
    
  •            throw SysError(format("derivation '%1%' requested impure path ‘%2%’, but it was not in allowed-impure-host-deps (‘%3%’)") % drvPath % i % allowed);
    
    This should be Error, not SysError.


Reply to this email directly or view it on GitHub.

@copumpkin
Copy link
Copy Markdown
Member Author

Yeah, I'm not seeing it primarily as a security mechanism, but I've also noticed that the types of errors you get aren't what they'd be if you were in a blank system. It's still what Apple itself (and Chrome) uses to wall off their system services, so I expect that apart from the information issues it should still be reasonably secure. I don't think there's anything much we can do about the FS error types, but am open to suggestions!

On Jan 12, 2015, at 08:30, Eelco Dolstra [email protected] wrote:

This patch still has a root information leak, namely, you can determine whether files in inaccessible directories exist. For instance, if /root/bla exists, then building

with import { }; runCommand "foo" { __impureHostDeps = "/root/bla"; } "ls -l /root/bla"
will print

error: getting status of ‘/root/bla’: No such file or directory
otherwise it prints

error: derivation '/nix/store/8vxyw8jcsc41sflf4b2fwi28z9hik31h-foo.drv' requested impure path ‘/root/bla’, but it was not in allowed-impure-host-deps (‘’): No such file or directory

Reply to this email directly or view it on GitHub.

@copumpkin
Copy link
Copy Markdown
Member Author

Oh, sorry, you're talking about the permitted prefix mechanism! Can people catch those nix-level errors?

On Jan 12, 2015, at 08:30, Eelco Dolstra [email protected] wrote:

This patch still has a root information leak, namely, you can determine whether files in inaccessible directories exist. For instance, if /root/bla exists, then building

with import { }; runCommand "foo" { __impureHostDeps = "/root/bla"; } "ls -l /root/bla"
will print

error: getting status of ‘/root/bla’: No such file or directory
otherwise it prints

error: derivation '/nix/store/8vxyw8jcsc41sflf4b2fwi28z9hik31h-foo.drv' requested impure path ‘/root/bla’, but it was not in allowed-impure-host-deps (‘’): No such file or directory

Reply to this email directly or view it on GitHub.

@shlevy
Copy link
Copy Markdown
Member

shlevy commented Jan 12, 2015

@copumpkin I think the issue @edolstra points to can be fixed simply by checking the prefix before checking if the file exists.

@copumpkin
Copy link
Copy Markdown
Member Author

Oh, I thought I was already doing that! Will definitely add that later and
figure out why I thought I had :)

On Monday, January 12, 2015, Shea Levy [email protected] wrote:

@copumpkin https://github.com/copumpkin I think the issue @edolstra
https://github.com/edolstra points to can be fixed simply by checking
the prefix before checking if the file exists.


Reply to this email directly or view it on GitHub
#434 (comment).

@edolstra
Copy link
Copy Markdown
Member

@shlevy Yes, but you have to ensure that the path does not contain .. elements and other obfuscations. Which is what canonPath does, but it throws an error because it's called with resolveSymlinks. But it probably doesn't need to use resolveSymlinks in the first place.

@shlevy
Copy link
Copy Markdown
Member

shlevy commented Jan 12, 2015

Ah, so perhaps we should catch the exception from canonPath and print the same error message if canonPath throws or if the prefix isn't matched?

@copumpkin
Copy link
Copy Markdown
Member Author

@edolstra can you elaborate on the precise threat you're seeing? That a non-root user can learn things about the root account, when running with the daemon? Is it that ": No such file or directory" part that (I assume?) comes from resolveSymlinks?

I don't have too much of a sense of how the daemon works, so apologies for being dense.

I can easily turn off the resolveSymlinks flag, but am not sure what else is needed.

@edolstra
Copy link
Copy Markdown
Member

@copumpkin Yes, that's exactly right. I've just merged this PR with resolveSymlinks disabled. Thanks!

@edolstra edolstra closed this Jan 13, 2015
@copumpkin
Copy link
Copy Markdown
Member Author

Thank you! How long before nixUnstable becomes a real thing again?

@shlevy
Copy link
Copy Markdown
Member

shlevy commented Jan 13, 2015

It's pretty easy to make an unstable "release", IMO if we have users using it we should just release it.

@copumpkin
Copy link
Copy Markdown
Member Author

Looks like the hydra build nixUnstable usually builds off of is broken.

@datakurre
Copy link
Copy Markdown

@copumpkin What am I missing? I have a clean nix 1.9 on OSX 10.10 with CLI tools installed. When I try to install something, the derivation build fails with:

sandbox-exec: /bin/sh: Operation not permitted

My /etc/nix/nix.conf is:

build-use-chroot = true
use-binary-caches = false

@copumpkin
Copy link
Copy Markdown
Member Author

@datakurre nix knows how to sandbox things, but the darwin stdenv in nixpkgs is currently still quite impure and not set up to behave properly in the presence of sandboxes. There's a massive effort off in a branch called pure-darwin in my fork that we're considering merging back to master soon. It's not yet finished, but it might actually be better than what we have in master right now.

cc @pikajude

@datakurre
Copy link
Copy Markdown

@copumpkin Thanks. I try to continue with pure-darwin copumpkin/nixpkgs#77

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants