RE: forkat(int pidfd), execveat(int pidfd), other awful things?
From: Andy Lutomirski > Sent: 01 February 2021 18:30 ... > 2. A sane process creation API. It would be delightful to be able to > create a fully-specified process without forking. This might end up > being a fairly complicated project, though -- there are a lot of > inherited process properties to be enumerated. Since you are going to (eventually) load in a program image have to do several system calls to create the process isn't likely to be a problem. So using separate calls for each property isn't really an issue and solves the horrid problem of the API structure. So you could create an embryonic process that inherits a lot of stuff from the current process, the do actions that sort out the fds, argv, namespace etc. Finally running the new program. It would probably make implement posix_spawn() easier. David - Registered Address Lakeside, Bramley Road, Mount Farm, Milton Keynes, MK1 1PT, UK Registration No: 1397386 (Wales)
Re: forkat(int pidfd), execveat(int pidfd), other awful things?
On 2/1/2021 9:47 AM, Jason A. Donenfeld wrote: > Hi Andy & others, > > I was reversing some NT stuff recently and marveling over how wild and > crazy things are over in Windows-land. A few things related to process > creation caught my interest: > > - It's possible to create a new process with an *arbitrary parent > process*, which means it'll then inherit various things like handles > and security attributes and tokens from that new parent process. > > - It's possible to create a new process with the memory space handle > of a different process. Consider this on Linux, and you have some > abomination like `forkat(int pidfd)`. > > The big question is "why!?" At first I was just amused by its presence > in NT. Everything is an object and you can usually freely mix and > match things, and it's very flexible, which is cool. But this is NT, > not Linux. > > Jann and I were discussing, though, that maybe some variant of these > features might be useful to get rid of setuid executables. Imagine > something like `systemd-sudod`, forked off of PID 1 very early. > Subsequently all new processes on the system run with > PR_SET_NO_NEW_PRIVS or similar policies to prevent non-root->root > transition. Then, if you want to transition, you ask systemd-sudod (or > polkitd, or whatever else you have in mind) to make you a new process, > and it then does the various policy checks, and executes a new process > for you as the parent of the requesting process. > > So how would that work? Well, executing processes with arbitrary > parents would be part of it, as above. But we'd probably want to more > carefully control that new process. Which chroot is it in? How do > cgroups work? And so on. And ultimately this design leads to something > like ZwCreateProcess, where you have several arguments, each to a > handle to some part of the new process state, or null to be inherited > from its parent. > > int execve_parent(int parent_pidfd, int root_dirfd, int cgroup_fd, int > namespace_fd, const char *pathname, char *const argv[], char *const > envp[]); > > One could imagine this growing pretty unwieldy. There's also this > other design aspect of Linux that's worth considering. Namespaces and > other process-inherited resources are generally hierarchical, with > children getting the resource from their parent. This makes sense and > is simple to conceptualize. Everytime we add a new thing_fd as a > pointer to one of these resources, and allow it to be used outside of > that hierarchy, it introduces a kind of "escape hatch". That might be > considered "bad design" by some; it might not be by others. Seen this > way, NT is one massive escape hatch, with pretty much everything being > an object with a handle. > > But! Maybe this is nonetheless an interesting design avenue to > explore. The introduction of pidfd is sort of just the "beginning" of > that kind of design. > > Is any of this interesting to you as a future of privilege escalation > and management on Linux? TL;DR - We have plenty of flayed cats. My brief analysis of your proposal doesn't lead me to think that there's anything you couldn't already do with systemd and an application launcher. We already have a bunch of security mechanisms and behaviors that the masses have decided are too complicated or dangerous to use. And some that *are* too complicated or dangerous to use. I wouldn't see these mechanisms as "hardening" the kernel. I would see them as complicating what passes for the Linux security policy. > > Jason
Re: forkat(int pidfd), execveat(int pidfd), other awful things?
On Mon, Feb 1, 2021 at 9:47 AM Jason A. Donenfeld wrote: > > Hi Andy & others, > > I was reversing some NT stuff recently and marveling over how wild and > crazy things are over in Windows-land. A few things related to process > creation caught my interest: > > - It's possible to create a new process with an *arbitrary parent > process*, which means it'll then inherit various things like handles > and security attributes and tokens from that new parent process. > > - It's possible to create a new process with the memory space handle > of a different process. Consider this on Linux, and you have some > abomination like `forkat(int pidfd)`. My general thought is that this is an excellent idea, but maybe not quite in this form. I do rather like a lot about the NT design, although I have to say that their actual taste in the structures passed into APIs is baroque at best. If we're going to do this, though, can we stay away from fork and and exec entirely? Fork is cute but inefficient, and exec is the source of neverending complexity and bugs in the kernel. But I also think that whole project can be decoupled into two almost-orthogonal pieces: 1. Inserting new processes into unusual places in the process tree. The only part of setuid that really needs kernel help to replace is for the daemon to be able to make its newly-spawned child be a child of the process that called out to the daemon. Christian's pidfd proposal could help here, and there could be a new API that is only a minor tweak to existing fork/exec to fork-and-reparent. 2. A sane process creation API. It would be delightful to be able to create a fully-specified process without forking. This might end up being a fairly complicated project, though -- there are a lot of inherited process properties to be enumerated. (Bonus #3): binfmts are a pretty big attack surface. Having a way to handle all the binfmt magic in userspace might be a nice extension to #2. --Andy
Re: forkat(int pidfd), execveat(int pidfd), other awful things?
On Mon, Feb 01, 2021 at 06:47:17PM +0100, Jason A. Donenfeld wrote: > Hi Andy & others, > > I was reversing some NT stuff recently and marveling over how wild and > crazy things are over in Windows-land. A few things related to process > creation caught my interest: > > - It's possible to create a new process with an *arbitrary parent > process*, which means it'll then inherit various things like handles > and security attributes and tokens from that new parent process. > > - It's possible to create a new process with the memory space handle > of a different process. Consider this on Linux, and you have some > abomination like `forkat(int pidfd)`. > > The big question is "why!?" At first I was just amused by its presence > in NT. Everything is an object and you can usually freely mix and > match things, and it's very flexible, which is cool. But this is NT, > not Linux. > > Jann and I were discussing, though, that maybe some variant of these > features might be useful to get rid of setuid executables. Imagine > something like `systemd-sudod`, forked off of PID 1 very early. > Subsequently all new processes on the system run with > PR_SET_NO_NEW_PRIVS or similar policies to prevent non-root->root > transition. Then, if you want to transition, you ask systemd-sudod (or > polkitd, or whatever else you have in mind) to make you a new process, > and it then does the various policy checks, and executes a new process > for you as the parent of the requesting process. > > So how would that work? Well, executing processes with arbitrary > parents would be part of it, as above. But we'd probably want to more > carefully control that new process. Which chroot is it in? How do > cgroups work? And so on. And ultimately this design leads to something > like ZwCreateProcess, where you have several arguments, each to a > handle to some part of the new process state, or null to be inherited > from its parent. > > int execve_parent(int parent_pidfd, int root_dirfd, int cgroup_fd, int > namespace_fd, const char *pathname, char *const argv[], char *const > envp[]); > > One could imagine this growing pretty unwieldy. There's also this > other design aspect of Linux that's worth considering. Namespaces and > other process-inherited resources are generally hierarchical, with > children getting the resource from their parent. This makes sense and > is simple to conceptualize. Everytime we add a new thing_fd as a > pointer to one of these resources, and allow it to be used outside of > that hierarchy, it introduces a kind of "escape hatch". That might be > considered "bad design" by some; it might not be by others. Seen this > way, NT is one massive escape hatch, with pretty much everything being > an object with a handle. > > But! Maybe this is nonetheless an interesting design avenue to > explore. The introduction of pidfd is sort of just the "beginning" of > that kind of design. > > Is any of this interesting to you as a future of privilege escalation > and management on Linux? A bunch of this was discussed in a breakout room during Linux Plumbers last year and I also had discussions with Lennart about this a little while ago. One API I had proposed was to extend pidfd_open() to give you a pidfd that does not yet refer to any process, i.e. instead of int pidfd = pidfd_open(1234, 0); you could do int pidfd = pidfd_open(-1/-ESRCH, 0); which would give you an empty process handle without any mentionable properties. A simple/dumb design would then be to let clone3() not just return pidfds but also take pidfds as an argument. You could then hand-off the pidfd to another process SCM_RIGHTS/pidfd_getfd() and have it create a process for you with the privileges of the caller, you'd still be the parent. Or in addition to pidfd_open() we add new syscalls to configure a process context pidfd_configure() or sm. This design I initially proposed before we ended up with what we have now. So yes, I would love to have at least the concept to create a process for another process, delegated fork, essentially. Christian
Re: forkat(int pidfd), execveat(int pidfd), other awful things?
> int execve_parent(int parent_pidfd, int root_dirfd, int cgroup_fd, int > namespace_fd, const char *pathname, char *const argv[], char *const > envp[]); A variant on the same scheme would be: int execve_remote(int pidfd, int root_dirfd, int cgroup_fd, int namespace_fd, const char *pathname, char *const argv[], char *const envp[]); Unpriv'd process calls fork(), and from that fork sends its pidfd through a unix socket to systemd-sudod, which then calls execve_remote on that pidfd. There are a lot of (potentially very bad) ways to skin this cat.
forkat(int pidfd), execveat(int pidfd), other awful things?
Hi Andy & others, I was reversing some NT stuff recently and marveling over how wild and crazy things are over in Windows-land. A few things related to process creation caught my interest: - It's possible to create a new process with an *arbitrary parent process*, which means it'll then inherit various things like handles and security attributes and tokens from that new parent process. - It's possible to create a new process with the memory space handle of a different process. Consider this on Linux, and you have some abomination like `forkat(int pidfd)`. The big question is "why!?" At first I was just amused by its presence in NT. Everything is an object and you can usually freely mix and match things, and it's very flexible, which is cool. But this is NT, not Linux. Jann and I were discussing, though, that maybe some variant of these features might be useful to get rid of setuid executables. Imagine something like `systemd-sudod`, forked off of PID 1 very early. Subsequently all new processes on the system run with PR_SET_NO_NEW_PRIVS or similar policies to prevent non-root->root transition. Then, if you want to transition, you ask systemd-sudod (or polkitd, or whatever else you have in mind) to make you a new process, and it then does the various policy checks, and executes a new process for you as the parent of the requesting process. So how would that work? Well, executing processes with arbitrary parents would be part of it, as above. But we'd probably want to more carefully control that new process. Which chroot is it in? How do cgroups work? And so on. And ultimately this design leads to something like ZwCreateProcess, where you have several arguments, each to a handle to some part of the new process state, or null to be inherited from its parent. int execve_parent(int parent_pidfd, int root_dirfd, int cgroup_fd, int namespace_fd, const char *pathname, char *const argv[], char *const envp[]); One could imagine this growing pretty unwieldy. There's also this other design aspect of Linux that's worth considering. Namespaces and other process-inherited resources are generally hierarchical, with children getting the resource from their parent. This makes sense and is simple to conceptualize. Everytime we add a new thing_fd as a pointer to one of these resources, and allow it to be used outside of that hierarchy, it introduces a kind of "escape hatch". That might be considered "bad design" by some; it might not be by others. Seen this way, NT is one massive escape hatch, with pretty much everything being an object with a handle. But! Maybe this is nonetheless an interesting design avenue to explore. The introduction of pidfd is sort of just the "beginning" of that kind of design. Is any of this interesting to you as a future of privilege escalation and management on Linux? Jason