There is one thing that, by maintaining PAM, I absolutely dread. No, it’s not cross-compilation but rather the handling of pam_limits for what concerns services, and, in particular,
I have said before that if you don’t properly run
start-stop-daemon to start your services, pam_limits is not executed and thus you won’t have limits supported by your startup. That is, indeed, the case. Unfortunately, the situation is not as black and white as I hoped, or most people expected.
Christian reported to me the other day he was having trouble getting user-based limits properly respected by services; I also had similar situations before, but I never went as far as checking them out properly. So I went to check it out with his particular use case: dovecot.
Dovecot processes have their user and group set to specific limited users; on the other hand, they have to be started as root to begin with; not only the runtime directories are not writeable but by root, but also it fails to bind the standard ports for IMAP as user (since they are lower than 1024); and it fails further on when starting user processes, most likely because they are partly run with the privileges of the user logging in.
So with the following configuration in
/etc/security/limits.conf, what happens?
* hard nproc 50 root hard nproc unlimited root soft nproc unlimited dovecot hard nproc 300 dovecot soft nproc 100
The first problem is that, as I said, dovecot is started from root, not the dovecot user; and when the privileges are actually dropped, it happens directly within dovecot, and does not pass through pam_limits! So the obvious answer is, the processes are started with the limits of root, which, with the previous configuration, are unlimited. Unfortunately, as Christian reported, that was not the case; the nproc limit was set to 50 (and that was low enough that gradm killed it.
The first guess was that the user’s session limits are imposed after starting the service; but this is exactly what we’re trying to avoid by using
start-stop-daemon. So, we’ve got a problem, at first glance. A quick check on OpenRC’s
start-stop-daemon code shows what the problem is:
if (changeuser != NULL) pamr = pam_start("start-stop-daemon", changeuser, &conv, &pamh); else pamr = pam_start("start-stop-daemon", "nobody", &conv, &pamh);
So here’s the problem; unless we’re using the
start-stop-daemon is applying limits for user nobody not root. Right now we’ve got a problem; this means that we cannot easily configure per-user limits for the users used by services to drop their privileges, and that is a bad thing. How can we solve this problem?
The first obvious solution is adding something like
--user foo --nochuid that would make
start-stop-daemon abide to the limits of the user provided, but no call to
setuid() is performed, leaving that to the software itself to take care of that. This is fast but partly hacky. The second option is not exclusive and actually should probably be implemented anyway: set up the proper file-based capabilities on the software, then run it as the user directly in
s-s-d. With maybe a bit of help from
pam_cap to set it per-user rather than per-file.
At any rate, this is one thing that we should be looking into. Sigh!
To finish it off, there is one nasty situation that I haven’t checked yet and actually worries me: if you set the “standard user” limits lower than nobody (since that is what the services would get), they can probably workaround the limits by using
start-stop-daemon to start their own code; I’m not sure if it works, but if it does, we’ve got a security issue on at least openrc at our hands!