| From b6fef599bf8493480664b766040fa9b0d4b1e335 Mon Sep 17 00:00:00 2001 |
| From: William Hubbs <w.d.hubbs@gmail.com> |
| Date: Fri, 20 Nov 2020 09:15:59 -0600 |
| Subject: [PATCH] checkpath: fix CVE-2018-21269 |
| |
| This walks the directory path to the file we are going to manipulate to make |
| sure that when we create the file and change the ownership and permissions |
| we are working on the same file. |
| Also, all non-terminal symbolic links must be owned by root. This will |
| keep a non-root user from making a symbolic link as described in the |
| bug. If root creates the symbolic link, it is assumed to be trusted. |
| |
| On non-linux platforms, we no longer follow non-terminal symbolic links |
| by default. If you need to do that, add the -s option on the checkpath |
| command line, but keep in mind that this is not secure. |
| |
| This fixes #201. |
| |
| [Patch taken from upstream: |
| https://github.com/OpenRC/openrc/commit/b6fef599bf8493480664b766040fa9b0d4b1e335] |
| Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com> |
| --- |
| man/openrc-run.8 | 6 +++ |
| src/rc/checkpath.c | 103 ++++++++++++++++++++++++++++++++++++++++++--- |
| 2 files changed, 102 insertions(+), 7 deletions(-) |
| |
| diff --git a/man/openrc-run.8 b/man/openrc-run.8 |
| index 1102daaa..ec4b88de 100644 |
| --- a/man/openrc-run.8 |
| +++ b/man/openrc-run.8 |
| @@ -461,6 +461,7 @@ Mark the service as inactive. |
| .Op Fl p , -pipe |
| .Op Fl m , -mode Ar mode |
| .Op Fl o , -owner Ar owner |
| +.Op Fl s , -symlinks |
| .Op Fl W , -writable |
| .Op Fl q , -quiet |
| .Ar path ... |
| @@ -481,6 +482,11 @@ or with names, and are separated by a colon. |
| The truncate options (-D and -F) cause the directory or file to be |
| cleared of all contents. |
| .Pp |
| +If -s is not specified on a non-linux platform, checkpath will refuse to |
| +allow non-terminal symbolic links to exist in the path. This is for |
| +security reasons so that a non-root user can't create a symbolic link to |
| +a root-owned file and take ownership of that file. |
| +.Pp |
| If -W is specified, checkpath checks to see if the first path given on |
| the command line is writable. This is different from how the test |
| command in the shell works, because it also checks to make sure the file |
| diff --git a/src/rc/checkpath.c b/src/rc/checkpath.c |
| index 448c9cf8..ff54a892 100644 |
| --- a/src/rc/checkpath.c |
| +++ b/src/rc/checkpath.c |
| @@ -16,6 +16,7 @@ |
| * except according to the terms contained in the LICENSE file. |
| */ |
| |
| +#define _GNU_SOURCE |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| |
| @@ -23,6 +24,7 @@ |
| #include <fcntl.h> |
| #include <getopt.h> |
| #include <grp.h> |
| +#include <libgen.h> |
| #include <pwd.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| @@ -44,7 +46,7 @@ typedef enum { |
| |
| const char *applet = NULL; |
| const char *extraopts ="path1 [path2] [...]"; |
| -const char *getoptstring = "dDfFpm:o:W" getoptstring_COMMON; |
| +const char *getoptstring = "dDfFpm:o:sW" getoptstring_COMMON; |
| const struct option longopts[] = { |
| { "directory", 0, NULL, 'd'}, |
| { "directory-truncate", 0, NULL, 'D'}, |
| @@ -53,6 +55,7 @@ const struct option longopts[] = { |
| { "pipe", 0, NULL, 'p'}, |
| { "mode", 1, NULL, 'm'}, |
| { "owner", 1, NULL, 'o'}, |
| + { "symlinks", 0, NULL, 's'}, |
| { "writable", 0, NULL, 'W'}, |
| longopts_COMMON |
| }; |
| @@ -64,15 +67,92 @@ const char * const longopts_help[] = { |
| "Create a named pipe (FIFO) if not exists", |
| "Mode to check", |
| "Owner to check (user:group)", |
| + "follow symbolic links (irrelivent on linux)", |
| "Check whether the path is writable or not", |
| longopts_help_COMMON |
| }; |
| const char *usagestring = NULL; |
| |
| +static int get_dirfd(char *path, bool symlinks) { |
| + char *ch; |
| + char *item; |
| + char *linkpath = NULL; |
| + char *path_dupe; |
| + char *str; |
| + int components = 0; |
| + int dirfd; |
| + int flags = 0; |
| + int new_dirfd; |
| + struct stat st; |
| + ssize_t linksize; |
| + |
| + if (!path || *path != '/') |
| + eerrorx("%s: empty or relative path", applet); |
| + dirfd = openat(dirfd, "/", O_RDONLY); |
| + if (dirfd == -1) |
| + eerrorx("%s: unable to open the root directory: %s", |
| + applet, strerror(errno)); |
| + path_dupe = xstrdup(path); |
| + ch = path_dupe; |
| + while (*ch) { |
| + if (*ch == '/') |
| + components++; |
| + ch++; |
| + } |
| + item = strtok(path_dupe, "/"); |
| +#ifdef O_PATH |
| + flags |= O_PATH; |
| +#endif |
| + if (!symlinks) |
| + flags |= O_NOFOLLOW; |
| + flags |= O_RDONLY; |
| + while (dirfd > 0 && item && components > 1) { |
| + str = xstrdup(linkpath ? linkpath : item); |
| + new_dirfd = openat(dirfd, str, flags); |
| + if (new_dirfd == -1) |
| + eerrorx("%s: %s: could not open %s: %s", applet, path, str, |
| + strerror(errno)); |
| + if (fstat(new_dirfd, &st) == -1) |
| + eerrorx("%s: %s: unable to stat %s: %s", applet, path, item, |
| + strerror(errno)); |
| + if (S_ISLNK(st.st_mode) ) { |
| + if (st.st_uid != 0) |
| + eerrorx("%s: %s: synbolic link %s not owned by root", |
| + applet, path, str); |
| + linksize = st.st_size+1; |
| + if (linkpath) |
| + free(linkpath); |
| + linkpath = xmalloc(linksize); |
| + memset(linkpath, 0, linksize); |
| + if (readlinkat(new_dirfd, "", linkpath, linksize) != st.st_size) |
| + eerrorx("%s: symbolic link destination changed", applet); |
| + /* |
| + * now follow the symlink. |
| + */ |
| + close(new_dirfd); |
| + } else { |
| + close(dirfd); |
| + dirfd = new_dirfd; |
| + free(linkpath); |
| + linkpath = NULL; |
| + item = strtok(NULL, "/"); |
| + components--; |
| + } |
| + } |
| + free(path_dupe); |
| + if (linkpath) { |
| + free(linkpath); |
| + linkpath = NULL; |
| + } |
| + return dirfd; |
| +} |
| + |
| static int do_check(char *path, uid_t uid, gid_t gid, mode_t mode, |
| - inode_t type, bool trunc, bool chowner, bool selinux_on) |
| + inode_t type, bool trunc, bool chowner, bool symlinks, bool selinux_on) |
| { |
| struct stat st; |
| + char *name = NULL; |
| + int dirfd; |
| int fd; |
| int flags; |
| int r; |
| @@ -93,14 +173,16 @@ static int do_check(char *path, uid_t uid, gid_t gid, mode_t mode, |
| #endif |
| if (trunc) |
| flags |= O_TRUNC; |
| - readfd = open(path, readflags); |
| + xasprintf(&name, "%s", basename_c(path)); |
| + dirfd = get_dirfd(path, symlinks); |
| + readfd = openat(dirfd, name, readflags); |
| if (readfd == -1 || (type == inode_file && trunc)) { |
| if (type == inode_file) { |
| einfo("%s: creating file", path); |
| if (!mode) /* 664 */ |
| mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH; |
| u = umask(0); |
| - fd = open(path, flags, mode); |
| + fd = openat(dirfd, name, flags, mode); |
| umask(u); |
| if (fd == -1) { |
| eerror("%s: open: %s", applet, strerror(errno)); |
| @@ -122,7 +204,7 @@ static int do_check(char *path, uid_t uid, gid_t gid, mode_t mode, |
| strerror (errno)); |
| return -1; |
| } |
| - readfd = open(path, readflags); |
| + readfd = openat(dirfd, name, readflags); |
| if (readfd == -1) { |
| eerror("%s: unable to open directory: %s", applet, |
| strerror(errno)); |
| @@ -140,7 +222,7 @@ static int do_check(char *path, uid_t uid, gid_t gid, mode_t mode, |
| strerror (errno)); |
| return -1; |
| } |
| - readfd = open(path, readflags); |
| + readfd = openat(dirfd, name, readflags); |
| if (readfd == -1) { |
| eerror("%s: unable to open fifo: %s", applet, |
| strerror(errno)); |
| @@ -259,6 +341,7 @@ int main(int argc, char **argv) |
| int retval = EXIT_SUCCESS; |
| bool trunc = false; |
| bool chowner = false; |
| + bool symlinks = false; |
| bool writable = false; |
| bool selinux_on = false; |
| |
| @@ -293,6 +376,11 @@ int main(int argc, char **argv) |
| eerrorx("%s: owner `%s' not found", |
| applet, optarg); |
| break; |
| + case 's': |
| +#ifndef O_PATH |
| + symlinks = true; |
| +#endif |
| + break; |
| case 'W': |
| writable = true; |
| break; |
| @@ -320,7 +408,8 @@ int main(int argc, char **argv) |
| while (optind < argc) { |
| if (writable) |
| exit(!is_writable(argv[optind])); |
| - if (do_check(argv[optind], uid, gid, mode, type, trunc, chowner, selinux_on)) |
| + if (do_check(argv[optind], uid, gid, mode, type, trunc, chowner, |
| + symlinks, selinux_on)) |
| retval = EXIT_FAILURE; |
| optind++; |
| } |
| -- |
| 2.20.1 |
| |