/
githubmirror
/
libcap-ng
Обзор
Документация
Войти
/
githubmirror
/
libcap-ng
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
utils/pscap.c
551 строка
13 KB
Steve Grubb
Audit utility license notices
03 авг 2026, 01:38
03 авг 2026, 01:38
38b19c1
Код
Авторство
О чём код?
/* * pscap.c - A program that lists running processes with capabilities * Copyright (c) 2009,2012,2019-20,2023-24,2026 Red Hat Inc. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Steve Grubb <sgrubb@redhat.com> */ #include "config.h" #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <dirent.h> #include <fcntl.h> #include <stdbool.h> #include <sys/stat.h> #include "cap-ng.h" #include "proc-account.h" #include "proc-output.h" #include "proc-sanitize.h" #include "proc-status.h" #define CMD_LEN 16 #define ACCOUNT_LEN 32 #define USERNS_MARK_LEN 3 // two characters plus '\0'. #ifndef PSCAP_NO_MAIN static void usage(void) { fprintf(stderr, "usage: pscap [-a] [-p pid] [--tree]\n"); exit(1); } struct proc_info { pid_t pid; pid_t ppid; char *cmd; char account[ACCOUNT_LEN]; char *caps_text; }; static int append_marker(char **text, const char *marker) __attr_access ((__read_write__, 1)) __attr_access ((__read_only__, 2)) __wur; static char *format_caps(int caps, bool ambient, bool bounds) __attribute_malloc__ __attr_dealloc_free __wur; /* * read_euid - read one process effective UID from procfs. * @pid: process ID whose status file should be inspected. * * Returns the effective UID on success, or (uid_t)-1 when it cannot be read. */ static uid_t read_euid(int pid) { struct proc_status status; if (proc_read_status(pid, &status) < 0 || !status.seen_euid) return (uid_t)-1; return status.euid; } /* * get_account_name - format the account name for a process effective UID. * @pid: process ID whose effective UID should be read. * @account: destination buffer receiving the account label. * @account_len: size of @account in bytes. * * Returns no value. */ static void get_account_name(int pid, char *account, size_t account_len) { uid_t euid = read_euid(pid); proc_format_account_name_from_euid(euid, account, account_len); } #endif #ifdef PSCAP_TEST /* * wrap_to - test-visible wrapper for the shared plain text wrap helper. * @text: source string to wrap. * @max: maximum number of bytes to include before wrapping. * * Returns the byte offset where output should wrap. */ size_t wrap_to(const char *text, size_t max) { return proc_wrap_plain(text, max); } #endif #ifndef PSCAP_NO_MAIN /* * utility_logic_test only needs wrap_to(). Keep the rest of the pscap * implementation out of PSCAP_NO_MAIN builds so the test object does not * accumulate main-program helpers that trigger unused-function warnings. */ /* * compare_pid - order processes by pid for sorting/bsearch * @a: pointer to left struct proc_info * @b: pointer to right struct proc_info * * Returns -1, 0, or 1 for ordering. */ static int compare_pid(const void *a, const void *b) { const struct proc_info *left = a; const struct proc_info *right = b; if (left->pid < right->pid) return -1; if (left->pid > right->pid) return 1; return 0; } /* * find_proc - locate a process record by pid * @procs: process array sorted by pid * @count: number of entries in @procs * @pid: process id to locate * * Returns pointer to the matching entry or NULL if not found. */ static void *find_proc(struct proc_info *procs, size_t count, pid_t pid) { struct proc_info key; key.pid = pid; return bsearch(&key, procs, count, sizeof(*procs), compare_pid); } /* * append_marker - append a marker string to the capability text * @text: capability text buffer pointer to append to * @marker: marker string to append (e.g. " @" or " +") * * Returns 0 on success, -1 on allocation failure. */ static int append_marker(char **text, const char *marker) { size_t len = strlen(*text); size_t marker_len = strlen(marker); char *tmp = realloc(*text, len + marker_len + 1); if (!tmp) return -1; memcpy(tmp + len, marker, marker_len + 1); *text = tmp; return 0; } /* * format_caps - format capability text with optional markers * @caps: capability summary state from capng_have_capabilities() * @ambient: true if ambient capabilities are present * @bounds: true if bounding set differs from full * * Returns allocated capability string or NULL on allocation failure. */ static char *format_caps(int caps, bool ambient, bool bounds) { char *text; if (caps == CAPNG_PARTIAL) text = capng_print_caps_text(CAPNG_PRINT_BUFFER, CAPNG_PERMITTED); else if (caps == CAPNG_FULL) text = strdup("full"); else text = strdup("none"); if (!text) return NULL; if (ambient && append_marker(&text, " @") < 0) { free(text); return NULL; } if (bounds && append_marker(&text, " +") < 0) { free(text); return NULL; } return text; } /* * print_tree_node - render a node and its children in tree mode * @procs: process array * @count: number of entries in @procs * @index: index of current node in @procs * @prefix: current line prefix * @is_last: true if this node is the last child of its parent * @is_root: true if this node is a tree root * * Returns nothing. Recurses through children to emit full subtree. */ static void print_tree_node(struct proc_info *procs, size_t count, size_t index, const char *prefix, bool is_last, bool is_root, int width) { struct proc_info *proc = &procs[index]; size_t child_total = 0; size_t child_seen = 0; size_t i; char head[64]; const char *branch = ""; char *text; size_t text_len; char *line_prefix; char *cont_prefix; if (!is_root) branch = is_last ? " " : "│ "; line_prefix = malloc(strlen(prefix) + strlen(is_root ? "" : (is_last ? "└─ " : "├─ ")) + 1); if (!line_prefix) return; strcpy(line_prefix, prefix); if (!is_root) strcat(line_prefix, is_last ? "└─ " : "├─ "); cont_prefix = malloc(strlen(prefix) + strlen(branch) + 1); if (!cont_prefix) { free(line_prefix); return; } strcpy(cont_prefix, prefix); strcat(cont_prefix, branch); snprintf(head, sizeof(head), "%s(%d:%s) [", proc->cmd, proc->pid, proc->account); text_len = strlen(head) + strlen(proc->caps_text) + 2; text = malloc(text_len); if (!text) { free(line_prefix); free(cont_prefix); return; } snprintf(text, text_len, "%s%s]", head, proc->caps_text); proc_print_wrapped(line_prefix, cont_prefix, text, width); free(text); free(line_prefix); for (i = 0; i < count; i++) { if (procs[i].ppid == proc->pid) child_total++; } if (child_total == 0) { free(cont_prefix); return; } for (i = 0; i < count; i++) { if (procs[i].ppid != proc->pid) continue; child_seen++; print_tree_node(procs, count, i, cont_prefix, child_seen == child_total, false, width); } free(cont_prefix); } /* * print_tree - render all process trees in pid order * @procs: process array * @count: number of entries in @procs * * Returns nothing. Each tree starts at a pid whose parent isn't present. */ static void print_tree(struct proc_info *procs, size_t count) { size_t i; int width = proc_output_width(); if (count > 1) qsort(procs, count, sizeof(*procs), compare_pid); for (i = 0; i < count; i++) { if (!find_proc(procs, count, procs[i].ppid)) print_tree_node(procs, count, i, "", true, true, width); } } /* * Precise recursive checks for parent-child relation between namespaces * using ioctl() were avoided, because there didn't seem to be any case when * we may dereference the namespace symlink in /proc/PID/ns for processes in * user namespaces other than the current or child ones. Thus, the check just * tries to dereference the link and checks that it does not point to the * current NS. */ static bool in_child_userns(int pid) { char ns_file_path[32]; struct stat statbuf; ino_t own_ns_inode; dev_t own_ns_dev; if (stat("/proc/self/ns/user", &statbuf) < 0) return false; own_ns_inode = statbuf.st_ino; own_ns_dev = statbuf.st_dev; snprintf(ns_file_path, sizeof(ns_file_path), "/proc/%d/ns/user", pid); if (stat(ns_file_path, &statbuf) < 0) return false; return statbuf.st_ino != own_ns_inode || statbuf.st_dev != own_ns_dev; } #ifndef PSCAP_NO_MAIN int main(int argc, char *argv[]) { char *endptr = NULL; DIR *d; struct dirent *ent; int header = 0, show_all = 0, caps; pid_t our_pid = getpid(); pid_t target_pid = 0; uid_t last_uid = (uid_t)-1; const char *name = NULL; int tree_mode = 0; struct proc_info *procs = NULL; size_t proc_count = 0; size_t proc_capacity = 0; size_t i; for (i = 1; i < (size_t)argc; i++) { if (strcmp(argv[i], "-a") == 0) { show_all = 1; continue; } if (strcmp(argv[i], "--tree") == 0) { tree_mode = 1; continue; } if (strcmp(argv[i], "-p") == 0) { if (i + 1 >= (size_t)argc) usage(); errno = 0; target_pid = strtol(argv[++i], &endptr, 10); if (errno) { fprintf(stderr, "Can't read pid: %s\n", argv[i]); return 1; } if ((endptr == argv[i]) || (*endptr != '\0') || !target_pid) { fprintf(stderr, "Invalid pid argument: %s\n", argv[i]); return 1; } if (target_pid == 1) show_all = 1; continue; } usage(); } d = opendir("/proc"); if (d == NULL) { fprintf(stderr, "Can't open /proc: %s\n", strerror(errno)); return 1; } while (( ent = readdir(d) )) { int pid, ppid; char buf[100]; char *safe_cmd = NULL; char *tmp, cmd[CMD_LEN + USERNS_MARK_LEN], state; int fd, len; // Skip non-process dir entries if(*ent->d_name<'0' || *ent->d_name>'9') continue; errno = 0; pid = strtol(ent->d_name, NULL, 10); if (errno) continue; if (target_pid && (pid != target_pid)) continue; /* Skip our pid so we aren't listed */ if (pid == our_pid) continue; // Parse up the stat file for the proc snprintf(buf, sizeof(buf), "/proc/%d/stat", pid); fd = open(buf, O_RDONLY|O_CLOEXEC, 0); if (fd < 0) continue; len = read(fd, buf, sizeof(buf) - 1); close(fd); if (len < 40) continue; buf[len] = 0; tmp = strrchr(buf, ')'); if (tmp) *tmp = 0; else continue; memset(cmd, 0, sizeof(cmd)); sscanf(buf, "%d (%15c", &ppid, cmd); // ppid is throwaway sscanf(tmp+2, "%c %d", &state, &ppid); // Skip kthreads if (pid == 2 || ppid == 2) continue; // now get the capabilities capng_clear(CAPNG_SELECT_ALL); capng_setpid(pid); if (capng_get_caps_process()) continue; // And print out anything with capabilities caps = capng_have_capabilities(CAPNG_SELECT_CAPS); safe_cmd = sanitize_untrusted_field(cmd); if (!safe_cmd) continue; if (in_child_userns(pid)) { char *marked = malloc(strlen(safe_cmd) + 3); if (!marked) { free(safe_cmd); continue; } snprintf(marked, strlen(safe_cmd) + 3, "%s *", safe_cmd); free(safe_cmd); safe_cmd = marked; } if (tree_mode) { char *caps_text; bool has_ambient; bool has_bounds; if (!show_all && caps <= CAPNG_NONE) { free(safe_cmd); continue; } has_ambient = capng_have_capabilities( CAPNG_SELECT_AMBIENT) > CAPNG_NONE; has_bounds = capng_have_capabilities( CAPNG_SELECT_BOUNDS) > CAPNG_NONE; caps_text = format_caps(caps, has_ambient, has_bounds); if (!caps_text) { free(safe_cmd); continue; } if (proc_count == proc_capacity) { size_t new_capacity = proc_capacity ? proc_capacity * 2 : 256; struct proc_info *pi_tmp; pi_tmp = realloc(procs, new_capacity * sizeof(*procs)); if (!pi_tmp) { free(caps_text); free(safe_cmd); continue; } procs = pi_tmp; proc_capacity = new_capacity; } procs[proc_count].pid = pid; procs[proc_count].ppid = ppid; procs[proc_count].cmd = safe_cmd; get_account_name(pid, procs[proc_count].account, sizeof(procs[proc_count].account)); procs[proc_count].caps_text = caps_text; proc_count++; safe_cmd = NULL; } else if (caps > CAPNG_NONE) { char *caps_text; bool has_ambient; bool has_bounds; uid_t euid = read_euid(pid); if (header == 0) { printf("%-7s %-7s %-16s %-15s %s\n", "ppid", "pid", "uid", "command", "capabilities"); header = 1; } proc_update_account_cache(euid, &last_uid, &name); has_ambient = capng_have_capabilities( CAPNG_SELECT_AMBIENT) > CAPNG_NONE; has_bounds = capng_have_capabilities( CAPNG_SELECT_BOUNDS) > CAPNG_NONE; caps_text = format_caps(caps, has_ambient, has_bounds); if (!caps_text) { free(safe_cmd); continue; } if (name) { printf("%-7d %-7d %-16s %-15s ", ppid, pid, name, safe_cmd); } else printf("%-7d %-7d %-16u %-15s ", ppid, pid, (unsigned int)last_uid, safe_cmd); printf("%s\n", caps_text); free(caps_text); free(safe_cmd); safe_cmd = NULL; } if (safe_cmd) free(safe_cmd); } closedir(d); if (tree_mode) { print_tree(procs, proc_count); for (i = 0; i < proc_count; i++) { free(procs[i].cmd); free(procs[i].caps_text); } free(procs); } return 0; } #endif #endif