Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
openSUSE:Step:15-SP4
permissions.13180
0007-chkstat-fix-privesc-CVE-2019-3690.patch
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File 0007-chkstat-fix-privesc-CVE-2019-3690.patch of Package permissions.13180
Index: chkstat.c =================================================================== --- chkstat.c.orig +++ chkstat.c @@ -18,6 +18,7 @@ **************************************************************** */ +#define _GNU_SOURCE #include <stdio.h> #include <pwd.h> #include <grp.h> @@ -29,11 +30,11 @@ #include <errno.h> #include <dirent.h> #include <sys/capability.h> -#define __USE_GNU #include <fcntl.h> +#include <stdbool.h> #define BAD_LINE() \ - fprintf(stderr, "bad permissions line %s:%d\n", permfiles[i], lcnt); + fprintf(stderr, "bad permissions line %s:%d\n", permfiles[i], lcnt) struct perm { struct perm *next; @@ -46,17 +47,17 @@ struct perm { struct perm *permlist; char **checklist; -int nchecklist; +size_t nchecklist; uid_t euid; char *root; -int rootl; -int nlevel; +size_t rootl; +size_t nlevel; char** level; int do_set = -1; int default_set = 1; int have_fscaps = -1; char** permfiles = NULL; -int npermfiles = 0; +size_t npermfiles = 0; char* force_level; struct perm* @@ -71,12 +72,12 @@ add_permlist(char *file, char *owner, ch char *nfile; nfile = malloc(strlen(file) + rootl + (*file != '/' ? 2 : 1)); if (nfile) - { - strcpy(nfile, root); - if (*file != '/') - strcat(nfile, "/"); - strcat(nfile, file); - } + { + strcpy(nfile, root); + if (*file != '/') + strcat(nfile, "/"); + strcat(nfile, file); + } file = nfile; } else @@ -116,7 +117,7 @@ add_permlist(char *file, char *owner, ch int in_checklist(char *e) { - int i; + size_t i; for (i = 0; i < nchecklist; i++) if (!strcmp(e, checklist[i])) return 1; @@ -137,23 +138,23 @@ add_checklist(char *e) if ((nchecklist & 63) == 0) { if (checklist == 0) - checklist = malloc(sizeof(char *) * (nchecklist + 64)); + checklist = malloc(sizeof(char *) * (nchecklist + 64)); else - checklist = realloc(checklist, sizeof(char *) * (nchecklist + 64)); + checklist = realloc(checklist, sizeof(char *) * (nchecklist + 64)); if (checklist == 0) - { - perror("checklist alloc"); - exit(1); - } + { + perror("checklist alloc"); + exit(1); + } } checklist[nchecklist++] = e; } int -readline(FILE *fp, char *buf, int len) +readline(FILE *fp, char *buf, size_t len) { - int l; - if (!fgets(buf, len, fp)) + size_t l; + if (!fgets(buf, (int)len, fp)) return 0; l = strlen(buf); if (l && buf[l - 1] == '\n') @@ -164,7 +165,8 @@ readline(FILE *fp, char *buf, int len) if (l + 1 < len) return 1; fprintf(stderr, "warning: buffer overrun in line starting with '%s'\n", buf); - while ((l = getc(fp)) != EOF && l != '\n') + char c; + while ((c = getc(fp)) != EOF && c != '\n') ; buf[0] = 0; return 1; @@ -173,7 +175,7 @@ readline(FILE *fp, char *buf, int len) int in_level(char *e) { - int i; + size_t i; for (i = 0; i < nlevel; i++) if (!strcmp(e, level[i])) return 1; @@ -181,19 +183,19 @@ in_level(char *e) } void -ensure_array(void** array, int* size) +ensure_array(void** array, size_t* size) { if ((*size & 63) == 0) { if (*array == NULL) - *array = malloc(sizeof(char *) * (*size + 64)); + *array = malloc(sizeof(char *) * (*size + 64)); else - *array = realloc(*array, sizeof(char *) * (*size + 64)); + *array = realloc(*array, sizeof(char *) * (*size + 64)); if (*array == NULL) - { - perror("array alloc"); - exit(1); - } + { + perror("array alloc"); + exit(1); + } } } @@ -221,7 +223,7 @@ int parse_sysconf(const char* file) { FILE* fp; - char line[1024]; + char line[PATH_MAX]; char* p; if ((fp = fopen(file, "r")) == 0) { @@ -231,77 +233,77 @@ parse_sysconf(const char* file) while (readline(fp, line, sizeof(line))) { if (!*line) - continue; + continue; for (p = line; *p == ' '; ++p); if (!*p || *p == '#') - continue; + continue; if (!strncmp(p, "PERMISSION_SECURITY=", 20)) - { - if (force_level) - continue; - - p+=20; - if (isquote(*p)) - ++p; - p = strtok(p, " "); - if (p && !isquote(*p)) - { - do - { - if (isquote(p[strlen(p)-1])) - { - p[strlen(p)-1] = '\0'; - } - if (*p && strcmp(p, "local")) - add_level(p); - } - while ((p = strtok(NULL, " "))); - } - } + { + if (force_level) + continue; + + p+=20; + if (isquote(*p)) + ++p; + p = strtok(p, " "); + if (p && !isquote(*p)) + { + do + { + if (isquote(p[strlen(p)-1])) + { + p[strlen(p)-1] = '\0'; + } + if (*p && strcmp(p, "local")) + add_level(p); + } + while ((p = strtok(NULL, " "))); + } + } else if (!strncmp(p, "CHECK_PERMISSIONS=", 18)) - { - p+=18; - if (isquote(*p)) - ++p; - if (!strncmp(p, "set", 3)) - { - p+=3; - if (isquote(*p) || !*p) - default_set=1; - } - else if ((!strncmp(p, "no", 2) && (!p[3] || isquote(p[3]))) || !*p || isquote(*p)) - { - p+=2; - if (isquote(*p) || !*p) - { - default_set = -1; - } - } - else - { - //fprintf(stderr, "invalid value for CHECK_PERMISSIONS (must be 'set', 'warn' or 'no')\n"); - } - } + { + p+=18; + if (isquote(*p)) + ++p; + if (!strncmp(p, "set", 3)) + { + p+=3; + if (isquote(*p) || !*p) + default_set=1; + } + else if ((!strncmp(p, "no", 2) && (!p[3] || isquote(p[3]))) || !*p || isquote(*p)) + { + p+=2; + if (isquote(*p) || !*p) + { + default_set = -1; + } + } + else + { + //fprintf(stderr, "invalid value for CHECK_PERMISSIONS (must be 'set', 'warn' or 'no')\n"); + } + } #define FSCAPSENABLE "PERMISSION_FSCAPS=" else if (have_fscaps == -1 && !strncmp(p, FSCAPSENABLE, strlen(FSCAPSENABLE))) - { - p+=strlen(FSCAPSENABLE); - if (isquote(*p)) - ++p; - if (!strncmp(p, "yes", 3)) - { - p+=3; - if (isquote(*p) || !*p) - have_fscaps=1; - } - else if (!strncmp(p, "no", 2)) - { - p+=2; - if (isquote(*p) || !*p) - have_fscaps=0; - } else - have_fscaps=1; /* default */ - } + { + p+=strlen(FSCAPSENABLE); + if (isquote(*p)) + ++p; + if (!strncmp(p, "yes", 3)) + { + p+=3; + if (isquote(*p) || !*p) + have_fscaps=1; + } + else if (!strncmp(p, "no", 2)) + { + p+=2; + if (isquote(*p) || !*p) + have_fscaps=0; + } else + have_fscaps=1; /* default */ + } } fclose(fp); return 0; @@ -316,7 +318,7 @@ compare(const void* a, const void* b) static void collect_permfiles() { - int i; + size_t i; DIR* dir; ensure_array((void**)&permfiles, &npermfiles); @@ -327,76 +329,81 @@ collect_permfiles() for (i = 0; i < nlevel; ++i) { if (!strcmp(level[i], "easy") - || !strcmp(level[i], "secure") - || !strcmp(level[i], "paranoid")) - { - char fn[4096]; - snprintf(fn, sizeof(fn), "/etc/permissions.%s", level[i]); - if (access(fn, R_OK) == 0) - { - ensure_array((void**)&permfiles, &npermfiles); - permfiles[npermfiles++] = strdup(fn); - } - } + || !strcmp(level[i], "secure") + || !strcmp(level[i], "paranoid")) + { + char fn[4096]; + snprintf(fn, sizeof(fn), "/etc/permissions.%s", level[i]); + if (access(fn, R_OK) == 0) + { + ensure_array((void**)&permfiles, &npermfiles); + permfiles[npermfiles++] = strdup(fn); + } + } } // 3. package specific permissions dir = opendir("/etc/permissions.d"); if (dir) { char** files = NULL; - int nfiles = 0; + size_t nfiles = 0; struct dirent* d; while ((d = readdir(dir))) - { - char* p; - if (!strcmp("..", d->d_name) || !strcmp(".", d->d_name)) - continue; - - /* filter out backup files */ - if ((strlen(d->d_name)>2) && (d->d_name[strlen(d->d_name)-1] == '~')) - continue; - if (strstr(d->d_name,".rpmnew") || strstr(d->d_name,".rpmsave")) - continue; - - ensure_array((void**)&files, &nfiles); - if ((p = strchr(d->d_name, '.'))) - { - *p = '\0'; - } - files[nfiles++] = strdup(d->d_name); - } + { + char* p; + if (!strcmp("..", d->d_name) || !strcmp(".", d->d_name)) + continue; + + /* filter out backup files */ + if ((strlen(d->d_name)>2) && (d->d_name[strlen(d->d_name)-1] == '~')) + continue; + if (strstr(d->d_name,".rpmnew") || strstr(d->d_name,".rpmsave")) + continue; + + ensure_array((void**)&files, &nfiles); + if ((p = strchr(d->d_name, '.'))) + { + *p = '\0'; + } + files[nfiles++] = strdup(d->d_name); + } closedir(dir); if (nfiles) - { - qsort(files, nfiles, sizeof(char*), compare); - for (i = 0; i < nfiles; ++i) - { - char fn[4096]; - int l; - // skip duplicates - if (i && !strcmp(files[i-1], files[i])) - continue; - - snprintf(fn, sizeof(fn), "/etc/permissions.d/%s", files[i]); - if (access(fn, R_OK) == 0) - { - ensure_array((void**)&permfiles, &npermfiles); - permfiles[npermfiles++] = strdup(fn); - } - - for (l = 0; l < nlevel; ++l) - { - snprintf(fn, sizeof(fn), "/etc/permissions.d/%s.%s", files[i], level[l]); - - if (access(fn, R_OK) == 0) - { - ensure_array((void**)&permfiles, &npermfiles); - permfiles[npermfiles++] = strdup(fn); - } - } - - } - } + { + qsort(files, nfiles, sizeof(char*), compare); + for (i = 0; i < nfiles; ++i) + { + char fn[4096]; + size_t l; + // skip duplicates + if (i && !strcmp(files[i-1], files[i])) + continue; + + snprintf(fn, sizeof(fn), "/etc/permissions.d/%s", files[i]); + if (access(fn, R_OK) == 0) + { + ensure_array((void**)&permfiles, &npermfiles); + permfiles[npermfiles++] = strdup(fn); + } + + for (l = 0; l < nlevel; ++l) + { + snprintf(fn, sizeof(fn), "/etc/permissions.d/%s.%s", files[i], level[l]); + + if (access(fn, R_OK) == 0) + { + ensure_array((void**)&permfiles, &npermfiles); + permfiles[npermfiles++] = strdup(fn); + } + } + + } + for (i = 0; i < nfiles; ++i) + { + free(files[i]); + } + } + free(files); } // 4. central permissions files with user defined level incl 'local' for (i = 0; i < nlevel; ++i) @@ -404,14 +411,14 @@ collect_permfiles() char fn[4096]; if (!strcmp(level[i], "easy") || !strcmp(level[i], "secure") || !strcmp(level[i], "paranoid")) - continue; + continue; snprintf(fn, sizeof(fn), "/etc/permissions.%s", level[i]); if (access(fn, R_OK) == 0) - { - ensure_array((void**)&permfiles, &npermfiles); - permfiles[npermfiles++] = strdup(fn); - } + { + ensure_array((void**)&permfiles, &npermfiles); + permfiles[npermfiles++] = strdup(fn); + } } } @@ -424,117 +431,158 @@ usage(int x) "b) chkstat --system [OPTIONS] <files>...\n" "\n" "Options:\n" -" --set apply changes\n" -" --warn only tell which changes are needed\n" -" --noheader don't print intro message\n" -" --fscaps force use of fscaps\n" -" --no-fscaps disable use of fscaps\n" -" --system system mode, act according to /etc/permissions/security\n" -" --level LEVEL force use LEVEL (only with --system)\n" -" --examine FILE apply to specified file only\n" -" --files FILELIST read list of files to apply from FILELIST\n" -" --root DIR check files relative to DIR\n" +" --set apply changes\n" +" --warn only tell which changes are needed\n" +" --noheader don't print intro message\n" +" --fscaps force use of fscaps\n" +" --no-fscaps disable use of fscaps\n" +" --system system mode, act according to /etc/permissions/security\n" +" --level LEVEL force use LEVEL (only with --system)\n" +" --examine FILE apply to specified file only\n" +" --files FILELIST read list of files to apply from FILELIST\n" +" --root DIR check files relative to DIR\n" ); exit(x); } int -safepath(char *path, uid_t uid, gid_t gid) +safe_open(char *path, struct stat *stb, uid_t target_uid, bool *traversed_insecure) { - struct stat stb; - char pathbuf[1024]; - char linkbuf[1024]; - char *p, *p2; - int l, l2, lcnt; + char pathbuf[PATH_MAX]; + char *p; + int lcnt; + int pathfd = -1; + struct stat root_st; + + *traversed_insecure = false; lcnt = 0; - l2 = strlen(path); - if ((unsigned)l2 >= sizeof(pathbuf)) - return 0; - strcpy(pathbuf, path); - if (pathbuf[0] != '/') - return 0; - p = pathbuf + rootl; - for (;;) + if ((size_t)snprintf(pathbuf, sizeof(pathbuf), "%s", path + rootl) >= sizeof(pathbuf)) + goto fail; + p = pathbuf; + do { - p = strchr(p, '/'); - if (!p) - return 1; - *p = 0; - if (lstat(*pathbuf ? pathbuf : "/", &stb)) - return 0; - if (S_ISLNK(stb.st_mode)) - { - if (++lcnt >= 256) - return 0; - l = readlink(pathbuf, linkbuf, sizeof(linkbuf)); - if (l <= 0 || (unsigned)l >= sizeof(linkbuf)) - return 0; - while(l && linkbuf[l - 1] == '/') - l--; - if ((unsigned)l + 1 >= sizeof(linkbuf)) - return 0; - linkbuf[l++] = '/'; - linkbuf[l] = 0; - *p++ = '/'; - if (linkbuf[0] == '/') - { - if (rootl) - { - p[-1] = 0; - fprintf(stderr, "can't handle symlink %s at the moment\n", pathbuf); - return 0; - } - l2 -= (p - pathbuf); - memmove(pathbuf + rootl, p, l2 + 1); - l2 += rootl; - p = pathbuf + rootl; - } - else - { - if (p - 1 == pathbuf) - return 0; /* huh, "/" is a symlink */ - for (p2 = p - 2; p2 >= pathbuf; p2--) - if (*p2 == '/') - break; - if (p2 < pathbuf + rootl) /* cannot happen */ - return 0; - p2++; /* am now after '/' */ - memmove(p2, p, pathbuf + l2 - p + 1); - l2 -= (p - p2); - p = p2; - } - if ((unsigned)(l + l2) >= sizeof(pathbuf)) - return 0; - memmove(p + l, p, pathbuf + l2 - p + 1); - memmove(p, linkbuf, l); - l2 += l; - if (pathbuf[0] != '/') /* cannot happen */ - return 0; - if (p == pathbuf) - p++; - continue; - } - if (!S_ISDIR(stb.st_mode)) - return 0; - - /* write is always forbidden for other */ - if ((stb.st_mode & 02) != 0) - return 0; + *p = '/'; + char *cursor = p + 1; + + if (pathfd == -1) + { + pathfd = open(rootl ? root : "/", O_PATH | O_CLOEXEC); + if (pathfd == -1) + { + fprintf(stderr, "failed to open root directory %s: %s\n", root, strerror(errno)); + goto fail; + } + if (fstat(pathfd, &root_st)) + { + fprintf(stderr, "failed to stat root directory %s: %s\n", root, strerror(errno)); + goto fail; + } + // stb and pathfd must be in sync for the root-escape check below + memcpy(stb, &root_st, sizeof(*stb)); + } + + p = strchr(cursor, '/'); + // p is NULL when we reach the final path element + if (p) + *p = 0; + + // multiple consecutive slashes: ignore + if (p && *cursor == '\0') + continue; + + // never move up from the configured root directory (using the stat result from the previous loop iteration) + if (strcmp(cursor, "..") == 0 && rootl && stb->st_dev == root_st.st_dev && stb->st_ino == root_st.st_ino) + continue; + + // cursor is an empty string for trailing slashes, open again with different open_flags. + int newpathfd = openat(pathfd, *cursor ? cursor : ".", O_PATH | O_NOFOLLOW | O_CLOEXEC | O_NONBLOCK); + if (newpathfd == -1) + goto fail; + + close(pathfd); + pathfd = newpathfd; + + if (fstat(pathfd, stb)) + goto fail; - /* owner must be ok as she may change the mode */ + /* owner of directories must be trusted for setuid/setgid/capabilities as we have no way to verify file contents */ /* for euid != 0 it is also ok if the owner is euid */ - if (stb.st_uid && stb.st_uid != uid && stb.st_uid != euid) - return 0; + if (stb->st_uid && stb->st_uid != euid && p) + *traversed_insecure = true; + // path is in a world-writable directory, or file is world-writable itself. + if (!S_ISLNK(stb->st_mode) && (stb->st_mode & S_IWOTH) && p) + *traversed_insecure = true; + // if parent directory is not owned by root, the file owner must match the owner of parent + if (stb->st_uid && stb->st_uid != target_uid && stb->st_uid != euid) + { + if (p) + goto fail_insecure_path; + // do not backport this behavior change + //else + // fprintf(stderr, "%s: has unexpected owner. refusing to correct due to unknown integrity.\n", path+rootl); + //goto fail; + } - /* group gid may do fancy things */ - /* for euid != 0 we don't check this */ - if ((stb.st_mode & 020) != 0 && !euid) - if (!gid || stb.st_gid != gid) - return 0; + if (S_ISLNK(stb->st_mode)) + { + // Don't follow symlinks owned by regular users. + // In theory, we could also trust symlinks where the owner of the target matches the owner + // of the link, but we're going the simple route for now. + if (stb->st_uid && stb->st_uid != euid) + goto fail_insecure_path; + + if (++lcnt >= 256) + goto fail; + char linkbuf[PATH_MAX]; + ssize_t l = readlinkat(pathfd, "", linkbuf, sizeof(linkbuf) - 1); + if (l <= 0 || (size_t)l >= sizeof(linkbuf) - 1) + goto fail; + while(l && linkbuf[l - 1] == '/') + l--; + linkbuf[l] = 0; + if (linkbuf[0] == '/') + { + // absolute link + close(pathfd); + pathfd = -1; + } + size_t len; + char tmp[sizeof(pathbuf)]; // need a temporary buffer because p points into pathbuf and snprintf doesn't allow the same buffer as source and destination + if (p) + len = (size_t)snprintf(tmp, sizeof(tmp), "%s/%s", linkbuf, p + 1); + else + len = (size_t)snprintf(tmp, sizeof(tmp), "%s", linkbuf); + if (len >= sizeof(pathbuf)) + goto fail; + strcpy(pathbuf, tmp); + p = pathbuf; + } + } while (p); - *p++ = '/'; - } + // world-writable file: error out due to unknown file integrity + if (S_ISREG(stb->st_mode) && (stb->st_mode & S_IWOTH)) { + fprintf(stderr, "%s: file has insecure permissions (world-writable)\n", path+rootl); + goto fail; + } + + return pathfd; +fail_insecure_path: + + { + char linkpath[PATH_MAX]; + char procpath[100]; + snprintf(procpath, sizeof(procpath), "/proc/self/fd/%d", pathfd); + ssize_t l = readlink(procpath, linkpath, sizeof(linkpath) - 1); + if (l > 0 && (size_t)l < sizeof(linkpath) - 1) + linkpath[l] = '\0'; + fprintf(stderr, "%s: on an insecure path - %s has different non-root owner who could tamper with the file.\n", path+rootl, linkpath); + } + +fail: + if (pathfd >= 0) + close(pathfd); + return -1; } /* check /sys/kernel/fscaps, 2.6.39 */ @@ -560,7 +608,7 @@ out: int main(int argc, char **argv) { - char *opt, *p, *str; + char *opt, *str; int told = 0; int use_checklist = 0; int systemmode = 0; @@ -568,16 +616,17 @@ main(int argc, char **argv) FILE *fp; char line[512]; char *part[4]; - int i, pcnt, lcnt; + int pcnt, lcnt; + size_t i; int inpart; mode_t mode; struct perm *e; - struct stat stb, stb2; + struct stat stb; struct passwd *pwd = 0; struct group *grp = 0; uid_t uid; gid_t gid; - int fd, r; + int fd = -1; int errors = 0; cap_t caps = NULL; @@ -585,137 +634,137 @@ main(int argc, char **argv) { opt = argv[1]; if (!strcmp(opt, "--")) - break; + break; if (*opt == '-' && opt[1] == '-') - opt++; + opt++; if (!strcmp(opt, "-system")) - { - argc--; - argv++; - systemmode = 1; - continue; - } + { + argc--; + argv++; + systemmode = 1; + continue; + } // hidden option for use by suseconfig only if (!strcmp(opt, "-suseconfig")) - { - argc--; - argv++; - suseconfig = 1; - systemmode = 1; - continue; - } + { + argc--; + argv++; + suseconfig = 1; + systemmode = 1; + continue; + } if (!strcmp(opt, "-fscaps")) - { - argc--; - argv++; - have_fscaps = 1; - continue; - } + { + argc--; + argv++; + have_fscaps = 1; + continue; + } if (!strcmp(opt, "-no-fscaps")) - { - argc--; - argv++; - have_fscaps = 0; - continue; - } + { + argc--; + argv++; + have_fscaps = 0; + continue; + } if (!strcmp(opt, "-s") || !strcmp(opt, "-set")) - { - do_set=1; - argc--; - argv++; - continue; - } + { + do_set=1; + argc--; + argv++; + continue; + } if (!strcmp(opt, "-warn")) - { - do_set=0; - argc--; - argv++; - continue; - } + { + do_set=0; + argc--; + argv++; + continue; + } if (!strcmp(opt, "-n") || !strcmp(opt, "-noheader")) - { - told = 1; - argc--; - argv++; - continue; - } + { + told = 1; + argc--; + argv++; + continue; + } if (!strcmp(opt, "-e") || !strcmp(opt, "-examine")) - { - argc--; - argv++; - if (argc == 1) - { - fprintf(stderr, "examine: argument required\n"); - exit(1); - } - add_checklist(argv[1]); - use_checklist = 1; - argc--; - argv++; - continue; - } + { + argc--; + argv++; + if (argc == 1) + { + fprintf(stderr, "examine: argument required\n"); + exit(1); + } + add_checklist(argv[1]); + use_checklist = 1; + argc--; + argv++; + continue; + } if (!strcmp(opt, "-level")) - { - argc--; - argv++; - if (argc == 1) - { - fprintf(stderr, "level: argument required\n"); - exit(1); - } - force_level = argv[1]; - argc--; - argv++; - continue; - } + { + argc--; + argv++; + if (argc == 1) + { + fprintf(stderr, "level: argument required\n"); + exit(1); + } + force_level = argv[1]; + argc--; + argv++; + continue; + } if (!strcmp(opt, "-f") || !strcmp(opt, "-files")) - { - argc--; - argv++; - if (argc == 1) - { - fprintf(stderr, "files: argument required\n"); - exit(1); - } - if ((fp = fopen(argv[1], "r")) == 0) - { - fprintf(stderr, "files: %s: %s\n", argv[1], strerror(errno)); - exit(1); - } - while (readline(fp, line, sizeof(line))) - { - if (!*line) - continue; - add_checklist(line); - } - fclose(fp); - use_checklist = 1; - argc--; - argv++; - continue; - } + { + argc--; + argv++; + if (argc == 1) + { + fprintf(stderr, "files: argument required\n"); + exit(1); + } + if ((fp = fopen(argv[1], "r")) == 0) + { + fprintf(stderr, "files: %s: %s\n", argv[1], strerror(errno)); + exit(1); + } + while (readline(fp, line, sizeof(line))) + { + if (!*line) + continue; + add_checklist(line); + } + fclose(fp); + use_checklist = 1; + argc--; + argv++; + continue; + } if (!strcmp(opt, "-r") || !strcmp(opt, "-root")) - { - argc--; - argv++; - if (argc == 1) - { - fprintf(stderr, "root: argument required\n"); - exit(1); - } - root = argv[1]; - rootl = strlen(root); - if (*root != '/') - { - fprintf(stderr, "root: must begin with '/'\n"); - exit(1); - } - argc--; - argv++; - continue; - } + { + argc--; + argv++; + if (argc == 1) + { + fprintf(stderr, "root: argument required\n"); + exit(1); + } + root = argv[1]; + rootl = strlen(root); + if (*root != '/') + { + fprintf(stderr, "root: must begin with '/'\n"); + exit(1); + } + argc--; + argv++; + continue; + } if (*opt == '-') - usage(!strcmp(opt, "-h") || !strcmp(opt, "-help") ? 0 : 1); + usage(!strcmp(opt, "-h") || !strcmp(opt, "-help") ? 0 : 1); break; } @@ -724,50 +773,50 @@ main(int argc, char **argv) const char file[] = "/etc/sysconfig/security"; parse_sysconf(file); if(do_set == -1) - { - if (default_set < 0) - { - fprintf(stderr, "permissions handling disabled in %s\n", file); - exit(0); - } - if (suseconfig && default_set) - { - char* module = getenv("ONLY_MODULE"); - if (!module || strcmp(module, "permissions")) - { - puts("no permissions will be changed if not called explicitly"); - default_set = 0; - } - } - do_set = default_set; - } + { + if (default_set < 0) + { + fprintf(stderr, "permissions handling disabled in %s\n", file); + exit(0); + } + if (suseconfig && default_set) + { + char* module = getenv("ONLY_MODULE"); + if (!module || strcmp(module, "permissions")) + { + puts("no permissions will be changed if not called explicitly"); + default_set = 0; + } + } + do_set = default_set; + } if (force_level) - { - char *p = strtok(force_level, " "); - do - { - add_level(p); - } - while ((p = strtok(NULL, " "))); - } + { + char *p = strtok(force_level, " "); + do + { + add_level(p); + } + while ((p = strtok(NULL, " "))); + } if (!nlevel) - add_level("secure"); + add_level("secure"); add_level("local"); // always add local - for (i = 1; i < argc; i++) - { - add_checklist(argv[i]); - use_checklist = 1; - continue; - } + for (i = 1; i < (size_t)argc; i++) + { + add_checklist(argv[i]); + use_checklist = 1; + continue; + } collect_permfiles(); } else if (argc <= 1) usage(1); else { - npermfiles = argc-1; + npermfiles = (size_t)argc-1; permfiles = &argv[1]; } @@ -786,94 +835,95 @@ main(int argc, char **argv) for (i = 0; i < npermfiles; i++) { if ((fp = fopen(permfiles[i], "r")) == 0) - { - perror(permfiles[i]); - exit(1); - } + { + perror(permfiles[i]); + exit(1); + } lcnt = 0; struct perm* last = NULL; int extline; while (readline(fp, line, sizeof(line))) - { - extline = 0; - lcnt++; - if (*line == 0 || *line == '#' || *line == '$') - continue; - inpart = 0; - pcnt = 0; - for (p = line; *p; p++) - { - if (*p == ' ' || *p == '\t') - { - *p = 0; - if (inpart) - { - pcnt++; - inpart = 0; - } - continue; - } - if (pcnt == 0 && !inpart && *p == '+') - { - extline = 1; - break; - } - if (!inpart) - { - inpart = 1; - if (pcnt == 3) - break; - part[pcnt] = p; - } - } - if (extline) - { - if (!last) - { - BAD_LINE(); - continue; - } - if (!strncmp(p, "+capabilities ", 14)) - { - if (have_fscaps != 1) - continue; - p += 14; - caps = cap_from_text(p); - if (caps) - { - cap_free(last->caps); - last->caps = caps; - } - continue; - } - BAD_LINE(); - continue; - } - if (inpart) - pcnt++; - if (pcnt != 3) - { - BAD_LINE(); - continue; - } - part[3] = part[2]; - part[2] = strchr(part[1], ':'); - if (!part[2]) - part[2] = strchr(part[1], '.'); - if (!part[2]) - { - BAD_LINE(); - continue; - } - *part[2]++ = 0; - mode = strtoul(part[3], part + 3, 8); - if (mode > 07777 || part[3][0]) - { - BAD_LINE(); - continue; - } - last = add_permlist(part[0], part[1], part[2], mode); - } + { + extline = 0; + lcnt++; + if (*line == 0 || *line == '#' || *line == '$') + continue; + inpart = 0; + pcnt = 0; + char *p; + for (p = line; *p; p++) + { + if (*p == ' ' || *p == '\t') + { + *p = 0; + if (inpart) + { + pcnt++; + inpart = 0; + } + continue; + } + if (pcnt == 0 && !inpart && *p == '+') + { + extline = 1; + break; + } + if (!inpart) + { + inpart = 1; + if (pcnt == 3) + break; + part[pcnt] = p; + } + } + if (extline) + { + if (!last) + { + BAD_LINE(); + continue; + } + if (!strncmp(p, "+capabilities ", 14)) + { + if (have_fscaps != 1) + continue; + p += 14; + caps = cap_from_text(p); + if (caps) + { + cap_free(last->caps); + last->caps = caps; + } + continue; + } + BAD_LINE(); + continue; + } + if (inpart) + pcnt++; + if (pcnt != 3) + { + BAD_LINE(); + continue; + } + part[3] = part[2]; + part[2] = strchr(part[1], ':'); + if (!part[2]) + part[2] = strchr(part[1], '.'); + if (!part[2]) + { + BAD_LINE(); + continue; + } + *part[2]++ = 0; + mode = (mode_t)strtoul(part[3], part + 3, 8); + if (mode > 07777 || part[3][0]) + { + BAD_LINE(); + continue; + } + last = add_permlist(part[0], part[1], part[2], mode); + } fclose(fp); } @@ -881,243 +931,244 @@ main(int argc, char **argv) for (e = permlist; e; e = e->next) { if (use_checklist && !in_checklist(e->file+rootl)) - continue; - if (lstat(e->file, &stb)) - continue; + continue; + + pwd = strcmp(e->owner, "unknown") ? getpwnam(e->owner) : NULL; + grp = strcmp(e->group, "unknown") ? getgrnam(e->group) : NULL; + uid = pwd ? pwd->pw_uid : 0; + gid = grp ? grp->gr_gid : 0; + + bool traversed_insecure; + if (fd >= 0) + { + // close fd from previous loop iteration + close(fd); + fd = -1; + } + + fd = safe_open(e->file, &stb, uid, &traversed_insecure); + if (fd < 0) + continue; if (S_ISLNK(stb.st_mode)) - continue; + continue; + if (!e->mode && !strcmp(e->owner, "unknown")) - { - char uids[16], gids[16]; - pwd = getpwuid(stb.st_uid); - grp = getgrgid(stb.st_gid); - if (!pwd) - sprintf(uids, "%d", stb.st_uid); - if (!grp) - sprintf(gids, "%d", stb.st_gid); - fprintf(stderr, "%s: cannot verify %s:%s %04o - not listed in /etc/permissions\n", - e->file+rootl, - pwd?pwd->pw_name:uids, - grp?grp->gr_name:gids, - (int)(stb.st_mode&07777)); - pwd = 0; - grp = 0; - continue; - } - if ((!pwd || strcmp(pwd->pw_name, e->owner)) && (pwd = getpwnam(e->owner)) == 0) - { - fprintf(stderr, "%s: unknown user %s\n", e->file+rootl, e->owner); - continue; - } - if ((!grp || strcmp(grp->gr_name, e->group)) && (grp = getgrnam(e->group)) == 0) - { - fprintf(stderr, "%s: unknown group %s\n", e->file+rootl, e->group); - continue; - } - uid = pwd->pw_uid; - gid = grp->gr_gid; - caps = cap_get_file(e->file); + { + fprintf(stderr, "%s: cannot verify ", e->file+rootl); + pwd = getpwuid(stb.st_uid); + if (pwd) + fprintf(stderr, "%s:", pwd->pw_name); + else + fprintf(stderr, "%d:", stb.st_uid); + grp = getgrgid(stb.st_gid); + if (grp) + fprintf(stderr, "%s", grp->gr_name); + else + fprintf(stderr, "%d", stb.st_gid); + fprintf(stderr, " %04o - not listed in /etc/permissions\n", + (int)(stb.st_mode&07777)); + continue; + } + if (!pwd) + { + fprintf(stderr, "%s: unknown user %s. ignoring entry.\n", e->file+rootl, e->owner); + continue; + } + else if (!grp) + { + fprintf(stderr, "%s: unknown group %s. ignoring entry.\n", e->file+rootl, e->group); + continue; + } + + // fd is opened with O_PATH, file oeprations like cap_get_fd() and fchown() don't work with it. + // + // We also don't want to do a proper open() of the file, since that doesn't even work for sockets + // and might have side effects for pipes or devices. + // + // So we use path-based operations (yes!) with /proc/self/fd/xxx. (Since safe_open already resolved + // all symlinks, 'fd' can't refer to a symlink which we'd have to worry might get followed.) + char fd_path[100]; + snprintf(fd_path, sizeof(fd_path), "/proc/self/fd/%d", fd); + + caps = cap_get_file(fd_path); if (!caps) - { - cap_free(caps); - caps = NULL; - if (errno == EOPNOTSUPP) - { - //fprintf(stderr, "%s: fscaps not supported\n", e->file+rootl); - cap_free(e->caps); - e->caps = NULL; - } - } + { + // we get EBADF for files that don't support capabilities, e.g. sockets or FIFOs + if (errno == EBADF) + { + if (e->caps) + { + fprintf(stderr, "%s: cannot assign capabilities for this kind of file\n", e->file+rootl); + cap_free(e->caps); + errors++; + } + e->caps = NULL; + } + if (errno == EOPNOTSUPP) + { + if (e->caps) + cap_free(e->caps); + e->caps = NULL; + } + } if (e->caps) - { - e->mode &= 0777; - } + { + e->mode &= 0777; + } int perm_ok = (stb.st_mode & 07777) == e->mode; int owner_ok = stb.st_uid == uid && stb.st_gid == gid; int caps_ok = 0; if (!caps && !e->caps) - caps_ok = 1; + caps_ok = 1; else if (caps && e->caps && !cap_compare(e->caps, caps)) - caps_ok = 1; + caps_ok = 1; if (perm_ok && owner_ok && caps_ok) - continue; + continue; if (!told) - { - told = 1; - printf("Checking permissions and ownerships - using the permissions files\n"); - for (i = 0; i < npermfiles; i++) - printf("\t%s\n", permfiles[i]); - if (rootl) - { - printf("Using root %s\n", root); - } - } + { + told = 1; + printf("Checking permissions and ownerships - using the permissions files\n"); + for (i = 0; i < npermfiles; i++) + printf("\t%s\n", permfiles[i]); + if (rootl) + { + printf("Using root %s\n", root); + } + } if (!do_set) - printf("%s should be %s:%s %04o", e->file+rootl, e->owner, e->group, e->mode); + printf("%s should be %s:%s %04o", e->file+rootl, e->owner, e->group, e->mode); else - printf("setting %s to %s:%s %04o", e->file+rootl, e->owner, e->group, e->mode); + printf("setting %s to %s:%s %04o", e->file+rootl, e->owner, e->group, e->mode); if (!caps_ok && e->caps) { - str = cap_to_text(e->caps, NULL); - printf(" \"%s\"", str); - cap_free(str); + str = cap_to_text(e->caps, NULL); + printf(" \"%s\"", str); + cap_free(str); } printf(". (wrong"); if (!owner_ok) - { - pwd = getpwuid(stb.st_uid); - grp = getgrgid(stb.st_gid); - if (pwd) - printf(" owner/group %s", pwd->pw_name); - else - printf(" owner/group %d", stb.st_uid); - if (grp) - printf(":%s", grp->gr_name); - else - printf(":%d", stb.st_gid); - pwd = 0; - grp = 0; - } + { + pwd = getpwuid(stb.st_uid); + grp = getgrgid(stb.st_gid); + if (pwd) + printf(" owner/group %s", pwd->pw_name); + else + printf(" owner/group %d", stb.st_uid); + if (grp) + printf(":%s", grp->gr_name); + else + printf(":%d", stb.st_gid); + pwd = 0; + grp = 0; + } if (!perm_ok) - printf(" permissions %04o", (int)(stb.st_mode & 07777)); + printf(" permissions %04o", (int)(stb.st_mode & 07777)); if (!caps_ok) { - if (!perm_ok || !owner_ok) - { - fputc(',', stdout); - } - if (caps) - { - str = cap_to_text(caps, NULL); - printf(" capabilities \"%s\"", str); - cap_free(str); - } - else - fputs(" missing capabilities", stdout); + if (!perm_ok || !owner_ok) + { + fputc(',', stdout); + } + if (caps) + { + str = cap_to_text(caps, NULL); + printf(" capabilities \"%s\"", str); + cap_free(str); + } + else + fputs(" missing capabilities", stdout); } putchar(')'); putchar('\n'); if (!do_set) - continue; + continue; + + // don't give high privileges to files controlled by non-root users + if ((e->caps || (e->mode & S_ISUID) || (e->mode & S_ISGID)) && !S_ISREG(stb.st_mode) && !S_ISDIR(stb.st_mode)) + { + fprintf(stderr, "%s: will only assign capabilities or setXid bits to regular files or directories\n", e->file+rootl); + errors++; + continue; + } + if (traversed_insecure && (e->caps || (e->mode & S_ISUID) || ((e->mode & S_ISGID) && S_ISREG(stb.st_mode)))) + { + fprintf(stderr, "%s: will not give away capabilities or setXid bits on an insecure path\n", e->file+rootl); + errors++; + continue; + } - fd = -1; - if (S_ISDIR(stb.st_mode)) - { - fd = open(e->file, O_RDONLY|O_DIRECTORY|O_NONBLOCK|O_NOFOLLOW); - if (fd == -1) - { - perror(e->file); - errors++; - continue; - } - } - else if (S_ISREG(stb.st_mode)) - { - fd = open(e->file, O_RDONLY|O_NONBLOCK|O_NOFOLLOW); - if (fd == -1) - { - perror(e->file); - errors++; - continue; - } - if (fstat(fd, &stb2)) - continue; - if (stb.st_mode != stb2.st_mode || stb.st_nlink != stb2.st_nlink || stb.st_dev != stb2.st_dev || stb.st_ino != stb2.st_ino) - { - fprintf(stderr, "%s: too fluctuating\n", e->file+rootl); - errors++; - continue; - } - if (stb.st_nlink > 1 && !safepath(e->file, 0, 0)) - { - fprintf(stderr, "%s: on an insecure path\n", e->file+rootl); - errors++; - continue; - } - else if (e->mode & 06000) - { - /* extra checks for s-bits */ - if (!safepath(e->file, (e->mode & 02000) == 0 ? uid : 0, (e->mode & 04000) == 0 ? gid : 0)) - { - fprintf(stderr, "%s: will not give away s-bits on an insecure path\n", e->file+rootl); - errors++; - continue; - } - } - } - else if (strncmp(e->file, "/dev/", 5) != 0) // handle special files only in /dev - { - fprintf(stderr, "%s: don't know what to do with that type of file\n", e->file+rootl); - errors++; - continue; - } if (euid == 0 && !owner_ok) - { - /* if we change owner or group of a setuid file the bit gets reset so - also set perms again */ - if (e->mode & 06000) - perm_ok = 0; - if (fd >= 0) - r = fchown(fd, uid, gid); - else - r = chown(e->file, uid, gid); - if (r) - { - fprintf(stderr, "%s: chown: %s\n", e->file+rootl, strerror(errno)); - errors++; - } - if (fd >= 0) - r = fstat(fd, &stb); - else - r = lstat(e->file, &stb); - if (r) - { - fprintf(stderr, "%s: too fluctuating\n", e->file+rootl); - errors++; - continue; - } - } - if (!perm_ok) - { - if (fd >= 0) - r = fchmod(fd, e->mode); - else - r = chmod(e->file, e->mode); - if (r) - { - fprintf(stderr, "%s: chmod: %s\n", e->file+rootl, strerror(errno)); - errors++; - } - } + { + /* if we change owner or group of a setuid file the bit gets reset so + also set perms again */ + if (e->mode & (S_ISUID | S_ISGID)) + perm_ok = 0; + if (chown(fd_path, uid, gid)) + { + fprintf(stderr, "%s: chown: %s\n", e->file+rootl, strerror(errno)); + errors++; + } + } + if (!perm_ok && chmod(fd_path, e->mode)) + { + fprintf(stderr, "%s: chmod: %s\n", e->file+rootl, strerror(errno)); + errors++; + } if (!caps_ok) - { - if (fd >= 0) - r = cap_set_fd(fd, e->caps); - else - r = cap_set_file(e->file, e->caps); - if (r) - { - fprintf(stderr, "%s: cap_set_file: %s\n", e->file+rootl, strerror(errno)); - errors++; - } - } - if (fd >= 0) - close(fd); + { + if (S_ISREG(stb.st_mode)) + { + // cap_set_file() tries to be helpful and does a lstat() to check that it isn't called on + // a symlink. So we have to open() it (without O_PATH) and use cap_set_fd(). + int cap_fd = open(fd_path, O_NOATIME | O_CLOEXEC); + if (cap_fd == -1) + { + fprintf(stderr, "%s: open() for changing capabilities: %s\n", e->file+rootl, strerror(errno)); + errors++; + } + else if (cap_set_fd(cap_fd, e->caps)) + { + fprintf(stderr, "%s: cap_set_fd: %s\n", e->file+rootl, strerror(errno)); + errors++; + } + if (cap_fd != -1) + close(cap_fd); + } + else + { + fprintf(stderr, "%s: cannot set capabilities: not a regular file\n", e->file+rootl); + errors++; + } + } + } + // close fd from last loop iteration + if (fd >= 0) + { + close(fd); + fd = -1; } if (errors) { fprintf(stderr, "ERROR: not all operations were successful.\n"); exit(1); } + if (permfiles != argv + 1) + { + for (i = 0; i < npermfiles; i++) + { + free(permfiles[i]); + } + free(permfiles); + } exit(0); }
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor