Dropping Privileges
Since bubblewrap is a suid application (on many systems), I had a look at the privilege dropping part.
Taking this as an example https://www.oreilly.com/library/view/secure-programming-cookbook/0596003943/ch01s03.html there seem to be several things missing.
One of the differences in bubblewrap is the handling of ancillary groups
One more issue needs to be addressed with regard to dropping privileges. In addition to the effective, real, and saved group IDs of a process, a process also has ancillary groups. Ancillary groups are inherited by a process from its parent process, and they can only be altered by a process with superuser privileges. Therefore, if a process with superuser privileges is dropping these privileges, it must also be sure to drop any ancillary groups it may have. This is achieved by calling setgroups( ) with a single group, which is the real group ID for the process. Because the setgroups( ) system call is guarded by requiring the effective user ID of the process to be that of the superuser, it must be done prior to dropping root privileges. Ancillary groups should be dropped regardless of whether privileges are being dropped permanently or temporarily. In the case of a temporary privilege drop, the process can restore the ancillary groups if necessary when elevated privileges are restored.
While I did not have the time to look at this in detail, I found several areas where bubblewrap uses setuid to drop root privileges, but does not use setreuid as is recommended.
EDIT: Actually setreuid is not the best solution, rather we should use setresuid: http://man7.org/linux/man-pages/man2/setresuid.2.html
On Linux and Solaris, the setgid( ) and setuid( ) system calls do not alter the process’s saved group and user IDs in all cases. (In particular, if the effective ID is not the superuser, the saved ID is not altered; otherwise, it is.). That means that these calls can’t reliably be used to permanently drop privileges. Instead, setregid( ) and setreuid( ) are used, which actually simplifies the process except that these two system calls have different semantics on the BSD-derived platforms.
More Info: https://wiki.sei.cmu.edu/confluence/display/c/POS37-C.+Ensure+that+privilege+relinquishment+is+successful
https://wiki.sei.cmu.edu/confluence/display/c/POS36-C.+Observe+correct+revocation+order+while+relinquishing+privileges
Under normal circumstances, setuid() and related calls do not alter the supplementary group IDs. However, a setuid-root program can alter its supplementary group IDs and then relinquish root privileges, in which case, it maintains the supplementary group IDs but lacks the privilege necessary to relinquish them. Consequently, it is recommended that a program immediately relinquish supplementary group IDs before relinquishing root privileges.
Maybe someine with more inside into bubblewrap can confirm weather this is an issue or not. In any case when dropping privileges these best practices should be followed to make sure the use of suid is safe indeed.
Some more intersting information can be found in this talk: https://media.ccc.de/v/32c3-7284-check_your_privileges
Linux setuid(2) says setuid(x) is equivalent to setresuid(x, x, x) if you're root, although I agree that setresuid would be more obviously correct:
If the user is root or the program is set-user-ID-root, special care must be taken: setuid() checks the effective user ID of the caller and if it is the superuser, all process-related user ID's are set to uid. After this has occurred, it is impossible for the program to regain root privileges.
—setuid(2)
So I think this is OK.
You wrote:
if the effective ID is not the superuser, the saved ID is not altered
but bubblewrap only calls setuid() if the effective ID is the superuser, so this doesn't affect us. (One call is guarded by euid == 0, and the other is guarded by is_privileged, which is real_uid != euid: but if real_uid != euid && euid != 0, which would indicate an incorrect installation that is setuid to a uid other than root, we exit early with an error.)
However, a setuid-root program can alter its supplementary group IDs and then relinquish root privileges, in which case, it maintains the supplementary group IDs but lacks the privilege necessary to relinquish them.
bubblewrap doesn't do anything special with its supplementary group IDs, so it inherits whatever it was called with, which seems correct? It's only setuid, not setgid, so it gets this privilege transition when invoked (if we assume you are uid 1000, with primary group 100 and supplementary groups 123 and 456):
caller: setuid bubblewrap: after dropping privileges:
euid 1000 -> 0 -> 1000
ruid 1000 -> 1000 -> 1000
suid 1000 -> 0 -> 1000
egid 100 -> 100 -> 100
rgid 100 -> 100 -> 100
sgid 100 -> 100 -> 100
supp.groups 123, 456 -> no change -> no change
Second-guessing what its group IDs should have been, like with getgrouplist(), has a risk of accidentally granting or taking away privileges (because Unix groups are an attribute of a process, and are not necessarily in sync with the system's user/group database).