Current state of trace mode?

Joshua Root jmr at macports.org
Mon Sep 10 15:30:30 PDT 2012


On 2012-9-9 18:18 , Jordan K. Hubbard wrote:
> 
> On Sep 3, 2012, at 9:15 AM, Joshua Root <jmr at macports.org> wrote:
> 
>> At a high level, this is what MacPorts wants:
>> * to deny write access to everything outside ${workpath} for most
>> phases, excepting also a handful of other places on a per-phase basis
>> (like ${distpath} for fetch).
>> * to deny read access to any file installed by a port that is not a
>> direct or indirect dependency of the port being built -- ideally it
>> looks to the build system like the files don't even exist.
>> * to deny read access to everything else that isn't part of the base OS
>> (e.g. /usr/local, /Library/Frameworks).
> 
> Hmm.  That could be done entirely by sandboxing, actually.  If all you're trying to do is deny filesystem operations and/or execution outside certain paths, all of that can be done with custom SBPL (sandbox profile language) code, numerous examples of which exist in /usr/share/sandbox.  sandbox-exec will also read this profile and apply it to a hierarchy of processes, so you don't even need to call any of the sandbox SPI calls to put the "current process" in a sandbox if what's being traced is already a sub-process (which I believe it is).

See, I thought that should be the case, but last time I went looking
(around the time Lion was released I believe) I couldn't find sufficient
docs to try anything. The man page for sandbox-exec is fine as far as it
goes, but where can I read about SBPL and these sandbox SPI calls in detail?

> Figuring out the filesystem access needs of the direct/indirect dependency graph is a harder problem unless every macport also (programmatically) creates a .sb file that expresses its own needs and can then be included by any ports which need it, which could be useful for other reasons (like actually sandboxing the port itself).   There are sandbox rule files like system.sb that can be (and frequently are) included to encapsulate all of the system requirements, so that part is easy.

I don't think it would be terribly difficult to construct a file
dynamically from registry information. What I don't know is how well it
will perform, given that there are a *lot* of files covered by some
dependency lists, and they will be listed individually.

>> We don't actually need to *know* what is being attempted, the OS just
>> has to allow or disallow it according to the rules we set up. Having the
>> info available on request would be good for debugging though. That said,
>> we definitely care about stat and exec.
> 
> All of which can be done by sandboxing, including the reporting part "(debug deny)".   It would be trivial to prototype this with sandbox-exec in any case, since all the mechanism is already there and it's easy to sandbox-exec a shell for interactive testing of the sandbox parameters.   You would probably need to do a substitution pass over a boilerplate sandbox profile file to expand things like ${workpath} and ${distpath} for that specific port before doing the sandbox-exec on it, but again, that's trivial to do.
> 
>> For port(1) itself we ideally want the permissions to be dynamically
>> modifiable, i.e. we can get back permissions that we gave up while
>> running a phase in the portfile. But for the child processes it should
>> be locked in for the lifetime of the process. If we have to, I guess we
>> could use helper processes instead of changing the access of port(1),
>> but that's a fair bit of extra work.
> 
> Well, that's the sticky part.  Once a child is in a specific sandbox, it can't break out / modify its own rules since that would obviously defeat the purpose of sandboxing.   If you were willing (perhaps only when using trace mode) to invoke each phase of port as a separate fork/exec then, of course, it would be easy and also let you customize the boilerplate for each phase (only granting access to ${distpath} for fetch, for example).   Alternately, you could stick to the single port(1) process for all phases but be sure to invoke the tools which actually do the work (${fetch.cmd} for example) with the appropriate sandbox profiles, accomplishing much the since it's obviously not port(1) which is doing the filesystem munging, it's shell commands like make(1), curl(1) and so on!

Running a separate process per open portfile is probably OK. Starting up
and initialising a new process and interpreter for each phase, much less so.

Can the rules be modified by a different process? Perhaps in a similar
way to how apps gain access to files chosen with open/save dialogs? If
we can't modify access during different phases, we'd have to just allow
everything that might be needed during any phase. Given that the
activate phase needs to be able to write new files to just about
anywhere, that's problematic.

Sandboxing only the child processes helps, but not as much as we'd
really like, since many ports do a portion of their work with Tcl calls.

> Perhaps most beneficially about the sandboxing approach is the fact that SBCL is so expressive and powerful, being a dialect of scheme which also has the ability to restrict access to any sort of file access, process creation, mach port lookup, you name it and with just a couple of lines of scheme code.  At that point, trace mode doesn't just become a safety belt, it becomes an intrinsic part of macports' security model which allows it to run as root (which it already does) without necessarily letting it, or a malformed / malicious / buggy port from running amuck on a user's system.   I think we've just gone a lot further than tracing!

Yes, getting this working would be a major win.

- Josh


More information about the macports-dev mailing list