/* * BRLTTY - A background process providing access to the console screen (when in * text mode) for a blind person using a refreshable braille display. * * Copyright (C) 1995-2021 by The BRLTTY Developers. * * BRLTTY comes with ABSOLUTELY NO WARRANTY. * * This is free software, placed under the terms of the * GNU Lesser General Public License, as published by the Free Software * Foundation; either version 2.1 of the License, or (at your option) any * later version. Please see the file LICENSE-LGPL for details. * * Web Page: http://brltty.app/ * * This software is maintained by Dave Mielke . */ #include "prologue.h" #include #include #include #include "log.h" #include "strfmt.h" #include "pgmprivs.h" #include "system_linux.h" #include "file.h" #include "parse.h" #define SCF_LOG_LEVEL LOG_DEBUG #define SCF_LOG_PROGRAM 0 //#undef HAVE_PWD_H //#undef HAVE_GRP_H //#undef HAVE_SYS_PRCTL_H //#undef HAVE_SYS_CAPABILITY_H //#undef HAVE_LIBCAP //#undef HAVE_SCHED_H //#undef HAVE_LINUX_AUDIT_H //#undef HAVE_LINUX_FILTER_H //#undef HAVE_LINUX_SECCOMP_H #ifdef HAVE_SYS_PRCTL_H #include #endif /* HAVE_SYS_PRCTL_H */ #ifdef HAVE_PWD_H #include #endif /* HAVE_PWD_H */ #ifdef HAVE_GRP_H #include #endif /* HAVE_GRP_H */ #ifdef HAVE_LIBCAP #ifdef HAVE_SYS_CAPABILITY_H #include static STR_BEGIN_FORMATTER(formatCapabilityName, cap_value_t capability) { char *name = cap_to_name(capability); if (name) { STR_PRINTF("%s", name); cap_free(name); } } if (!STR_LENGTH) STR_PRINTF("CAP#%d", capability); STR_END_FORMATTER #define MAKE_CAPABILITY_NAME(buffer,capability) \ char buffer[0X20]; \ STR_BEGIN(buffer, sizeof(buffer)); \ STR_FORMAT(formatCapabilityName, capability); \ STR_END; static int hasCapability (cap_t caps, cap_flag_t set, cap_value_t capability) { cap_flag_value_t value; if (cap_get_flag(caps, capability, set, &value) != -1) return value == CAP_SET; logSystemError("cap_get_flag"); return 0; } static int setCapabilities (cap_t caps) { if (cap_set_proc(caps) != -1) return 1; logSystemError("cap_set_proc"); return 0; } static int addCapability (cap_t caps, cap_flag_t set, cap_value_t capability) { if (cap_set_flag(caps, set, 1, &capability, CAP_SET) != -1) return 1; logSystemError("cap_set_flag"); return 0; } static int requestCapability (cap_t caps, cap_value_t capability, int inheritable) { if (!hasCapability(caps, CAP_EFFECTIVE, capability)) { if (!hasCapability(caps, CAP_PERMITTED, capability)) { MAKE_CAPABILITY_NAME(nameBuffer, capability); logMessage(LOG_DEBUG, "capability not permitted: %s", nameBuffer); return 0; } if (!addCapability(caps, CAP_EFFECTIVE, capability)) return 0; if (!inheritable) return setCapabilities(caps); } else if (!inheritable) { return 1; } if (!hasCapability(caps, CAP_INHERITABLE, capability)) { if (!addCapability(caps, CAP_INHERITABLE, capability)) { return 0; } } if (setCapabilities(caps)) { #ifdef PR_CAP_AMBIENT_RAISE if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, capability, 0, 0) != -1) return 1; logSystemError("prctl[PR_CAP_AMBIENT_RAISE]"); #else /* PR_CAP_AMBIENT_RAISE */ logMessage(LOG_WARNING, "can't raise ambient capabilities"); #endif /* PR_CAP_AMBIENT_RAISE */ } return 0; } static int needCapability (cap_value_t capability, int inheritable, const char *reason) { int haveCapability = 0; const char *outcome = NULL; cap_t caps; if ((caps = cap_get_proc())) { if (hasCapability(caps, CAP_EFFECTIVE, capability)) { haveCapability = 1; outcome = "already added"; } else if (requestCapability(caps, capability, inheritable)) { haveCapability = 1; outcome = "added"; } else { outcome = "not granted"; } cap_free(caps); } else { logSystemError("cap_get_proc"); } if (outcome) { MAKE_CAPABILITY_NAME(nameBuffer, capability); logMessage(LOG_DEBUG, "temporary capability %s: %s (%s)", outcome, nameBuffer, reason ); } return haveCapability; } #endif /* HAVE_SYS_CAPABILITY_H */ #endif /* HAVE_LIBCAP */ #if defined(HAVE_GRP_H) || defined(HAVE_PWD_H) static int canSetSupplementaryGroups (const char *reason) { #ifdef CAP_SETGID if (needCapability(CAP_SETGID, 0, reason)) { return 1; } #endif /* CAP_SETGID */ return 0; } #endif /* defined(HAVE_GRP_H) || defined(HAVE_PWD_H) */ static int amPrivilegedUser (void) { return !geteuid(); } typedef struct { const char *reason; int (*install) (void); } KernelModuleEntry; static const KernelModuleEntry kernelModuleTable[] = { { .reason = "for playing alert tunes via the built-in PC speaker", .install = installSpeakerModule, }, { .reason = "for creating virtual devices", .install = installUinputModule, }, }; static const uint8_t kernelModuleCount = ARRAY_COUNT(kernelModuleTable); static void installKernelModules (int stayPrivileged) { const KernelModuleEntry *kme = kernelModuleTable; const KernelModuleEntry *end = kme + kernelModuleCount; while (kme < end) { kme->install(); kme += 1; } } #ifdef HAVE_GRP_H typedef struct { const char *message; const gid_t *groups; size_t count; } GroupsLogData; static size_t groupsLogFormatter (char *buffer, size_t size, const void *data) { const GroupsLogData *gld = data; size_t length; STR_BEGIN(buffer, size); STR_PRINTF("%s:", gld->message); const gid_t *gid = gld->groups; const gid_t *end = gid + gld->count; while (gid < end) { STR_PRINTF(" %d", *gid); const struct group *grp = getgrgid(*gid); if (grp) STR_PRINTF("(%s)", grp->gr_name); gid += 1; } length = STR_LENGTH; STR_END; return length; } static void logGroups (int level, const char *message, const gid_t *groups, size_t count) { GroupsLogData gld = { .message = message, .groups = groups, .count = count }; logData(level, groupsLogFormatter, &gld); } static void logGroup (int level, const char *message, gid_t group) { logGroups(level, message, &group, 1); } typedef struct { const char *reason; const char *name; const char *path; unsigned char needRead:1; unsigned char needWrite:1; } RequiredGroupEntry; static const RequiredGroupEntry requiredGroupTable[] = { { .reason = "for reading screen content", .name = "tty", .path = "/dev/vcs1", }, { .reason = "for virtual console monitoring and control", .name = "tty", .path = "/dev/tty1", }, { .reason = "for serial I/O", .path = "/dev/ttyS0", }, { .reason = "for USB I/O via USBFS", .path = "/dev/bus/usb", }, { .reason = "for playing sound via the ALSA framework", .name = "audio", .path = "/dev/snd/seq", }, { .reason = "for playing sound via the Pulse Audio daemon", .name = "pulse-access", }, { .reason = "for monitoring keyboard input", .name = "input", .path = "/dev/input/mice", }, { .reason = "for creating virtual devices", .path = "/dev/uinput", .needRead = 1, .needWrite = 1, }, { .reason = "for reading BrlAPI's authorization key file", .path = BRLAPI_ETCDIR "/" BRLAPI_AUTHKEYFILE, .needRead = 1, }, }; static const uint8_t requiredGroupCount = ARRAY_COUNT(requiredGroupTable); static void processRequiredGroups (GroupsProcessor *processGroups, int logProblems, void *data) { gid_t groups[requiredGroupCount * 2]; size_t count = 0; { const RequiredGroupEntry *rge = requiredGroupTable; const RequiredGroupEntry *end = rge + requiredGroupCount; while (rge < end) { { const char *name = rge->name; if (name) { const struct group *grp; if ((grp = getgrnam(name))) { groups[count++] = grp->gr_gid; } else if (logProblems) { logMessage(LOG_DEBUG, "unknown group: %s", name); } } } { const char *path = rge->path; if (path) { struct stat status; if (stat(path, &status) != -1) { groups[count++] = status.st_gid; if (logProblems) { if (rge->needRead && !(status.st_mode & S_IRGRP)) { logMessage(LOG_DEBUG, "path not group readable: %s", path); } if (rge->needWrite && !(status.st_mode & S_IWGRP)) { logMessage(LOG_DEBUG, "path not group writable: %s", path); } } } else if (logProblems) { logMessage(LOG_DEBUG, "path access error: %s: %s", path, strerror(errno)); } } } rge += 1; } } removeDuplicateGroups(groups, &count); processGroups(groups, count, data); } typedef struct { const gid_t *groups; size_t count; } CurrentGroupsData; static void setSupplementaryGroups (const gid_t *groups, size_t count, void *data) { if (haveSupplementaryGroups(groups, count)) return; const CurrentGroupsData *cgd = data; size_t total = count; if (cgd) total += cgd->count; gid_t buffer[total]; if (cgd && (cgd->count > 0)) { gid_t *gid = buffer; gid = mempcpy(gid, groups, ARRAY_SIZE(groups, count)); if (cgd) { gid = mempcpy(gid, cgd->groups, ARRAY_SIZE(cgd->groups, cgd->count)); } count = gid - buffer; removeDuplicateGroups(buffer, &count); groups = buffer; } if (canSetSupplementaryGroups("for joining the required groups")) { logGroups(LOG_DEBUG, "setting supplementary groups", groups, count); if (setgroups(count, groups) == -1) { logSystemError("setgroups"); } } else { logMessage(LOG_WARNING, "can't set supplementary groups"); } } static void joinRequiredGroups (int stayPrivileged) { const int logProblems = 1; #ifdef HAVE_PWD_H if (stayPrivileged || !amPrivilegedUser()) { uid_t uid = geteuid(); const struct passwd *pwd = getpwuid(uid); if (pwd) { const char *user = pwd->pw_name; gid_t group = pwd->pw_gid; int count = 0; getgrouplist(user, group, NULL, &count); count += 1; // allow for the primary group gid_t groups[count]; if (getgrouplist(user, group, groups, &count) != -1) { size_t size = count; removeDuplicateGroups(groups, &size); CurrentGroupsData cgd = { .groups = groups, .count = size }; processRequiredGroups(setSupplementaryGroups, logProblems, &cgd); return; } else { logSystemError("getgrouplist"); } } } #endif /* HAVE_PWD_H */ processRequiredGroups(setSupplementaryGroups, logProblems, NULL); } static void logUnjoinedGroups (const gid_t *groups, size_t count, void *data) { const CurrentGroupsData *cgd = data; const gid_t *cur = cgd->groups; const gid_t *curEnd = cur + cgd->count; const gid_t *req = groups; const gid_t *reqEnd = req + count; while (req < reqEnd) { int relation = (cur < curEnd)? compareGroups(*cur, *req): 1; if (relation > 0) { logGroup(LOG_WARNING, "group not joined", *req++); } else { if (!relation) req += 1; cur += 1; } } } static void logWantedGroups (const gid_t *groups, size_t count, void *data) { CurrentGroupsData cgd = { .groups = groups, .count = count }; processRequiredGroups(logUnjoinedGroups, 0, &cgd); } static void logMissingGroups (void) { processSupplementaryGroups(logWantedGroups, NULL); } static void closeGroupsDatabase (void) { endgrent(); } #endif /* HAVE_GRP_H */ #ifdef CAP_IS_SUPPORTED typedef struct { const char *label; cap_t caps; } CapabilitiesLogData; static size_t capabilitiesLogFormatter (char *buffer, size_t size, const void *data) { const CapabilitiesLogData *cld = data; size_t length; STR_BEGIN(buffer, size); STR_PRINTF("capabilities: %s:", cld->label); int capsAllocated = 0; cap_t caps; if (!(caps = cld->caps)) { if (!(caps = cap_get_proc())) { logSystemError("cap_get_proc"); goto done; } capsAllocated = 1; } { char *text; if ((text = cap_to_text(caps, NULL))) { STR_PRINTF(" %s", text); cap_free(text); } else { logSystemError("cap_to_text"); } } if (capsAllocated) { cap_free(caps); caps = NULL; } done: length = STR_LENGTH; STR_END; return length; } static void logCapabilities (cap_t caps, const char *label) { CapabilitiesLogData cld = { .label=label, .caps=caps }; logData(LOG_DEBUG, capabilitiesLogFormatter, &cld); } static void logCurrentCapabilities (const char *label) { logCapabilities(NULL, label); } typedef struct { const char *reason; cap_value_t value; } RequiredCapabilityEntry; static const RequiredCapabilityEntry requiredCapabilityTable[] = { { .reason = "for injecting input characters typed on a braille device", .value = CAP_SYS_ADMIN, }, { .reason = "for playing alert tunes via the built-in PC speaker", .value = CAP_SYS_TTY_CONFIG, }, { .reason = "for creating needed but missing special device files", .value = CAP_MKNOD, }, }; static const uint8_t requiredCapabilityCount = ARRAY_COUNT(requiredCapabilityTable); static void setRequiredCapabilities (int stayPrivileged) { cap_t newCaps, oldCaps; if (amPrivilegedUser()) { oldCaps = NULL; } else if (!(oldCaps = cap_get_proc())) { logSystemError("cap_get_proc"); return; } { cap_t (*function) (void); const char *name; if (stayPrivileged) { function = cap_get_proc; name = "cap_get_proc"; } else { function = cap_init; name = "cap_init"; } if (!(newCaps = function())) logSystemError(name); } if (newCaps) { { const RequiredCapabilityEntry *rce = requiredCapabilityTable; const RequiredCapabilityEntry *end = rce + requiredCapabilityCount; while (rce < end) { cap_value_t capability = rce->value; if (!oldCaps || hasCapability(oldCaps, CAP_PERMITTED, capability)) { if (!addCapability(newCaps, CAP_PERMITTED, capability)) break; if (!addCapability(newCaps, CAP_EFFECTIVE, capability)) break; } rce += 1; } } setCapabilities(newCaps); cap_free(newCaps); } #ifdef PR_CAP_AMBIENT_CLEAR_ALL if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL, 0, 0, 0) == -1) { logSystemError("prctl[PR_CAP_AMBIENT_CLEAR_ALL]"); } #else /* PR_CAP_AMBIENT_CLEAR_ALL */ logMessage(LOG_WARNING, "can't clear ambient capabilities"); #endif /* PR_CAP_AMBIENT_CLEAR_ALL */ if (oldCaps) cap_free(oldCaps); } static void logMissingCapabilities (void) { cap_t caps; if ((caps = cap_get_proc())) { const RequiredCapabilityEntry *rce = requiredCapabilityTable; const RequiredCapabilityEntry *end = rce + requiredCapabilityCount; while (rce < end) { cap_value_t capability = rce->value; if (!hasCapability(caps, CAP_EFFECTIVE, capability)) { MAKE_CAPABILITY_NAME(nameBuffer, capability); logMessage(LOG_WARNING, "required capability not granted: %s (%s)", nameBuffer, rce->reason ); } rce += 1; } cap_free(caps); } else { logSystemError("cap_get_proc"); } } #else /* CAP_IS_SUPPORTED */ static void logCurrentCapabilities (const char *label) { } #endif /* CAP_IS_SUPPORTED */ #ifdef HAVE_SCHED_H #include typedef struct { const char *name; const char *summary; int unshareFlag; } IsolatedNamespaceEntry; static const IsolatedNamespaceEntry isolatedNamespaceTable[] = { #ifdef CLONE_NEWCGROUP { .unshareFlag = CLONE_NEWCGROUP, .name = "cgroup", .summary = "control groups", }, #endif /* CLONE_NEWCGROUP */ #ifdef CLONE_NEWNS { .unshareFlag = CLONE_NEWNS, .name = "mount", .summary = "mount points", }, #endif /* CLONE_NEWNS */ #ifdef CLONE_NEWUTS { .unshareFlag = CLONE_NEWUTS, .name = "UTS", .summary = "host name and NIS domain name", }, #endif /* CLONE_NEWUTS */ }; static const uint8_t isolatedNamespaceCount = ARRAY_COUNT(isolatedNamespaceTable); static void isolateNamespaces (void) { int canIsolateNamespaces = 0; #ifdef CAP_SYS_ADMIN if (needCapability(CAP_SYS_ADMIN, 0, "for isolating namespaces")) { canIsolateNamespaces = 1; } #endif /* CAP_SYS_ADMIN */ if (canIsolateNamespaces) { int unshareFlags = 0; const IsolatedNamespaceEntry *ine = isolatedNamespaceTable; const IsolatedNamespaceEntry *end = ine + isolatedNamespaceCount; while (ine < end) { logMessage(LOG_DEBUG, "isolating namespace: %s (%s)", ine->name, ine->summary ); unshareFlags |= ine->unshareFlag; ine += 1; } if (unshare(unshareFlags) == -1) { logSystemError("unshare"); } } else { logMessage(LOG_WARNING, "can't isolate namespaces"); } } #endif /* HAVE_SCHED_H */ #ifdef HAVE_LINUX_AUDIT_H #include #if defined(__i386__) #define SYSTEM_CALL_ARCHITECTURE AUDIT_ARCH_I386 #elif defined(__x86_64__) #define SYSTEM_CALL_ARCHITECTURE AUDIT_ARCH_X86_64 #else /* system call architecture */ #warning system call architecture not known for this platform #endif /* system call architecture */ #endif /* HAVE_LINUX_AUDIT_H */ #ifdef SYSTEM_CALL_ARCHITECTURE #ifdef HAVE_LINUX_FILTER_H #include #ifdef HAVE_LINUX_SECCOMP_H #include #endif /* HAVE_LINUX_SECCOMP_H */ #endif /* HAVE_LINUX_FILTER_H */ #endif /* SYSTEM_CALL_ARCHITECTURE */ #ifdef SECCOMP_MODE_FILTER static const char scfLogLabel[] = "SCF"; typedef struct SCFArgumentDescriptorStruct SCFArgumentDescriptor; // best to protect each of these with an #ifdef for the value's macro typedef struct { // required - must be first uint32_t value; // optional - use SCF_ARGUMENT(index, group) const SCFArgumentDescriptor *argument; } SCFValueDescriptor; typedef struct { const char *name; const SCFValueDescriptor *descriptors; size_t count; } SCFValueGroup; #define SCF_VALUE_GROUP(group) { \ .name = #group, \ .descriptors = group##Values, \ .count = ARRAY_COUNT(group##Values), \ } struct SCFArgumentDescriptorStruct { SCFValueGroup values; uint8_t index; }; #define SCF_ARGUMENT(n, group) \ .argument = &(const SCFArgumentDescriptor){ \ .values = SCF_VALUE_GROUP(group), \ .index = (n) \ } #define SCF_BEGIN_VALUES(group) static const SCFValueDescriptor group##Values[] = { #define SCF_END_VALUES }; #include "syscalls_linux.h" static const SCFValueGroup scfSystemCalls = SCF_VALUE_GROUP(systemCall); typedef struct SCFJumpStruct SCFJump; typedef enum { SCF_JUMP_ALWAYS, SCF_JUMP_TRUE, SCF_JUMP_FALSE } SCFJumpType; struct SCFJumpStruct { SCFJump *next; size_t location; SCFJumpType type; }; typedef struct { const SCFArgumentDescriptor *descriptor; SCFJump jump; } SCFArgument; #define SCF_INSTRUCTION(code, k) \ (const struct sock_filter)BPF_STMT((code), (k)) #define SCF_RETURN_INSTRUCTION(action, value) \ SCF_INSTRUCTION(BPF_RET|BPF_K, (SECCOMP_RET_##action | ((value) & SECCOMP_RET_DATA))) typedef struct { const char *name; // must be first const struct sock_filter *deny; } SCFMode; static const SCFMode scfModes[] = { // must be first { .name = "no", }, #ifdef SECCOMP_RET_LOG { .name = "log", .deny = &SCF_RETURN_INSTRUCTION(LOG, 0) }, #endif /* SECCOMP_RET_LOG */ #ifdef SECCOMP_RET_ERRNO { .name = "fail", .deny = &SCF_RETURN_INSTRUCTION(ERRNO, EPERM) }, #endif /* SECCOMP_RET_ERRNO */ #ifdef SECCOMP_RET_KILL_PROCESS { .name = "kill", .deny = &SCF_RETURN_INSTRUCTION(KILL_PROCESS, 0) }, #endif /* SECCOMP_RET_KILL_PROCESS */ // must be last { .name = NULL } }; static const SCFMode * scfGetMode (const char *name) { unsigned int choice; int valid = validateChoiceEx(&choice, name, scfModes, sizeof(scfModes[0])); const SCFMode *mode = &scfModes[choice]; if (!valid) { logMessage(LOG_WARNING, "unknown system call filter mode: %s: assuming %s", name, mode->name ); } return mode; } typedef struct { const SCFMode *mode; struct { struct sock_filter *array; size_t size; size_t count; } instruction; struct { SCFArgument *array; size_t size; size_t count; } argument; struct { SCFJump *jumps; } allow; } SCFObject; static int scfAddInstruction (SCFObject *scf, const struct sock_filter *instruction) { if (scf->instruction.count == BPF_MAXINSNS) { logMessage(LOG_ERR, "system call filter too large"); return 0; } if (scf->instruction.count == scf->instruction.size) { size_t newSize = scf->instruction.size? scf->instruction.size<<1: 0X10; struct sock_filter *newArray; if (!(newArray = realloc(scf->instruction.array, ARRAY_SIZE(newArray, newSize)))) { logMallocError(); return 0; } scf->instruction.array = newArray; scf->instruction.size = newSize; } scf->instruction.array[scf->instruction.count++] = *instruction; return 1; } static int scfAddAllowInstruction (SCFObject *scf) { static const struct sock_filter allow = SCF_RETURN_INSTRUCTION(ALLOW, 0); return scfAddInstruction(scf, &allow); } static int scfAddDenyInstruction (SCFObject *scf) { return scfAddInstruction(scf, scf->mode->deny); } static int scfLoadData (SCFObject *scf, uint32_t offset, uint8_t width) { struct sock_filter instruction = BPF_STMT(BPF_LD|BPF_ABS, offset); switch (width) { case 1: instruction.code |= BPF_B; break; case 2: instruction.code |= BPF_H; break; case 4: instruction.code |= BPF_W; break; default: logMessage(LOG_WARNING, "unsupported field width: %u", width); return 0; } return scfAddInstruction(scf, &instruction); } #define SCF_DATA_OFFSET(field) (offsetof(struct seccomp_data, field)) static int scfLoadArchitecture (SCFObject *scf) { return scfLoadData(scf, SCF_DATA_OFFSET(arch), 4); } static int scfLoadSystemCall (SCFObject *scf) { return scfLoadData(scf, SCF_DATA_OFFSET(nr), 4); } static int scfLoadArgument (SCFObject *scf, uint8_t index) { return scfLoadData(scf, SCF_DATA_OFFSET(args[index]), 4); } static void scfBeginJump (SCFObject *scf, SCFJump *jump, SCFJumpType type) { memset(jump, 0, sizeof(*jump)); jump->next = NULL; jump->location = scf->instruction.count; jump->type = type; } static int scfEndJump (SCFObject *scf, const SCFJump *jump) { size_t from = jump->location; struct sock_filter *instruction = &scf->instruction.array[from]; size_t to = scf->instruction.count - from - 1; SCFJumpType type = jump->type; switch (type) { case SCF_JUMP_ALWAYS: instruction->k = to; break; case SCF_JUMP_TRUE: instruction->jt = to; break; case SCF_JUMP_FALSE: instruction->jf = to; break; default: logMessage(LOG_WARNING, "unsupported jump type: %u", type); return 0; } return 1; } static void scfPushJump (SCFJump **jumps, SCFJump *jump) { jump->next = *jumps; *jumps = jump; } static SCFJump * scfPopJump (SCFJump **jumps) { SCFJump *jump = *jumps; if (jump) { *jumps = jump->next; jump->next = NULL; } return jump; } static int scfEndJumps (SCFObject *scf, SCFJump **jumps) { int ok = 1; while (1) { SCFJump *jump = scfPopJump(jumps); if (!jump) break; if (!scfEndJump(scf, jump)) ok = 0; free(jump); } return ok; } static int scfJumpTo (SCFObject *scf, SCFJump *jump) { struct sock_filter instruction = BPF_STMT(BPF_JMP|BPF_K|BPF_JA, 0); scfBeginJump(scf, jump, SCF_JUMP_ALWAYS); return scfAddInstruction(scf, &instruction); } typedef enum { SCF_TEST_NE, SCF_TEST_LT, SCF_TEST_LE, SCF_TEST_EQ, SCF_TEST_GE, SCF_TEST_GT, } SCFTest; static int scfJumpIf (SCFObject *scf, SCFTest test, uint32_t value, SCFJump *jump) { struct sock_filter instruction = BPF_STMT(BPF_JMP|BPF_K, value); int invert = 0; switch (test) { case SCF_TEST_NE: invert = 1; case SCF_TEST_EQ: instruction.code |= BPF_JEQ; break; case SCF_TEST_LT: invert = 1; case SCF_TEST_GE: instruction.code |= BPF_JGE; break; case SCF_TEST_LE: invert = 1; case SCF_TEST_GT: instruction.code |= BPF_JGT; break; default: logMessage(LOG_WARNING, "unsupported value test: %u", test); return 0; } scfBeginJump(scf, jump, (invert? SCF_JUMP_FALSE: SCF_JUMP_TRUE)); return scfAddInstruction(scf, &instruction); } static int scfVerifyArchitecture (SCFObject *scf) { SCFJump eqArch; return scfLoadArchitecture(scf) && scfJumpIf(scf, SCF_TEST_EQ, SYSTEM_CALL_ARCHITECTURE, &eqArch) && scfAddDenyInstruction(scf) && scfEndJump(scf, &eqArch); } static int scfJumpToArgument (SCFObject *scf, const SCFArgumentDescriptor *descriptor) { if (scf->argument.count == scf->argument.size) { size_t newSize = scf->argument.size? scf->argument.size<<1: 0X10; SCFArgument *newArray; if (!(newArray = realloc(scf->argument.array, ARRAY_SIZE(newArray, newSize)))) { logMallocError(); return 0; } scf->argument.array = newArray; scf->argument.size = newSize; } { size_t *count = &scf->argument.count; SCFArgument *argument = &scf->argument.array[*count]; argument->descriptor = descriptor; if (!scfJumpTo(scf, &argument->jump)) return 0; *count += 1; } return 1; } static int scfAllowValue (SCFObject *scf, const SCFValueDescriptor *descriptor) { if (descriptor->argument) { SCFJump neValue; return scfJumpIf(scf, SCF_TEST_NE, descriptor->value, &neValue) && scfJumpToArgument(scf, descriptor->argument) && scfEndJump(scf, &neValue); } else { SCFJump *eqValue; if ((eqValue = malloc(sizeof(*eqValue)))) { if (scfJumpIf(scf, SCF_TEST_EQ, descriptor->value, eqValue)) { scfPushJump(&scf->allow.jumps, eqValue); return 1; } free(eqValue); } else { logMallocError(); } } return 0; } static int scfAllowValues (SCFObject *scf, const SCFValueDescriptor *descriptors, size_t count) { if (count <= 3) { const SCFValueDescriptor *descriptor = descriptors; const SCFValueDescriptor *end = descriptor + count; while (descriptor < end) { if (!scfAllowValue(scf, descriptor)) return 0; descriptor += 1; } return scfAddDenyInstruction(scf); } const SCFValueDescriptor *descriptor = descriptors + (count / 2); SCFJump gtValue; if (!scfJumpIf(scf, SCF_TEST_GT, descriptor->value, >Value)) return 0; if (!scfAllowValue(scf, descriptor)) return 0; if (!scfAllowValues(scf, descriptors, (descriptor - descriptors))) return 0; if (!scfEndJump(scf, >Value)) return 0; const SCFValueDescriptor *end = descriptors + count; descriptor += 1; return scfAllowValues(scf, descriptor, (end - descriptor)); } static int scfValueSorter (const void *element1, const void *element2) { const SCFValueDescriptor *descriptor1 = element1; const SCFValueDescriptor *descriptor2 = element2; if (descriptor1->value < descriptor2->value) return -1; if (descriptor1->value > descriptor2->value) return 1; return 0; } static void scfSortValues (SCFValueDescriptor *values, size_t count) { qsort(values, count, sizeof(*values), scfValueSorter); } static void scfRemoveDuplicateValues (SCFValueDescriptor *values, size_t *count, const char *name) { if (*count > 1) { SCFValueDescriptor *to = values; const SCFValueDescriptor *from = values + 1; const SCFValueDescriptor *end = values + *count; while (from < end) { if (from->value == to->value) { logMessage(LOG_WARNING, "%s: duplicate value: %s: 0X%08"PRIX32, scfLogLabel, name, from->value ); } else if (++to != from) { *to = *from; } from += 1; } *count = ++to - values; } } static int scfAllowValueGroup (SCFObject *scf, const SCFValueGroup *values) { { const char *name = values->name; size_t count = values->count; SCFValueDescriptor descriptors[count]; memcpy(descriptors, values->descriptors, sizeof(descriptors)); scfSortValues(descriptors, count); scfRemoveDuplicateValues(descriptors, &count, name); logMessage(SCF_LOG_LEVEL, "%s: value group size: %s: %zu", scfLogLabel, name, count ); if (!scfAllowValues(scf, descriptors, count)) return 0; } if (scf->allow.jumps) { if (!scfEndJumps(scf, &scf->allow.jumps)) return 0; if (!scfAddAllowInstruction(scf)) return 0; } return 1; } static int scfCheckSysemCall (SCFObject *scf) { return scfLoadSystemCall(scf) && scfAllowValueGroup(scf, &scfSystemCalls); } static int scfCheckArgument (SCFObject *scf, const SCFArgument *argument) { const SCFArgumentDescriptor *descriptor = argument->descriptor; return scfEndJump(scf, &argument->jump) && scfLoadArgument(scf, descriptor->index) && scfAllowValueGroup(scf, &descriptor->values); } static int scfCheckArguments (SCFObject *scf) { /* An argument's value group can include the specification of another * argument and its associated value group. The following iteration, * therefore, needs to handle the possibility that the pending argument * count may increase as each argument is processed. */ while (scf->argument.count > 0) { if (!scfCheckArgument(scf, &scf->argument.array[--scf->argument.count])) { return 0; } } return 1; } #if SCF_LOG_PROGRAM typedef struct { const struct sock_filter *instruction; size_t location; struct { int decimal; int hexadecimal; } width; } SCFInstructionFormattingData; static STR_BEGIN_FORMATTER(scfFormatLocation, size_t location, const SCFInstructionFormattingData *ifd) STR_PRINTF("X%0*zX", ifd->width.hexadecimal, location); STR_END_FORMATTER static STR_BEGIN_FORMATTER(scfDisassembleInstruction, const SCFInstructionFormattingData *ifd) const struct sock_filter *instruction = ifd->instruction; uint16_t code = instruction->code; uint32_t operand = instruction->k; const char *name = NULL; int hasSize = 0; int hasMode = 0; int hasSource = 0; int isJump = 0; int isReturn = 0; int problem = 0; switch (BPF_CLASS(code)) { case BPF_LD: name = "ld"; hasSize = 1; hasMode = 1; break; case BPF_LDX: name = "ldx"; hasSize = 1; hasMode = 1; break; case BPF_ST: name = "st"; hasSize = 1; hasMode = 1; break; case BPF_STX: name = "stx"; hasSize = 1; hasMode = 1; break; case BPF_ALU: switch (BPF_OP(code)) { case BPF_ADD: name = "add"; break; case BPF_SUB: name = "sub"; break; case BPF_MUL: name = "mul"; break; case BPF_DIV: name = "div"; break; case BPF_MOD: name = "mod"; break; case BPF_LSH: name = "lsh"; break; case BPF_RSH: name = "rsh"; break; case BPF_AND: name = "and"; break; case BPF_OR: name = "or"; break; case BPF_XOR: name = "xor"; break; case BPF_NEG: name = "neg"; break; default: name = "alu"; problem = 1; break; } hasSource = 1; break; case BPF_JMP: switch (BPF_OP(code)) { case BPF_JEQ: name = "jeq"; break; case BPF_JGT: name = "jgt"; break; case BPF_JGE: name = "jge"; break; case BPF_JSET: name = "jseT"; break; default: problem = 1; /* fall through */ case BPF_JA: name = "jmp"; break; } hasSource = 1; isJump = 1; break; case BPF_RET: name = "ret"; isReturn = 1; break; default: problem = 1; break; } if (name) STR_PRINTF("%s", name); if (hasSize) { const char *size = NULL; switch (BPF_SIZE(code)) { case BPF_B: size = "b"; break; case BPF_H: size = "h"; break; case BPF_W: size = "w"; break; default: problem = 1; break; } if (size) STR_PRINTF("%s", size); } if (hasMode) { const char *mode = NULL; switch (BPF_MODE(code)) { case BPF_IMM: mode = "imm"; break; case BPF_ABS: mode = "abs"; break; case BPF_IND: mode = "ind"; break; case BPF_MEM: mode = "mem"; break; case BPF_LEN: mode = "len"; break; default: problem = 1; break; } if (mode) STR_PRINTF("-%s", mode); } if (hasSource) { const char *source = NULL; switch (BPF_SRC(code)) { case BPF_K: source = "k"; break; case BPF_X: source = "x"; break; default: problem = 1; break; } if (source) STR_PRINTF("-%s", source); } if (isReturn) { const char *action = NULL; switch (operand & SECCOMP_RET_ACTION_FULL) { case SECCOMP_RET_KILL_PROCESS: action = "kill-process"; break; case SECCOMP_RET_KILL_THREAD: action = "kill-thread"; break; case SECCOMP_RET_TRAP: action = "trap"; break; case SECCOMP_RET_ERRNO: action = "errno"; break; case SECCOMP_RET_USER_NOTIF: action = "notify"; break; case SECCOMP_RET_TRACE: action = "trace"; break; case SECCOMP_RET_LOG: action = "log"; break; case SECCOMP_RET_ALLOW: action = "allow"; break; default: break; } if (action) { STR_PRINTF("-%s", action); uint16_t data = operand & SECCOMP_RET_DATA; if (data) STR_PRINTF("(%u)", data); } } if (problem) { STR_PRINTF("?"); } else if (isJump) { STR_PRINTF(" -> "); size_t from = ifd->location + 1; if (BPF_OP(code) == BPF_JA) { STR_FORMAT(scfFormatLocation, (from + operand), ifd); } else { STR_FORMAT(scfFormatLocation, (from + instruction->jt), ifd); STR_PRINTF(" "); STR_FORMAT(scfFormatLocation, (from + instruction->jf), ifd); } } STR_END_FORMATTER static STR_BEGIN_FORMATTER(scfFormatInstruction, const SCFInstructionFormattingData *ifd) STR_FORMAT(scfFormatLocation, ifd->location, ifd); STR_PRINTF( ": %04X %08X %02X %02X: ", ifd->instruction->code, ifd->instruction->k, ifd->instruction->jt, ifd->instruction->jf ); STR_FORMAT(scfDisassembleInstruction, ifd); STR_END_FORMATTER static void scfLogProgram (SCFObject *scf) { size_t count = scf->instruction.count; SCFInstructionFormattingData ifd; memset(&ifd, 0, sizeof(ifd)); { size_t index = count; if (index > 0) index -= 1; char buffer[0X40]; ifd.width.decimal = snprintf(buffer, sizeof(buffer), "%zu", index); ifd.width.hexadecimal = snprintf(buffer, sizeof(buffer), "%zx", index); } for (size_t index=0; indexinstruction.array[index]; ifd.location = index; STR_FORMAT(scfFormatInstruction, &ifd); STR_END; logMessage(SCF_LOG_LEVEL, "%s", log); } } #endif /* SCF_LOG_PROGRAM */ static void scfDestroyJumps (SCFJump **jumps) { while (1) { SCFJump *jump = scfPopJump(jumps); if (!jump) break; free(jump); } } static void scfDestroyObject (SCFObject *scf) { scfDestroyJumps(&scf->allow.jumps); if (scf->instruction.array) free(scf->instruction.array); if (scf->argument.array) free(scf->argument.array); free(scf); } static SCFObject * scfNewObject (const SCFMode *mode) { SCFObject *scf; if ((scf = malloc(sizeof(*scf)))) { memset(scf, 0, sizeof(*scf)); scf->mode = mode; scf->instruction.array = NULL; scf->instruction.size = 0; scf->instruction.count = 0; scf->argument.array = NULL; scf->argument.size = 0; scf->argument.count = 0; scf->allow.jumps = NULL; return scf; } else { logMallocError(); } return NULL; } static SCFObject * scfMakeFilter (const SCFMode *mode) { SCFObject *scf; if ((scf = scfNewObject(mode))) { if (scfVerifyArchitecture(scf)) { if (scfCheckSysemCall(scf)) { if (scfCheckArguments(scf)) { logMessage(SCF_LOG_LEVEL, "%s: program size: %zu", scfLogLabel, scf->instruction.count ); #if SCF_LOG_PROGRAM logMessage(SCF_LOG_LEVEL, "%s: begin program", scfLogLabel); scfLogProgram(scf); logMessage(SCF_LOG_LEVEL, "%s: end program", scfLogLabel); #endif /* SCF_LOG_PROGRAM */ return scf; } } } scfDestroyObject(scf); } return NULL; } static void scfInstallFilter (const char *modeName) { const SCFMode *mode = scfGetMode(modeName); if (!mode->deny) return; SCFObject *scf; #ifdef PR_SET_NO_NEW_PRIVS if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) { logSystemError("prctl[PR_SET_NO_NEW_PRIVS]"); } #endif /* PR_SET_NO_NEW_PRIVS */ if ((scf = scfMakeFilter(mode))) { struct sock_fprog program = { .filter = scf->instruction.array, .len = scf->instruction.count }; #if defined(PR_SET_SECCOMP) if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &program, 0, 0) == -1) { logSystemError("prctl[PR_SET_SECCOMP,SECCOMP_MODE_FILTER]"); } #elif defined(SECCOMP_SET_MODE_FILTER) unsigned int flags = 0; if (seccomp(SECCOMP_SET_MODE_FILTER, flags, &program) == -1) { logSystemError("seccomp[SECCOMP_SET_MODE_FILTER]"); } #else /* install system call filter */ #warning no mechanism for installing the system call filter #endif /* install system call filter */ scfDestroyObject(scf); } } #endif /* SECCOMP_MODE_FILTER */ typedef void PrivilegesEstablishmentFunction (int stayPrivileged); typedef void MissingPrivilegesLogger (void); typedef void ReleaseResourcesFunction (void); typedef struct { const char *reason; PrivilegesEstablishmentFunction *establishPrivileges; MissingPrivilegesLogger *logMissingPrivileges; ReleaseResourcesFunction *releaseResources; #ifdef CAP_IS_SUPPORTED cap_value_t capability; unsigned char inheritable:1; #endif /* CAP_IS_SUPPORTED */ } PrivilegesMechanismEntry; static const PrivilegesMechanismEntry privilegesMechanismTable[] = { { .reason = "for installing kernel modules", .establishPrivileges = installKernelModules, #ifdef CAP_SYS_MODULE .capability = CAP_SYS_MODULE, .inheritable = 1, #endif /* CAP_SYS_MODULE, */ }, #ifdef HAVE_GRP_H { .reason = "for joining the required groups", .establishPrivileges = joinRequiredGroups, .logMissingPrivileges = logMissingGroups, .releaseResources = closeGroupsDatabase, }, #endif /* HAVE_GRP_H */ // This one must be last because it relinquishes the temporary capabilities. #ifdef CAP_IS_SUPPORTED { .reason = "for assigning required capabilities", .establishPrivileges = setRequiredCapabilities, .logMissingPrivileges = logMissingCapabilities, } #endif /* CAP_IS_SUPPORTED */ }; static const uint8_t privilegesMechanismCount = ARRAY_COUNT(privilegesMechanismTable); static void establishPrivileges (int stayPrivileged) { if (amPrivilegedUser()) { const PrivilegesMechanismEntry *pme = privilegesMechanismTable; const PrivilegesMechanismEntry *end = pme + privilegesMechanismCount; while (pme < end) { pme->establishPrivileges(stayPrivileged); pme += 1; } } #ifdef CAP_IS_SUPPORTED else { const PrivilegesMechanismEntry *pme = privilegesMechanismTable; const PrivilegesMechanismEntry *end = pme + privilegesMechanismCount; while (pme < end) { cap_value_t capability = pme->capability; if (!capability || needCapability(capability, pme->inheritable, pme->reason)) { pme->establishPrivileges(stayPrivileged); } pme += 1; } } #endif /* CAP_IS_SUPPORTED */ { const PrivilegesMechanismEntry *pme = privilegesMechanismTable; const PrivilegesMechanismEntry *end = pme + privilegesMechanismCount; while (pme < end) { { MissingPrivilegesLogger *log = pme->logMissingPrivileges; if (log) log(); } { ReleaseResourcesFunction *release = pme->releaseResources; if (release) release(); } pme += 1; } } } static int isEnvironmentVariableSet (const char *name) { const char *value = getenv(name); return value && *value; } static int unsetEnvironmentVariable (const char *name) { if (!isEnvironmentVariableSet(name)) return 1; if (unsetenv(name) != -1) { logMessage(LOG_DEBUG, "environment variable unset: %s", name); return 1; } else { logSystemError("unsetenv"); } return 0; } static int setEnvironmentVariable (const char *name, const char *value) { if (setenv(name, value, 1) != -1) { logMessage(LOG_DEBUG, "environment variable set: %s: %s", name, value); return 1; } else { logSystemError("setenv"); } return 0; } static int changeEnvironmentVariable (const char *name, const char *value) { if (!isEnvironmentVariableSet(name)) return 1; return setEnvironmentVariable(name, value); } static int setHomeDirectory (const char *directory) { if (!directory) return 0; if (!*directory) return 0; if (chdir(directory) != -1) { logMessage(LOG_INFO, "%s: %s", gettext("working directory changed"), directory); setEnvironmentVariable("HOME", directory); return 1; } else { logMessage(LOG_WARNING, "working directory not changed: %s: %s", directory, strerror(errno) ); } return 0; } static int setCommandSearchPath (const char *path) { const char *variable = "PATH"; if (!*path) { int parameter = _CS_PATH; size_t size = confstr(parameter, NULL, 0); if (size > 0) { char buffer[size]; confstr(parameter, buffer, sizeof(buffer)); return setEnvironmentVariable(variable, buffer); } path = "/usr/sbin:/sbin:/usr/bin:/bin"; } return setEnvironmentVariable(variable, path); } static int setDefaultShell (const char *shell) { if (!*shell) shell = "/bin/sh"; return setEnvironmentVariable("SHELL", shell); } #ifdef HAVE_PWD_H static int canSwitchGroup (gid_t gid) { { gid_t rGid, eGid, sGid; getresgid(&rGid, &eGid, &sGid); if ((gid == rGid) || (gid == eGid) || (gid == sGid)) return 1; } return canSetSupplementaryGroups("for switching to the writable group"); } static int setXDGRuntimeDirectory (uid_t uid, gid_t gid) { const char *variable = "XDG_RUNTIME_DIR"; const char *oldPath = getenv(variable); if (!oldPath) return 1; if (!*oldPath) return 1; const char *oldName = locatePathName(oldPath); if (!oldName) return 1; int length = oldName - oldPath; char newPath[length + 0X20]; snprintf(newPath, sizeof(newPath), "%.*s%d", length, oldPath, uid); { logMessage(LOG_DEBUG, "checking XDG runtime directory: %s", newPath); int exists = 0; if (access(newPath, F_OK) != -1) { exists = 1; logMessage(LOG_DEBUG, "%s: %s", gettext("XDG runtime directory exists"), newPath); } else if (errno == ENOENT) { if (mkdir(newPath, S_IRWXU) != -1) { if (chown(newPath, uid, gid) != 01) { exists = 1; logMessage(LOG_INFO, "%s: %s", gettext("XDG runtime directory created"), newPath); } else { logSystemError("chown"); } if (!exists) { if (rmdir(newPath) == -1) { logSystemError("rmdir"); } } } else { logSystemError("mkdir"); } } else { logSystemError("access"); } if (!exists) { logMessage(LOG_WARNING, "%s: %s", gettext("XDG runtime directory access problem"), newPath); } } return setEnvironmentVariable(variable, newPath); } static int setProcessOwnership (uid_t uid, gid_t gid) { if (setXDGRuntimeDirectory(uid, gid)) { gid_t oldRgid, oldEgid, oldSgid; if (getresgid(&oldRgid, &oldEgid, &oldSgid) != -1) { if (setresgid(gid, gid, gid) != -1) { if (setresuid(uid, uid, uid) != -1) { return 1; } else { logSystemError("setresuid"); } setresgid(oldRgid, oldEgid, oldSgid); } else { logSystemError("setresgid"); } } else { logSystemError("getresgid"); } } return 0; } static int switchToUser (const char *user, int *haveHomeDirectory) { const struct passwd *pwd; if ((pwd = getpwnam(user))) { uid_t uid = pwd->pw_uid; gid_t gid = pwd->pw_gid; if (!uid) { logMessage(LOG_WARNING, "not an unprivileged user: %s", user); } else if (setProcessOwnership(uid, gid)) { logMessage(LOG_NOTICE, "%s: %s", gettext("switched to unprivileged user"), user); changeEnvironmentVariable("USER", user); changeEnvironmentVariable("LOGNAME", user); unsetEnvironmentVariable("XDG_CONFIG_HOME"); unsetEnvironmentVariable("XDG_DATA_DIRS"); if (setHomeDirectory(pwd->pw_dir)) *haveHomeDirectory = 1; forgetOverrideDirectories(); return 1; } } else { logMessage(LOG_WARNING, "unprivileged user not found: %s", user); } return 0; } static int switchUser (const char *user, int stayPrivileged, int *haveHomeDirectory) { if (amPrivilegedUser()) { if (stayPrivileged) { logMessage(LOG_NOTICE, "%s", gettext("not switching to an unprivileged user")); } else if (!*user) { logMessage(LOG_DEBUG, "default unprivileged user not configured"); } else if (switchToUser(user, haveHomeDirectory)) { return 1; } else { logMessage(LOG_WARNING, "couldn't switch to the unprivileged user: %s", user); } } { uid_t uid = getuid(); gid_t gid = getgid(); { const struct passwd *pwd; const char *name; char number[0X10]; if ((pwd = getpwuid(uid))) { name = pwd->pw_name; if (canSwitchGroup(pwd->pw_gid)) gid = pwd->pw_gid; } else { snprintf(number, sizeof(number), "%d", uid); name = number; } logMessage(LOG_NOTICE, "%s: %s", gettext("executing as the invoking user"), name); } setProcessOwnership(uid, gid); if (!amPrivilegedUser()) *haveHomeDirectory = 1; } return 0; } static const char * getSocketsDirectory (void) { const char *path = BRLAPI_SOCKETPATH; if (!ensureDirectory(path, 1)) path = NULL; return path; } typedef struct { const char *whichDirectory; const char *(*getPath) (void); const char *expectedName; } StateDirectoryEntry; static const StateDirectoryEntry stateDirectoryTable[] = { { .whichDirectory = "updatable", .getPath = getUpdatableDirectory, .expectedName = "brltty", }, { .whichDirectory = "writable", .getPath = getWritableDirectory, .expectedName = "brltty", }, { .whichDirectory = "sockets", .getPath = getSocketsDirectory, .expectedName = "BrlAPI", }, }; static const uint8_t stateDirectoryCount = ARRAY_COUNT(stateDirectoryTable); static int canCreateStateDirectory (void) { #ifdef CAP_DAC_OVERRIDE if (needCapability(CAP_DAC_OVERRIDE, 0, "for creating missing state directories")) { return 1; } #endif /* CAP_DAC_OVERRIDE */ return 0; } static const char * getStateDirectoryPath (const StateDirectoryEntry *sde) { { const char *path = sde->getPath(); if (path) return path; } if (!canCreateStateDirectory()) return NULL; return sde->getPath(); } static int canChangePathOwnership (const char *path) { #ifdef CAP_CHOWN if (needCapability(CAP_CHOWN, 0, "for claiming ownership of the state directories")) { return 1; } #endif /* CAP_CHOWN */ return 0; } static int canChangePathPermissions (const char *path) { #ifdef CAP_FOWNER if (needCapability(CAP_FOWNER, 0, "for adding group permissions to the state directories")) { return 1; } #endif /* CAP_FOWNER */ return 0; } typedef struct { uid_t owningUser; gid_t owningGroup; } StateDirectoryData; static int claimStateDirectory (const PathProcessorParameters *parameters) { const StateDirectoryData *sdd = parameters->data; const char *path = parameters->path; uid_t user = sdd->owningUser; gid_t group = sdd->owningGroup; struct stat status; if (stat(path, &status) != -1) { int ownershipClaimed = 0; if ((status.st_uid == user) && (status.st_gid == group)) { ownershipClaimed = 1; } else if (!canChangePathOwnership(path)) { logMessage(LOG_WARNING, "can't claim ownership: %s", path); } else if (chown(path, user, group) == -1) { logSystemError("chown"); } else { logMessage(LOG_INFO, "%s: %s", gettext("ownership claimed"), path); ownershipClaimed = 1; } if (ownershipClaimed) { mode_t oldMode = status.st_mode; mode_t newMode = oldMode; newMode |= S_IRGRP | S_IWGRP; if (S_ISDIR(newMode)) newMode |= S_IXGRP | S_ISGID; if (newMode != oldMode) { if (!canChangePathPermissions(path)) { logMessage(LOG_WARNING, "can't add group permissions: %s", path); } else if (chmod(path, newMode) != -1) { logMessage(LOG_INFO, "%s: %s", gettext("group permissions added"), path); } else { logSystemError("chmod"); } } } } else { logSystemError("stat"); } return 1; } static void claimStateDirectories (void) { StateDirectoryData sdd = { .owningUser = geteuid(), .owningGroup = getegid(), }; const StateDirectoryEntry *sde = stateDirectoryTable; const StateDirectoryEntry *end = sde + stateDirectoryCount; while (sde < end) { const char *path = getStateDirectoryPath(sde); if (path && *path) { const char *name = locatePathName(path); if (strcasecmp(name, sde->expectedName) == 0) { processPathTree(path, claimStateDirectory, &sdd); } else { logMessage(LOG_DEBUG, "not claiming %s directory: %s (expecting %s)", sde->whichDirectory, path, sde->expectedName ); } } sde += 1; } } #endif /* HAVE_PWD_H */ typedef enum { PARM_PATH, PARM_SCFMODE, PARM_SHELL, PARM_USER, } Parameters; const char *const * getPrivilegeParameterNames (void) { static const char *const names[] = NULL_TERMINATED_STRING_ARRAY( "path", "scfmode", "shell", "user" ); return names; } const char * getPrivilegeParametersPlatform (void) { return "lx"; } void establishProgramPrivileges (char **parameters, int stayPrivileged) { logCurrentCapabilities("at start"); setCommandSearchPath(parameters[PARM_PATH]); setDefaultShell(parameters[PARM_SHELL]); #ifdef PR_SET_KEEPCAPS if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) == -1) { logSystemError("prctl[PR_SET_KEEPCAPS]"); } #endif /* PR_SET_KEEPCAPS */ #ifdef HAVE_SCHED_H isolateNamespaces(); #endif /* HAVE_SCHED_H */ { const char *unprivilegedUser = parameters[PARM_USER]; int haveHomeDirectory = 0; #ifdef HAVE_PWD_H int switched = switchUser( unprivilegedUser, stayPrivileged, &haveHomeDirectory ); if (switched) { umask(umask(0) & ~S_IRWXG); claimStateDirectories(); } else { logMessage(LOG_DEBUG, "not claiming state directories"); } endpwent(); #endif /* HAVE_PWD_H */ if (!haveHomeDirectory) { if (!setHomeDirectory(getUpdatableDirectory())) { logMessage(LOG_WARNING, "home directory not set"); } } } establishPrivileges(stayPrivileged); logCurrentCapabilities("after relinquish"); #ifdef SECCOMP_MODE_FILTER scfInstallFilter(parameters[PARM_SCFMODE]); #endif /* SECCOMP_MODE_FILTER */ }