static PyObject * strcaps_get_file(PyObject *self, PyObject *args) { // (int) fd / (str) path / (file) file PyObject *file; if (!PyArg_ParseTuple(args, "O", &file)) return NULL; cap_t caps; if (PyFile_Check(file)) caps = cap_get_fd(PyObject_AsFileDescriptor(file)); else if (PyInt_Check(file)) caps = cap_get_fd(PyInt_AsLong(file)); else if (PyString_Check(file)) caps = cap_get_file(PyString_AsString(file)); else if (PyUnicode_Check(file)) { PyObject *file_dec = PyUnicode_AsEncodedString( file, Py_FileSystemDefaultEncoding, "strict" ); if (file_dec == NULL) return NULL; caps = cap_get_file(PyString_AsString(file_dec)); Py_DECREF(file_dec); } else { PyErr_SetString( PyExc_TypeError, "Expecting file object, descriptor int or path string" ); return NULL; } size_t strcaps_len; char *strcaps; if (caps == NULL) { if (errno == ENODATA) { strcaps = "\0"; strcaps_len = 0; } else { PyErr_SetFromErrno(PyExc_OSError); return NULL; } } else strcaps = cap_to_text(caps, &strcaps_len); cap_free(caps); return Py_BuildValue("s#", strcaps, strcaps_len); }; // (str) caps
/* * Return a caps hash containing the capabilities of self. */ VALUE cap2_file_getcaps(VALUE self) { cap_t cap_d; char *filename; VALUE result; filename = cap2_file_filename(self); cap_d = cap_get_file(filename); if (cap_d == NULL && errno != ENODATA) { rb_raise( rb_eRuntimeError, "Failed to get capabilities for file %s: (%s)\n", filename, strerror(errno) ); } else { result = cap2_caps_to_hash(cap_d); cap_free(cap_d); return result; } }
static int bin_getcap(char *nam, char **argv, UNUSED(Options ops), UNUSED(int func)) { int ret = 0; do { char *result = NULL; ssize_t length; cap_t caps; caps = cap_get_file(unmetafy(dupstring(*argv), NULL)); if(caps) result = cap_to_text(caps, &length); if (!caps || !result) { zwarnnam(nam, "%s: %e", *argv, errno); ret = 1; } else printf("%s %s\n", *argv, result); cap_free(caps); } while(*++argv); return ret; }
char * do_cap_get_file (const char *path) { cap_t cap; char *r, *ret; CHROOT_IN; cap = cap_get_file (path); CHROOT_OUT; if (cap == NULL) { reply_with_perror ("%s", path); return NULL; } r = cap_to_text (cap, NULL); if (r == NULL) { reply_with_perror ("cap_to_text"); cap_free (cap); return NULL; } cap_free (cap); /* 'r' is not an ordinary pointer that can be freed with free(3)! * In the current implementation of libcap, if you try to do that it * will segfault. We have to duplicate this into an ordinary * buffer, then call cap_free (r). */ ret = strdup (r); if (ret == NULL) { reply_with_perror ("strdup"); cap_free (r); return NULL; } cap_free (r); return ret; /* caller frees */ }
rpmVerifyAttrs rpmfilesVerify(rpmfiles fi, int ix, rpmVerifyAttrs omitMask) { rpmfileAttrs fileAttrs = rpmfilesFFlags(fi, ix); rpmVerifyAttrs flags = rpmfilesVFlags(fi, ix); const char * fn = rpmfilesFN(fi, ix); struct stat sb, fsb; rpmVerifyAttrs vfy = RPMVERIFY_NONE; /* * Check to see if the file was installed - if not pretend all is OK. */ switch (rpmfilesFState(fi, ix)) { case RPMFILE_STATE_NETSHARED: case RPMFILE_STATE_NOTINSTALLED: goto exit; break; case RPMFILE_STATE_REPLACED: /* For replaced files we can only verify if it exists at all */ flags = RPMVERIFY_LSTATFAIL; break; case RPMFILE_STATE_WRONGCOLOR: /* * Files with wrong color are supposed to share some attributes * with the actually installed file - verify what we can. */ flags &= ~(RPMVERIFY_FILEDIGEST | RPMVERIFY_FILESIZE | RPMVERIFY_MTIME | RPMVERIFY_RDEV); break; case RPMFILE_STATE_NORMAL: /* File from a non-installed package, try to verify nevertheless */ case RPMFILE_STATE_MISSING: break; } if (fn == NULL || lstat(fn, &sb) != 0 || rpmfilesStat(fi, ix, 0, &fsb)) { vfy |= RPMVERIFY_LSTATFAIL; goto exit; } /* If we expected a directory but got a symlink to one, follow the link */ if (S_ISDIR(fsb.st_mode) && S_ISLNK(sb.st_mode)) { struct stat dsb; /* ...if it actually points to a directory */ if (stat(fn, &dsb) == 0 && S_ISDIR(dsb.st_mode)) { /* ...and is by a legit user, to match fsmVerify() behavior */ if (sb.st_uid == 0 || sb.st_uid == fsb.st_uid) sb = dsb; /* struct assignment */ } } /* Links have no mode, other types have no linkto */ if (S_ISLNK(sb.st_mode)) flags &= ~(RPMVERIFY_MODE); else flags &= ~(RPMVERIFY_LINKTO); /* Not all attributes of non-regular files can be verified */ if (!S_ISREG(sb.st_mode)) flags &= ~(RPMVERIFY_FILEDIGEST | RPMVERIFY_FILESIZE | RPMVERIFY_MTIME | RPMVERIFY_CAPS); /* Content checks of %ghost files are meaningless. */ if (fileAttrs & RPMFILE_GHOST) flags &= ~(RPMVERIFY_FILEDIGEST | RPMVERIFY_FILESIZE | RPMVERIFY_MTIME | RPMVERIFY_LINKTO); /* Don't verify any features in omitMask. */ flags &= ~(omitMask | RPMVERIFY_FAILURES); if (flags & RPMVERIFY_FILEDIGEST) { const unsigned char *digest; int algo; size_t diglen; /* XXX If --nomd5, then prelinked library sizes are not corrected. */ if ((digest = rpmfilesFDigest(fi, ix, &algo, &diglen))) { unsigned char fdigest[diglen]; rpm_loff_t fsize; if (rpmDoDigest(algo, fn, 0, fdigest, &fsize)) { vfy |= (RPMVERIFY_READFAIL|RPMVERIFY_FILEDIGEST); } else { sb.st_size = fsize; if (memcmp(fdigest, digest, diglen)) vfy |= RPMVERIFY_FILEDIGEST; } } else { vfy |= RPMVERIFY_FILEDIGEST; } } if (flags & RPMVERIFY_LINKTO) { char linkto[1024+1]; int size = 0; if ((size = readlink(fn, linkto, sizeof(linkto)-1)) == -1) vfy |= (RPMVERIFY_READLINKFAIL|RPMVERIFY_LINKTO); else { const char * flink = rpmfilesFLink(fi, ix); linkto[size] = '\0'; if (flink == NULL || !rstreq(linkto, flink)) vfy |= RPMVERIFY_LINKTO; } } if ((flags & RPMVERIFY_FILESIZE) && (sb.st_size != fsb.st_size)) vfy |= RPMVERIFY_FILESIZE; if (flags & RPMVERIFY_MODE) { mode_t metamode = fsb.st_mode; mode_t filemode = sb.st_mode; /* * Comparing the type of %ghost files is meaningless, but perms are OK. */ if (fileAttrs & RPMFILE_GHOST) { metamode &= ~S_IFMT; filemode &= ~S_IFMT; } if (metamode != filemode) vfy |= RPMVERIFY_MODE; #if WITH_ACL /* * For now, any non-default acl's on a file is a difference as rpm * cannot have set them. */ acl_t facl = acl_get_file(fn, ACL_TYPE_ACCESS); if (facl) { if (acl_equiv_mode(facl, NULL) == 1) { vfy |= RPMVERIFY_MODE; } acl_free(facl); } #endif } if (flags & RPMVERIFY_RDEV) { if (S_ISCHR(fsb.st_mode) != S_ISCHR(sb.st_mode) || S_ISBLK(fsb.st_mode) != S_ISBLK(sb.st_mode)) { vfy |= RPMVERIFY_RDEV; } else if (S_ISDEV(fsb.st_mode) && S_ISDEV(sb.st_mode)) { rpm_rdev_t st_rdev = (sb.st_rdev & 0xffff); rpm_rdev_t frdev = (fsb.st_rdev & 0xffff); if (st_rdev != frdev) vfy |= RPMVERIFY_RDEV; } } #if WITH_CAP if (flags & RPMVERIFY_CAPS) { /* * Empty capability set ("=") is not exactly the same as no * capabilities at all but suffices for now... */ cap_t cap, fcap; cap = cap_from_text(rpmfilesFCaps(fi, ix)); if (!cap) { cap = cap_from_text("="); } fcap = cap_get_file(fn); if (!fcap) { fcap = cap_from_text("="); } if (cap_compare(cap, fcap) != 0) vfy |= RPMVERIFY_CAPS; cap_free(fcap); cap_free(cap); } #endif if ((flags & RPMVERIFY_MTIME) && (sb.st_mtime != fsb.st_mtime)) vfy |= RPMVERIFY_MTIME; if ((flags & RPMVERIFY_USER) && (sb.st_uid != fsb.st_uid)) vfy |= RPMVERIFY_USER; if ((flags & RPMVERIFY_GROUP) && (sb.st_gid != fsb.st_gid)) vfy |= RPMVERIFY_GROUP; exit: return vfy; }
int main(int argc, char **argv) { char *opt, *p, *str; int told = 0; int use_checklist = 0; int systemmode = 0; int suseconfig = 0; FILE *fp; char line[512]; char *part[4]; int i, pcnt, lcnt; int inpart; mode_t mode; struct perm *e; struct stat stb, stb2; struct passwd *pwd = 0; struct group *grp = 0; uid_t uid; gid_t gid; int fd, r; int errors = 0; cap_t caps = NULL; while (argc > 1) { opt = argv[1]; if (!strcmp(opt, "--")) break; if (*opt == '-' && opt[1] == '-') opt++; if (!strcmp(opt, "-system")) { argc--; argv++; systemmode = 1; continue; } // hidden option for use by suseconfig only if (!strcmp(opt, "-suseconfig")) { argc--; argv++; suseconfig = 1; systemmode = 1; continue; } if (!strcmp(opt, "-fscaps")) { argc--; argv++; have_fscaps = 1; continue; } if (!strcmp(opt, "-no-fscaps")) { argc--; argv++; have_fscaps = 0; continue; } if (!strcmp(opt, "-s") || !strcmp(opt, "-set")) { do_set=1; argc--; argv++; continue; } if (!strcmp(opt, "-warn")) { do_set=0; argc--; argv++; continue; } if (!strcmp(opt, "-n") || !strcmp(opt, "-noheader")) { 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; } if (!strcmp(opt, "-level")) { 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; } 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; } if (*opt == '-') usage(!strcmp(opt, "-h") || !strcmp(opt, "-help") ? 0 : 1); break; } if (systemmode) { 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 (force_level) { char *p = strtok(force_level, " "); do { add_level(p); } while ((p = strtok(NULL, " "))); } if (!nlevel) add_level("secure"); add_level("local"); // always add local for (i = 1; i < argc; i++) { add_checklist(argv[i]); use_checklist = 1; continue; } collect_permfiles(); } else if (argc <= 1) usage(1); else { npermfiles = argc-1; permfiles = &argv[1]; } if (have_fscaps == 1 && !check_fscaps_enabled()) { fprintf(stderr, "Warning: running kernel does not support fscaps\n"); } if (do_set == -1) do_set = 0; // add fake list entries for all files to check for (i = 0; i < nchecklist; i++) add_permlist(checklist[i], "unknown", "unknown", 0); for (i = 0; i < npermfiles; i++) { if ((fp = fopen(permfiles[i], "r")) == 0) { perror(argv[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); } fclose(fp); } euid = geteuid(); for (e = permlist; e; e = e->next) { if (use_checklist && !in_checklist(e->file+rootl)) continue; if (lstat(e->file, &stb)) continue; if (S_ISLNK(stb.st_mode)) 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); 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; } } if (e->caps) { 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; else if (caps && e->caps && !cap_compare(e->caps, caps)) caps_ok = 1; if (perm_ok && owner_ok && caps_ok) 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); } } if (!do_set) 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); if (!caps_ok && e->caps) { 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; } if (!perm_ok) 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); } putchar(')'); putchar('\n'); if (!do_set) 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 (!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 (errors) { fprintf(stderr, "ERROR: not all operations were successful.\n"); exit(1); } exit(0); }
int main(int argc, char **argv) { int tried_to_cap_setfcap = 0; char buffer[MAXCAP+1]; int retval, quiet=0, verify=0; cap_t mycaps; cap_value_t capflag; if (argc < 3) { usage(); } mycaps = cap_get_proc(); if (mycaps == NULL) { fprintf(stderr, "warning - unable to get process capabilities" " (old libcap?)\n"); } while (--argc > 0) { const char *text; cap_t cap_d; if (!strcmp(*++argv, "-q")) { quiet = 1; continue; } if (!strcmp(*argv, "-v")) { verify = 1; continue; } if (!strcmp(*argv, "-r")) { cap_d = NULL; } else { if (!strcmp(*argv,"-")) { retval = read_caps(quiet, *argv, buffer); if (retval) usage(); text = buffer; } else { text = *argv; } cap_d = cap_from_text(text); if (cap_d == NULL) { perror("fatal error"); usage(); } #ifdef DEBUG { ssize_t length; const char *result; result = cap_to_text(cap_d, &length); fprintf(stderr, "caps set to: [%s]\n", result); } #endif } if (--argc <= 0) usage(); /* * Set the filesystem capability for this file. */ if (verify) { cap_t cap_on_file; int cmp; if (cap_d == NULL) { cap_d = cap_from_text("="); } cap_on_file = cap_get_file(*++argv); if (cap_on_file == NULL) { cap_on_file = cap_from_text("="); } cmp = cap_compare(cap_on_file, cap_d); cap_free(cap_on_file); if (cmp != 0) { if (!quiet) { printf("%s differs in [%s%s%s]\n", *argv, CAP_DIFFERS(cmp, CAP_PERMITTED) ? "p" : "", CAP_DIFFERS(cmp, CAP_INHERITABLE) ? "i" : "", CAP_DIFFERS(cmp, CAP_EFFECTIVE) ? "e" : ""); } exit(1); } if (!quiet) { printf("%s: OK\n", *argv); } } else { if (!tried_to_cap_setfcap) { capflag = CAP_SETFCAP; /* * Raise the effective CAP_SETFCAP. */ if (cap_set_flag(mycaps, CAP_EFFECTIVE, 1, &capflag, CAP_SET) != 0) { perror("unable to manipulate CAP_SETFCAP - " "try a newer libcap?"); exit(1); } if (cap_set_proc(mycaps) != 0) { perror("unable to set CAP_SETFCAP effective capability"); exit(1); } tried_to_cap_setfcap = 1; } retval = cap_set_file(*++argv, cap_d); if (retval != 0) { fprintf(stderr, "Failed to set capabilities on file `%s' (%s)\n", argv[0], strerror(errno)); usage(); } } if (cap_d) { cap_free(cap_d); } } exit(0); }
int rpmVerifyFile(const rpmts ts, const rpmfi fi, rpmVerifyAttrs * res, rpmVerifyAttrs omitMask) { rpm_mode_t fmode = rpmfiFMode(fi); rpmfileAttrs fileAttrs = rpmfiFFlags(fi); rpmVerifyAttrs flags = rpmfiVFlags(fi); const char * fn = rpmfiFN(fi); struct stat sb; int rc; *res = RPMVERIFY_NONE; /* * Check to see if the file was installed - if not pretend all is OK. */ switch (rpmfiFState(fi)) { case RPMFILE_STATE_NETSHARED: case RPMFILE_STATE_REPLACED: case RPMFILE_STATE_NOTINSTALLED: case RPMFILE_STATE_WRONGCOLOR: return 0; break; case RPMFILE_STATE_NORMAL: break; } if (fn == NULL || lstat(fn, &sb) != 0) { *res |= RPMVERIFY_LSTATFAIL; return 1; } /* * Not all attributes of non-regular files can be verified. */ if (S_ISDIR(sb.st_mode)) flags &= ~(RPMVERIFY_MD5 | RPMVERIFY_FILESIZE | RPMVERIFY_MTIME | RPMVERIFY_LINKTO | RPMVERIFY_CAPS); else if (S_ISLNK(sb.st_mode)) { flags &= ~(RPMVERIFY_MD5 | RPMVERIFY_FILESIZE | RPMVERIFY_MTIME | RPMVERIFY_MODE | RPMVERIFY_CAPS); #if CHOWN_FOLLOWS_SYMLINK flags &= ~(RPMVERIFY_USER | RPMVERIFY_GROUP); #endif } else if (S_ISFIFO(sb.st_mode)) flags &= ~(RPMVERIFY_MD5 | RPMVERIFY_FILESIZE | RPMVERIFY_MTIME | RPMVERIFY_LINKTO | RPMVERIFY_CAPS); else if (S_ISCHR(sb.st_mode)) flags &= ~(RPMVERIFY_MD5 | RPMVERIFY_FILESIZE | RPMVERIFY_MTIME | RPMVERIFY_LINKTO | RPMVERIFY_CAPS); else if (S_ISBLK(sb.st_mode)) flags &= ~(RPMVERIFY_MD5 | RPMVERIFY_FILESIZE | RPMVERIFY_MTIME | RPMVERIFY_LINKTO | RPMVERIFY_CAPS); else flags &= ~(RPMVERIFY_LINKTO); /* * Content checks of %ghost files are meaningless. */ if (fileAttrs & RPMFILE_GHOST) flags &= ~(RPMVERIFY_MD5 | RPMVERIFY_FILESIZE | RPMVERIFY_MTIME | RPMVERIFY_LINKTO); /* * Don't verify any features in omitMask. */ flags &= ~(omitMask | RPMVERIFY_FAILURES); if (flags & RPMVERIFY_MD5) { const unsigned char *digest; pgpHashAlgo algo; size_t diglen; /* XXX If --nomd5, then prelinked library sizes are not corrected. */ if ((digest = rpmfiFDigest(fi, &algo, &diglen))) { unsigned char fdigest[diglen]; rpm_loff_t fsize; rc = rpmDoDigest(algo, fn, 0, fdigest, &fsize); sb.st_size = fsize; if (rc) { *res |= (RPMVERIFY_READFAIL|RPMVERIFY_MD5); } else if (memcmp(fdigest, digest, diglen)) { *res |= RPMVERIFY_MD5; } } else { *res |= RPMVERIFY_MD5; } } if (flags & RPMVERIFY_LINKTO) { char linkto[1024+1]; int size = 0; if ((size = readlink(fn, linkto, sizeof(linkto)-1)) == -1) *res |= (RPMVERIFY_READLINKFAIL|RPMVERIFY_LINKTO); else { const char * flink = rpmfiFLink(fi); linkto[size] = '\0'; if (flink == NULL || strcmp(linkto, flink)) *res |= RPMVERIFY_LINKTO; } } if (flags & RPMVERIFY_FILESIZE) { if (sb.st_size != rpmfiFSize(fi)) *res |= RPMVERIFY_FILESIZE; } if (flags & RPMVERIFY_MODE) { rpm_mode_t metamode = fmode; rpm_mode_t filemode; /* * Platforms (like AIX) where sizeof(rpm_mode_t) != sizeof(mode_t) * need the (rpm_mode_t) cast here. */ filemode = (rpm_mode_t)sb.st_mode; /* * Comparing the type of %ghost files is meaningless, but perms are OK. */ if (fileAttrs & RPMFILE_GHOST) { metamode &= ~0xf000; filemode &= ~0xf000; } if (metamode != filemode) *res |= RPMVERIFY_MODE; #if WITH_ACL /* * For now, any non-default acl's on a file is a difference as rpm * cannot have set them. */ acl_t facl = acl_get_file(fn, ACL_TYPE_ACCESS); if (facl) { if (acl_equiv_mode(facl, NULL) == 1) { *res |= RPMVERIFY_MODE; } acl_free(facl); } #endif } if (flags & RPMVERIFY_RDEV) { if (S_ISCHR(fmode) != S_ISCHR(sb.st_mode) || S_ISBLK(fmode) != S_ISBLK(sb.st_mode)) { *res |= RPMVERIFY_RDEV; } else if (S_ISDEV(fmode) && S_ISDEV(sb.st_mode)) { rpm_rdev_t st_rdev = (sb.st_rdev & 0xffff); rpm_rdev_t frdev = (rpmfiFRdev(fi) & 0xffff); if (st_rdev != frdev) *res |= RPMVERIFY_RDEV; } } #if WITH_CAP if (flags & RPMVERIFY_CAPS) { /* * Empty capability set ("=") is not exactly the same as no * capabilities at all but suffices for now... */ cap_t cap, fcap; cap = cap_from_text(rpmfiFCaps(fi)); if (!cap) { cap = cap_from_text("="); } fcap = cap_get_file(fn); if (!fcap) { fcap = cap_from_text("="); } if (cap_compare(cap, fcap) != 0) *res |= RPMVERIFY_CAPS; cap_free(fcap); cap_free(cap); } #endif if ((flags & RPMVERIFY_MTIME) && (sb.st_mtime != rpmfiFMtime(fi))) { /* Filter out timestamp differences of shared files */ rpmdbMatchIterator mi = rpmtsInitIterator(ts, RPMTAG_BASENAMES, fn, 0); if (rpmdbGetIteratorCount(mi) < 2) *res |= RPMVERIFY_MTIME; rpmdbFreeIterator(mi); } if (flags & RPMVERIFY_USER) { const char * name = uidToUname(sb.st_uid); const char * fuser = rpmfiFUser(fi); if (name == NULL || fuser == NULL || strcmp(name, fuser)) *res |= RPMVERIFY_USER; } if (flags & RPMVERIFY_GROUP) { const char * name = gidToGname(sb.st_gid); const char * fgroup = rpmfiFGroup(fi); if (name == NULL || fgroup == NULL || strcmp(name, fgroup)) *res |= RPMVERIFY_GROUP; } return 0; }
// Given the path to this program, fetch its configured capability set // (as set by `setcap ... /path/to/file`) and raise those capabilities // into the Ambient set. static int make_caps_ambient(const char *selfPath) { cap_t caps = cap_get_file(selfPath); if(!caps) { if(getenv(wrapperDebug)) fprintf(stderr, "no caps set or could not retrieve the caps for this file, not doing anything..."); return 1; } // We use `cap_to_text` and iteration over the tokenized result // string because, as of libcap's current release, there is no // facility for retrieving an array of `cap_value_t`'s that can be // given to `prctl` in order to lift that capability into the // Ambient set. // // Some discussion was had around shot-gunning all of the // capabilities we know about into the Ambient set but that has a // security smell and I deemed the risk of the current // implementation crashing the program to be lower than the risk // of a privilege escalation security hole being introduced by // raising all capabilities, even ones we didn't intend for the // program, into the Ambient set. // // `cap_t` which is returned by `cap_get_*` is an opaque type and // even if we could retrieve the bitmasks (which, as far as I can // tell we cannot) in order to get the `cap_value_t` // representation for each capability we would have to take the // total number of capabilities supported and iterate over the // sequence of integers up-to that maximum total, testing each one // against the bitmask ((bitmask >> n) & 1) to see if it's set and // aggregating each "capability integer n" that is set in the // bitmask. // // That, combined with the fact that we can't easily get the // bitmask anyway seemed much more brittle than fetching the // `cap_t`, transforming it into a textual representation, // tokenizing the string, and using `cap_from_name` on the token // to get the `cap_value_t` that we need for `prctl`. There is // indeed risk involved if the output string format of // `cap_to_text` ever changes but at this time the combination of // factors involving the below list have led me to the conclusion // that the best implementation at this time is reading then // parsing with *lots of documentation* about why we're doing it // this way. // // 1. No explicit API for fetching an array of `cap_value_t`'s or // for transforming a `cap_t` into such a representation // 2. The risk of a crash is lower than lifting all capabilities // into the Ambient set // 3. libcap is depended on heavily in the Linux ecosystem so // there is a high chance that the output representation of // `cap_to_text` will not change which reduces our risk that // this parsing step will cause a crash // // The preferred method, should it ever be available in the // future, would be to use libcap API's to transform the result // from a `cap_get_*` into an array of `cap_value_t`'s that can // then be given to prctl. // // - Parnell ssize_t capLen; char* capstr = cap_to_text(caps, &capLen); cap_free(caps); // TODO: For now, we assume that cap_to_text always starts its // result string with " =" and that the first capability is listed // immediately after that. We should verify this. assert(capLen >= 2); capstr += 2; char* saveptr = NULL; for(char* tok = strtok_r(capstr, ",", &saveptr); tok; tok = strtok_r(NULL, ",", &saveptr)) { cap_value_t capnum; if (cap_from_name(tok, &capnum)) { if(getenv(wrapperDebug)) fprintf(stderr, "cap_from_name failed, skipping: %s", tok); } else if (capnum == CAP_SETPCAP) { // Check for the cap_setpcap capability, we set this on the // wrapper so it can elevate the capabilities to the Ambient // set but we do not want to propagate it down into the // wrapped program. // // TODO: what happens if that's the behavior you want // though???? I'm preferring a strict vs. loose policy here. if(getenv(wrapperDebug)) fprintf(stderr, "cap_setpcap in set, skipping it\n"); } else { set_ambient_cap(capnum); if(getenv(wrapperDebug)) fprintf(stderr, "raised %s into the Ambient capability set\n", tok); } } cap_free(capstr); return 0; }