Re: [PATCH] security/commoncap: don't assume "setid" if all ids are identical
From: Max Kellermann <hidden>
Date: 2025-05-06 14:51:52
Also in:
lkml
On Tue, May 6, 2025 at 3:22 PM Serge E. Hallyn [off-list ref] wrote:
Just to quibble here: I don't use NO_NEW_PRIVS, but it seems to me quite likely that your claim is wrong here. The whole SECBIT_KEEP_CAPS etc dance is based on the idea that you understand that once you exec, you lose some of your existing privilege. Similarly, it seems quite likely to me that people using NO_NEW_PRIVS understand, expect, and count on the fact that their effective ids will be cleared on exec.
One could define NO_NEW_PRIVS that way, but that's not how it is documented. Of course, we can't rule out that somewhere, somebody exists who relies on the current behavior, and that we must preserve it for ABI stability (I think this was your point). If you desire ABI stability, then this behavior should really be documented. To me, the current implementation looks weird and buggy (but that's just my opinion). The code figures that that it's a set-id exec when effective!=real, which is indeed how set-id execution looks like, but still that check is slightly off: 1. it's really only set-id when new!=old; checking real!=effective is conceptually the wrong angle 2. there may be other reasons why real!=effective My patch is an attempt to fix this in an unintrusive way, by not rewriting it but adding another check to rule out some special case. If I were to rewrite this from scratch, I'd do it differently (only compare new!=old), but I don't want to mess too much with security code that I'm not very familiar with. I believe the guy who initially wrote it made wrong assumptions, but maybe I'm just wrong, I'm not the expert here.
Note also that so far I'm only asking about the intent of the patch.
In a shared webhosting environment, we want to run an Apache (or nginx) in each website's container. If the website owner does "chmod 600", the Apache should not be able to read the file; but PHP processes spawned by the Apache should have full access. Therefore, we run Apache with a different fsuid; when Apache executes PHP, the fsuid is reverted. But how to spawn Apache with a different fsuid? Not possible directly (fsuid is always reverted on exec), but by giving it a different euid (and ruid = website uid), granting it access to that secondary uid. After exec, Apache swaps uids, sets effective=real=apache_uid, and fsuid=website_uid. That works fine, until we enable NO_NEW_PRIVS - which is surprising, because we indeed don't want any new privs - just keep the existing ones. The documentation doesn't explain this behavior, and we don't want to omit NO_NEW_PRIVS as a workaround.
Apart from that, I do think the implementation is wrong, because you are impacting non-NO_NEW_PRIVS behavior as well, such as calculation of cap_permitted and the clearing of ambient capabilities.
You are right, it affects all three code blocks that are checking "is_setid", but why do you believe it's wrong? I can move the new check to the bottom, covering only the "secureexec=1" line, if that worries you. What sure is flawed is that my patch description fails to mention the other two changes. Sorry for that, I'll amend the description (if/when we agree that my patch is ok). Though I do believe that all 3 changes are correct. Why would you want to clear ambient capabilities just because real!=effective? The manpage says: "Executing a program that changes UID or GID due to the set-user-ID or set-group-ID bits or executing a program that has any file capabilities set will clear the ambient set." Documentation and code disagree! Currently, the kernel does not check for "changes UID/GID", but whether effective!=real. These two are orthogonal, the kernel is buggy, and my patch makes it a little bit more correct (but does not remove the wrong real!=effective check, see above).
And, I'm not sure the has_identical_uids_gids() is quite right, as I'm not sure what the bprm->cred->fsuid and suid make sense, though the process's fsuid and suid of course need to be checked.
Sorry, I don't get that. What do you mean?