Strict access checks for scripts, secrets files and call option files#577
Strict access checks for scripts, secrets files and call option files#577paulusmack wants to merge 3 commits intomasterfrom
Conversation
As a security measure, check that scripts that are run as root by pppd using run_program() could not have been changed by a non-root process. First, the path to the script file is converted to an absolute path without symlinks using realpath(). Then each component of the path from the root directory down, as well as the file itself, are checked to verify that they are owned by root and not writable by group or other. This ensures that some other file couldn't be substituted for the file that has been checked, and the file itself can't be changed. (Clearly a process running as root could modify the path or the file itself, but if that is being done maliciously, then the system must already be compromised.) The process of checking the path and the file is in a new function called ppp_check_access(). Most of the code added here was originally written by Jaco Kroon <jaco@uls.co.za>. I moved the code into a separate function, changed the error messages, changed fstatat(..., AT_SYMLINK_NOFOLLOW) to lstat(), and otherwise tweaked it a little. Originally-by: Jaco Kroon <jaco@uls.co.za> Signed-off-by: Paul Mackerras <paulus@ozlabs.org>
…cesses Check that files containing secrets used by a remote system to authenticate itself to this system could not be changed by a non-root process, that is, that the file and all directories in the real path to it are owned by root and not writable by group or other. The files that are checked in this way are: /etc/ppp/pap-secrets (or alternate location set by pap-secrets option) /etc/ppp/chap-secrets (ditto for chap-secrets option) /etc/ppp/srp-secrets /etc/ppp/eaptls-server /etc/ppp/eaptls-client Files specified using @/file syntax in a secrets file Signed-off-by: Paul Mackerras <paulus@ozlabs.org>
This checks, using ppp_check_access(), that files invoked using the "call" option can't be tampered with by non-root processes. Signed-off-by: Paul Mackerras <paulus@ozlabs.org>
|
@jkroonza: Have you seen this PR? |
jkroonza
left a comment
There was a problem hiding this comment.
One bug, two comments/questions/suggestions for possible improvement. The loop code duplication issue is probably best served by:
while (true) {
/* tests on part */
if (!slash)
break;
/* adjustments for slash */
}
/* last component explicit tests */
Which is a construct I also don't like, but I like the code duplication even less.
| * attack avenues (eg, changing symlinks). | ||
| */ | ||
| rpath = realpath(path, NULL); | ||
| if (rpath = NULL) { |
There was a problem hiding this comment.
Well, this will always be false, rpath == NULL here, or more succinctly, !rpath (which is also my preference to be honest).
| } | ||
|
|
||
| if (!ok) { | ||
| error("Can't safely use %v: see previous warning", path); |
There was a problem hiding this comment.
Doubling up, why not error() above and simply goto here? Or outright just add the goto's above ... probably less confusing in terms of code for someone reading afterwards.
| if (0 != (sbuf.st_mode & (S_IWGRP | S_IWOTH))) { | ||
| error("Can't safely use %v: writable by group or other", rpath); | ||
| goto err; | ||
| } |
There was a problem hiding this comment.
Need to think about if there's a relatively easy way to include these checks into the loop above that's NOT confusing, but eliminates the code duplication.
jkroonza
left a comment
There was a problem hiding this comment.
Just to be clear: In theory this still suffers a TOCTOU issue whereby if somehow the attacker has the ability to change ownership/permisssions between check time and use time ... which would require at least some level of root involvement in which case I believe there's already far larger issues.
This is a reworking and extension of @jkroonza's work in PR #561 to pull out the checking code into a separate function and use it for secrets files and call option files as well as scripts. I moved the checking code into a new function called ppp_check_access() and I used lstat() instead of fstatat(..., AT_SYMLINK_NOFOLLOW), updated the error messages, and reworked the main loop, but apart from that, the body of ppp_check_access() is basically @jkroonza's code.
I would appreciate review and testing - in particular, are there things being checked here that shouldn't be, or things that aren't and should be?