Brew: Recent regression on sandboxed installs as root

Created on 23 Aug 2016  ·  39Comments  ·  Source: Homebrew/brew

Up until recently, root-owned homebrew worked. There appears now to be an issue where the sandboxed brew install can no longer access TLS certificates when run as the root user.

$ sudo /opt/homebrew/bin/brew install pkg-config
==> Using the sandbox
==> Downloading https://pkgconfig.freedesktop.org/releases/pkg-config-0.29.1.tar.gz

curl: (35) SSL certificate problem: Couldn't understand the server certificate format
Trying a mirror…

Compare to non-root-owned:

$ brew install --sandbox --build-from-source pkg-config
==> Using the sandbox
==> Downloading https://pkgconfig.freedesktop.org/releases/pkg-config-0.29.1.tar.gz
######################################################################## 100.0%
==> ./configure --prefix=/usr/local/Cellar/pkg-config/0.29.1_1 ...

Most helpful comment

@ilovezfs and I'll finish it :rage4:

All 39 comments

Thanks for spotting the underlying issue: that this is a root problem.

CC @xu-cheng for help with a fix here.

In the mean time: we/I strongly recommend not running Homebrew as root. We don't drop privileges so any random Makefile could sudo rm -rf any file on your machine. Arguably the sandbox is even more important for root-owned Homebrew so I don't think we should disable it there but I'm wondering whether we should support root-owned Homebrew at all until someone who cares about it implements dropping privileges.

I'm trying to bisect to determine the exact location of the problem, but homebrew auto-updating is getting in my way :( It's at least more recent than ed5e2ea3003acc858e0886cb1c4a944f44ba598e

It will be almost certainly https://github.com/Homebrew/brew/pull/713

Ah, of course. Shouldn't this be fixable by allowing the sandbox to read TLS CAs? We have a use-case at Square where we want to have a system-installed brew that has base packages we want for installing system software, that won't collide with a user's own homebrew installation.

Probably. Feel free to beat me or @xu-cheng to a PR.

It looks from https://github.com/Homebrew/brew/issues/785 that the sandbox is trying and failing to write to /private/var/db/mds/system/mds.lock, if that helps.

It looks like the sandbox basically doesn't work as root.

Does the build continue once it's been fetched as a non-root user?

Ah, excellent. Of course, as you said, that's the exact case where you'd want it most.

We'll need to think how we're going to address this as a team. As a workaround you can set HOMEBREW_NO_SANDBOX but I'm increasingly personally tempted to say we just refuse to let Homebrew be run as root until privilege dropping is implemented.

Yep. Bare sudo ends up reusing ~/Library/Caches/Homebrew so after the source package is downloaded there it appears to build without issue.

It works as, e.g., the daemon user so this issue appears to be very specific to root.

@Homebrew/maintainers thoughts on how to address this? The two ways I see:

  • Disable sandbox as root and print a big, fat warning that this is a bad idea
  • Stop allowing Homebrew to run as root (my preferred option) until someone implements privilege dropping (i.e. the build itself runs as a non-root low-privilege user e.g. nobody)

Stop allowing Homebrew to run as root

this.

I know the Homebrew collective opinion is that the best experience is running Homebrew in /usr/local and changing the permissions so that the brew user can write there without sudo, but I don't want to do that. I have 3rd party binary packages not managed by brew that install into /usr/local. I also compile and install software not controlled bybrewinto/usr/local.

Homebrew should support a scenario where Homebrew is not in /usr/local because the brew package manager can't reasonably expect to have exclusive control over the contents of /usr/local. That is why sysV added the concept of /opt for the package manager. It's also decent security to not have the normal unprivileged user account to not have write permission to any directories that are in the $PATH.

  • Installed /opt/brew
  • /opt/brew is owned by root:wheel with 755.

It sounds like the ideal thing would be for brew to drop its privileges for downloading packages and tarballs and performing the build when run as root and only use the root privilege for the install step -- but failing that, please don't intentionally break this workflow. Print out a warning if you feel like that is necessary.

I know the Homebrew collective opinion is that the best experience is running Homebrew in /usr/local and changing the permissions so that the brew user can write there without sudo, but I don't want to do that.
Homebrew should support a scenario where Homebrew is not in /usr/local because the brew package manager can't reasonably expect to have exclusive control over the contents of /usr/local.

You can install Homebrew in any location as any user, it's just not our default.

It's also decent security to not have the normal unprivileged user account to not have write permission to any directories that are in the $PATH.
It sounds like the ideal thing would be for brew to drop its privileges for downloading packages and tarballs and performing the build when run as root and only use the root privilege for the install step -- but failing that, please don't intentionally break this workflow. Print out a warning if you feel like that is necessary.

It's terrible, terrible security to have every build-from-source build system running as root. A mistake in (or malicious actor modifying upstream) a single Makefile can do whatever they want to any file on your system. We/I don't trust every upstream to maintain a bug-free and uncompromised build system code and neither should you (which is why we introduced the sandbox).

Privilege dropping will be a non-trivial amount of work that none of the current maintainers will want to implement (we're volunteers working mostly in our spare time and it's low on our list of priorities). If you were able to implement it before I get the change to disable building as root then that would work. I'm not really in favour of allowing a security hole like this to go unplugged with a warning that most people will ignore.

@MikeMcQuaid I agree, the build shouldn't run as root. I didn't realize that brew runs the entire process as root. Homebrew shouldn't do that when run with sudo. MacPorts runs build under the unprivileged macports user context.

@breiter Assuming no code changes: will you personally do anything differently now?

@MikeMcQuaid Well... I recently evaluated Joyent's pkgsrc for OS X. I had some problems with library paths that were annoying but not insurmountable and also libgcrypt-cofig generated garbage that /usr/bin/ld couldn't understand which also was fixable, but the final straw was I couldn't get awscli to work when installed with pip from pkgsrc. So I would think long and hard about going back to MacPorts and just be sad that they aren't integrated with GitHub. (Their Trac system is unlovely.)

Ok so basically "not use Homebrew". FYI you could just use your identical workflow as any non-root user or your choice (say brewuser) and it'll work fine if that brewuser owns the Homebrew installation in /opt and you sudo -u brewuser brew instead of sudo brew.

Stop allowing Homebrew to run as root

Very much in favor of this option!

I'd love Homebrew to have saner root handling in terms of being able to drop privileges as appropriate, although I wouldn't necessarily say MacPorts' mechanism is particularly elegant; I don't mean disrespect to them by that, I add.

I have been meaning to do some thinking about this, but it's really not near the top of my priority list to say the least. Happy to help review any PR to improve our handling on this sort of thing for sure though.

It's also decent security to not have the normal unprivileged user account to not have write permission to any directories that are in the $PATH.

I'm honestly not sure this argument particularly works today. I mean, in theory sure, but npm recommend making their directories user-owned if installing in /usr/local, which is the default for Node's .pkg installer, for example. You also have situations like GOPATH & Perl's local::lib which IIRC can both be set up pretty much anywhere and are both traditionally user-owned.

People consistently favour usability over security, even when you try really _really_ hard to make them favour security. There's a whole bunch of people who would still be using MD5 in Homebrew today if we let them. We've recommended for a long time for people in /usr/local to not force link openssl but a ton of people still did so, because it solved their use case or made finding the desired OpenSSL easier on their project, so we ended up turning that into a hard block.

Disable sandbox as root and print a big, fat warning that this is a bad idea

I'd probably, with some reluctance, go with this.

I'd probably, with some reluctance, go with this.

I don't see how the cost-benefit analysis for that could possibly pay off.

I was reading some producty stuff earlier and I thought the correct answer is both:

Disable sandbox as root and print a big, fat warning that this is a bad idea

We do this for a short while (week/month/3 months) and say that it'll be turned off later.

Stop allowing Homebrew to run as root (my preferred option) until someone implements privilege dropping (i.e. the build itself runs as a non-root low-privilege user e.g. nobody)

We do this after the above warning period.

I don't see how the cost-benefit analysis for that could possibly pay off.

Well, until last week, nobody who wasn't opted-in was using the sandbox. From Homebrew's inception, I believe, people have been able to run brew as root if it is owned by root. It's ultimately less surprising to selectively roll back a week or so for some users than it is several years.

Security flaws we may or may not have had in the past don't justify bad security practices going forward. They are spilt milk.

Being less surprising to some doesn't outweigh possibly compromising others unless our priorities are upside down and backwards.

Lol, the other night you were dead against reproducible bottles despite the obvious security issue around having everyone ultimately trust the CI 🙈.

Ad hominem and non sequitur. Double bonus points.

We do this for a short while (week/month/3 months) and say that it'll be turned off later.

@MikeMcQuaid This seems like overkill, but meh

Ad hominem and non sequitur

Fancy words, but not particularly true on either case. I'm afraid it isn't actually a personal attack if I point out something you said yesterday conflicts with something you said today, and you may have a reason for that that is perfectly valid, but it's reasonable to question why x applies to y but not z.

As for all homebrew/brew things, file a PR or go with whatever the consensus turns out to be.

We do this for a short while (week/month/3 months) and say that it'll be turned off later.

This seems fine, personally. We could include a message that we'll accept a PR within that timeframe to improve Homebrew's root support, perhaps.

@DomT4 @ilovezfs don't make me bang your heads together...

We could include a message that we'll accept a PR within that timeframe to improve Homebrew's root support, perhaps.

👍

@MikeMcQuaid but he started it! :baby:

@ilovezfs and I'll finish it :rage4:

FYI after some digging, if (allow file-write-data (path "/private/var/db/mds/system/mds.lock")) is added in the sandbox rule, the bug described in this issue will be fixed. However, I don't know why curl is trying to write this file under root user.

@DomT4

I'm honestly not sure this argument particularly works today. I mean, in theory sure, but npm recommend making their directories user-owned if installing in /usr/local, which is the default for Node's .pkg installer, for example. You also have situations like GOPATH & Perl's local::lib which IIRC can both be set up pretty much anywhere and are both traditionally user-owned.

I think this is a worrying trend and attitude to take. Windows used to be ridiculed for having '.' implicitly in the %PATH% which opened it to all kinds of practical (and to this day) spoofing attacks for both binary hijacking and dll injection.

$GOPATH is a developer workspace, that is a bit of a different thing. If Node is recommending making /usr/local user-owned that is unfortunate. Maybe someone at Joyent -- like Bryan Cantrill -- should look into whether that is really a best practice.

I don't like the idea of a package manager (a) thinking it is OK to take over /usr/local nor (b) making part of the search path writeable by an unprivileged user.

I don't like the idea of a package manager (a) thinking it is OK to take over /usr/local nor (b) making part of the search path writeable by an unprivileged user.

You may want to use a different package manager.

(a) thinking it is OK to take over /usr/local

Just to nudge this back further, we don't take it over; nothing about Homebrew actively blocks you from installing other software into /usr/local at the same time.

You can install Homebrew anywhere you like with the exception of / and /usr & we've been actively working on reducing the amount of files we leaves in /usr/local & the level of privilege we need over the whole directory.

However, I don't know why curl is trying to write this file under root user.

It's a sandbox profile, apparently. Unsure at the moment why it doesn't write there when /usr/bin/curl is called without sudo:


clicky

;;
;; mds - sandbox profile
;; Copyright (c) 2006-2010 Apple Inc.  All Rights reserved.
;;
;; WARNING: The sandbox rules in this file currently constitute 
;; Apple System Private Interface and are subject to change at any time and
;; without notice. The contents of this file are also auto-generated and not
;; user editable; it may be overwritten at any time.
;;

(version 1)
(deny default)
(debug deny)
(disable-full-symbolication)
(import "system.sb")

;; Let us register our own name
(allow mach-register
       (global-name "com.apple.metadata.mds")
       (global-name "com.apple.metadata.mds.xpc")
       (global-name "com.apple.metadata.mds.xpcs"))

(allow file-fsctl)

(allow file-ioctl
    (literal "/dev/fsevents")
    (regex #"^/dev/nsmb")
    (regex #"^/private/var/folders/[^/]+/[^/]+/-Tmp-($|/)"))

(allow file-search)

(allow file-read*)

(allow file-write*
    (literal "/dev/console")
    (regex #"^/dev/nsmb")
    (literal "/private/var/db/mds/system/mds.lock")
    (literal "/private/var/run/mds.pid")
    (literal "/private/var/run/utmpx")
    (literal "/private/var/db/InstallResults.plist")
    (subpath "/private/var/folders/zz/zyxvpxvq6csfxvn_n0000000000000")
    (regex #"^/private/var/run/mds($|/)")
    (regex #"/Saved Spotlight Indexes($|/)")
    (regex #"/Backups.backupdb/\.spotlight_repair($|/)"))

(allow file-write* 
    (regex #"^/private/var/db/Spotlight-V100($|/)")
    (regex #"^/private/var/db/Spotlight($|/)")
    (regex #"^/Library/Caches/com\.apple\.Spotlight($|/)")
    (regex #"/\.Spotlight-V100($|/)")
    )


(allow file*
    (literal "/Library/Preferences/com.apple.SpotlightServer.plist")
    (literal "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/Metadata.framework/Versions/A/Resources/com.apple.SpotlightServer.plist"))

(allow file-read* file-write* (extension "com.apple.spotlight.tempdirectory"))
(allow file-read* file-write* (extension "com.apple.spotlight.cachedirectory"))

(allow file-write-xattr (xattr-regex #"^com\.apple\.metadata:"))

(allow job-creation
    (regex #"^/System/Library/Frameworks/CoreServices\.framework/Versions/A/Frameworks/Metadata\.framework/Versions/A/Support/mdworker(32)?$")
    (regex #"^/System/Library/Frameworks/CoreServices\.framework/Versions/A/Frameworks/Metadata\.framework/Versions/A/Support/mds_stores"))

(allow ipc-posix-shm)

;; (allow mach-lookup (global-name-regex "^com\.apple\."))

(allow mach-lookup 
    (global-name "com.apple.distributed_notifications@1v3")
    (global-name "com.apple.ocspd")
    (global-name "com.apple.SecurityServer")
    (global-name "com.apple.SystemConfiguration.configd")
    (global-name "com.apple.system.opendirectoryd.api")
    (global-name "com.apple.CoreServices.coreservicesd")
    (global-name "com.apple.coreservices.quarantine-resolver")
    (global-name "com.apple.DiskArbitration.diskarbitrationd")
    (global-name "com.apple.FSEvents")
    (global-name "com.apple.PowerManagement.control")
    (global-name "com.apple.tccd")
    (global-name "com.apple.metadata.mdbulkimport")
    (global-name "com.apple.metadata.mds.index")
    (global-name "com.apple.metadata.mds.scan")
    (global-name "com.apple.metadata.mdwrite")
    (global-name "com.apple.mdworker.bundles")
    (global-name "com.apple.mdworker.32bit")
    (global-name "com.apple.mdworker.isolation")
    (global-name "com.apple.mdworker.lsb")
    (global-name "com.apple.mdworker.mail")
    (global-name "com.apple.mdworker.shared")
    (global-name "com.apple.mdworker.single")
    (global-name "com.apple.mdworker.sizing")
    (global-name "com.apple.metadata.mdflagwriter")
    (global-name "com.apple.metadata.mds.spindump")
    (global-name "com.apple.coreduetd")
    (global-name "com.apple.bird")
    (global-name "com.apple.lsd.mapdb")
    (global-name "com.apple.lsd.modifydb")
    )

(allow mach-per-user-lookup)

(allow network-outbound
    (remote unix-socket (path-literal "/private/var/run/asl_input"))
    (remote unix-socket (path-literal "/private/var/run/syslog")))

(allow signal)

(allow sysctl*)

(allow distributed-notification-post)

(allow file-issue-extension (require-all
    (require-any
            (regex #"^/private/var/db/Spotlight-V100($|/)")
            (regex #"^/private/var/db/Spotlight($|/)")
            (regex #"^/Library/Caches/com\.apple\.Spotlight($|/)")
            (regex #"/\.Spotlight-V100($|/)"))
    (require-all (extension-class "com.apple.spotlight.indexer.read-write"))))

(allow file-issue-extension (require-all (extension-class "com.apple.spotlight.flagwriter.read-write")))
(allow file-issue-extension (require-all (extension-class "com.apple.spotlight.importer.readonly")))
(allow generic-issue-extension (require-all (extension-class "com.apple.mdworker.image_extension")))
(allow generic-issue-extension (require-all (extension-class "com.apple.mdworker.addressbook_extension")))
(allow generic-issue-extension (require-all (extension-class "com.apple.mdworker.calendar_extension")))

:+1: for sunsetting ability to use Homebrew with root and working around this in the meantime.

@MikeMcQuaid Since homebrew works as any other nonprivileged user, it seems completely reasonable to deprecate and eventually remove support for root-owned homebrew.

Was this page helpful?
0 / 5 - 0 ratings