Skip to content

Strict access checks for scripts, secrets files and call option files#577

Open
paulusmack wants to merge 3 commits intomasterfrom
strict
Open

Strict access checks for scripts, secrets files and call option files#577
paulusmack wants to merge 3 commits intomasterfrom
strict

Conversation

@paulusmack
Copy link
Collaborator

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?

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>
@Neustradamus
Copy link
Member

Neustradamus commented Mar 13, 2026

@jkroonza: Have you seen this PR?

Copy link
Contributor

@jkroonza jkroonza left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor

@jkroonza jkroonza left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants