Sandbox issue with macOS 10.15 and proposed fix, looking for feedback

It turns out that on darwin our stdenv sandbox includes /bin/sh (not sure how this is dealt with on linux, maybe /bin/sh is exposed to the chroot? I haven’t dug into that). It also turns out that on macOS 10.15, /bin/sh is observed to be roughly equivalent to the following shelllscript:

shell=$(readlink /private/var/select/sh) || shell=/bin/bash
exec "$shell" "$@"

What this means is that allowing access to /bin/sh isn’t sufficient on macOS, we need to allow access to /bin/bash (but denying access to /private/var/select/sh is fine and will ensure /bin/sh resolves to /bin/bash instead of another shell).

Unfortunately we don’t allow access to /bin/bash by default, and Nix doesn’t allow that as an impure host dep prefix either.

I’ve filed darwin: DEFAULT_ALLOWED_IMPURE_PREFIXES needs to include /bin/bash on macOS 10.15 · Issue #3223 · NixOS/nix · GitHub regarding adding /bin/bash to the default set of allowed impure host prefixes on darwin, but we also need to fix it so /bin/bash actually gets into the sandbox profile. As explained in that issue, there are two approaches that could work:

  1. Add /bin/bash to commonImpureHostDeps.
  2. Tweak Nix itself when constructing the sandbox to look for /bin/sh and insert /bin/bash as well if it’s found.

The former means rebuilding the entire darwin world as every stdenv derivation will change. The latter doesn’t rebuild anything, and can be targeted to macOS 10.15 as well. It’s not clear to me which is actually better.

Technically with the second approach we don’t actually need to modify DEFAULT_ALLOWED_IMPURE_PREFIXES but we should do it anyway in case someone explicitly lists /bin/bash in their impure host deps due to knowing about this macOS 10.15 quirk.

I’d love to get some feedback as to which approach is preferable.

Given that we need Nix release for Catalina anyway, it’s probably best to address it there.

However, adding /bin/bash impurity is quite bad, as we’d be in the bash 3 vs bash 4 hell again. Hope I’m mistaken.

/bin/bash is what /bin/sh invokes anyway. I actually thought /bin/sh was a hardlink to /bin/bash, looks like it’s technically a different binary (only 64 bytes different in size), but AFAIK executing /bin/sh is identical to executing /bin/bash with sh as argv[0].

My real question is why do we allow invoking /bin/sh at all, but maybe there’s no getting around it for things that invoke system().

In any case, in Catalina /bin/sh is no longer the shell itself but a wrapper that invokes /bin/bash, so we need to allow access to /bin/bash. This does unfortunately mean that someone could invoke /bin/bash directly, but I don’t see how to avoid that.

The only other possibility is if there’s some sort of undocumented env var that /bin/sh uses prior to consulting /private/var/select/sh that we could use to redirect it to Nix’s bash, but I’m not aware of such an env var offhand.

Edit: I checked, definitely no env var. Even if there was one, /bin/sh rejects any attempt to use a shell other than /bin/bash, /bin/dash, or /bin/zsh.

Thankfully, keeping /private/var/select/sh blacklisted means we’ll always get /bin/bash regardless of user customization. Though perhaps it’s worth patching Nix to explicitly deny access to that in case someone comes along and adds it to their extra-sandbox-paths. I’m not sure offhand how the Darwin sandbox handles the same path being both denied and allowed - which one takes precedence? We’ll want to figure that out so we can ensure the deny does. If the answer is “allow always takes precedence” then instead we’ll need to patch Nix to discard any attempt to allow access to /private/var/select/sh.

Bump, I’d still love to get some more feedback on this.

To summarize, which is a better approach:

  1. Patch Nix itself to insert /bin/bash into the sandbox when it sees /bin/sh and we’re running on macOS 10.15+, or
  2. Update stdenv’s commonImpureHostDeps to include /bin/bash, which will rebuild the entire world, and require a Nix patch to allow that anyway.

For the moment I’m leaning towards the first approach, because that will ensure anyone not using nixpkgs’s stdenv will still allow /bin/sh to work on macOS 10.15+ if they include it in the impure paths.

Can’t you add /bin/bash to sandbox-paths without having to patch Nix itself?

Every single macOS 10.15 user should not have to customize extra-sandbox-paths just to get the sandbox to work.

We already have the problem where users have to customize it for the system frameworks (and I have a few other items in there that I haven’t tracked down the need for yet), which I’m hoping to fix at some point (I have an open issue about the system frameworks issue that nobody’s providing feedback on, unfortunately). But in the case where they’ve got the sandbox working on macOS 10.14, upgrading to macOS 10.15 breaks it and I’d like to fix that. Nix users shouldn’t have to care that /bin/sh on macOS 10.15 runs /bin/bash.

For that matter, I also want to tweak it to explicitly blacklist /private/var/select/sh, regardless of how the sandbox is otherwise configured, in order to ensure /bin/sh is always /bin/bash and not e.g. /bin/zsh. And this definitely requires a Nix patch.

1 Like

Sure. Regardless, it’d be a simpler Nix patch to just add /bin/bash to the default sandbox-paths on macOS.

Sure, except Nix doesn’t default sandbox-paths to containing /bin/sh today, that’s done via nixpkgs’s commonImpureHostDeps in the stdenv.