File 0007-chkstat-fix-privesc-CVE-2019-3690.patch of Package permissions.13179
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/sysconfig/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/sysconfig/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 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);
}