Skip to content

Commit f930869

Browse files
committed
Stricter checks on scripts.
Perform symbolic link path resolution before performing checks (avoid symlink change attacks). Ensure the path leading to the executable is root:* owned, and not writable by anyone else. Ensure the executable itself is: Owned by root. Executable for root. Not writable by group or other. Doesn't look like we need to check FACLs (the bits for additional users are mixed into group permission bits according to my testing). Signed-off-by: Jaco Kroon <jaco@uls.co.za>
1 parent 6f3bf58 commit f930869

1 file changed

Lines changed: 59 additions & 3 deletions

File tree

pppd/main.c

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1914,10 +1914,52 @@ update_script_environment(void)
19141914
* reap_kids) iff the return value is > 0.
19151915
*/
19161916
pid_t
1917-
run_program(char *prog, char * const *args, int must_exist, void (*done)(void *), void *arg, int wait)
1917+
run_program(char *_prog, char * const *args, int must_exist, void (*done)(void *), void *arg, int wait)
19181918
{
19191919
int pid, status, ret;
19201920
struct stat sbuf;
1921+
char prog[PATH_MAX];
1922+
char *slash;
1923+
1924+
/*
1925+
* Resolve symlinks such that we eliminate a few potential ToCToU
1926+
* attack avenues (eg, changing symlinks).
1927+
*/
1928+
if (!realpath(_prog, prog)) {
1929+
if (must_exist || errno != ENOENT)
1930+
warn("Can't execute %s: %m", prog);
1931+
return 0;
1932+
}
1933+
1934+
/*
1935+
* full check the entire path, must be root: owned, and NOT be writable
1936+
* to group/other (which incorporates FACL bits somehow, so we can ignore
1937+
* explicit FACL checks).
1938+
*/
1939+
do {
1940+
bool ok = false;
1941+
if (fstatat(AT_FDCWD, slash ? prog : "/", &sbuf, AT_SYMLINK_NOFOLLOW) != 0) {
1942+
warn("Script path check failure: %s: %m", slash ? prog : "/");
1943+
} else if (sbuf.st_uid != 0 /* getuid() ? */) {
1944+
warn("Script path %s is not root owned.", slash ? prog : "/");
1945+
} else if (0 != (sbuf.st_mode & (S_IWGRP | S_IWOTH))) {
1946+
warn("Script path %s is group or other writable.", slash ? prog : "/");
1947+
} else {
1948+
ok = true;
1949+
}
1950+
1951+
if (slash)
1952+
*slash = '/';
1953+
1954+
if (!ok) {
1955+
warn("Can't execute %s: error checking path as per above");
1956+
return 0;
1957+
}
1958+
1959+
slash = strchr((slash ? slash : prog) + 1, '/');
1960+
if (slash)
1961+
*slash = 0;
1962+
} while (slash);
19211963

19221964
/*
19231965
* First check if the file exists and is executable.
@@ -1926,13 +1968,27 @@ run_program(char *prog, char * const *args, int must_exist, void (*done)(void *)
19261968
* might be accessible only to root.
19271969
*/
19281970
errno = EINVAL;
1929-
if (stat(prog, &sbuf) < 0 || !S_ISREG(sbuf.st_mode)
1930-
|| (sbuf.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)) == 0) {
1971+
if (stat(AT_FDCWD, prog, &sbuf, AT_SYMLINK_NOFOLLOW) != 0) {
19311972
if (must_exist || errno != ENOENT)
19321973
warn("Can't execute %s: %m", prog);
19331974
return 0;
19341975
}
19351976

1977+
if (sbuf.st_uid != 0 /* getuid() ? */) {
1978+
warn("Can't execute %s: Not root owned.", prog);
1979+
return 0;
1980+
}
1981+
1982+
if (0 != (sbuf.st_mode & (S_IWGRP | S_IWOTH))) {
1983+
warn("Can't execute %s: Writable by group or other.", prog);
1984+
return 0;
1985+
}
1986+
1987+
if (!S_ISREG(sbuf.st_mode) || (sbuf.st_mode & (S_IXUSR)) == 0) {
1988+
warn("Can't execute %s: Not a regular executable (for root) file.");
1989+
return 0;
1990+
}
1991+
19361992
pid = ppp_safe_fork(fd_devnull, fd_devnull, fd_devnull);
19371993
if (pid == -1) {
19381994
error("Failed to create child process for %s: %m", prog);

0 commit comments

Comments
 (0)