pgrep: Add support for ignoring ancestors with -A/--ignore-ancestors
pgrep and friends naturally filter their own processes from their matches. The same issue can occur when elevating with tools like sudo or doas, where the elevating shim layers linger as a parent and are returned in the results. For example: % sudo pkill -9 -cf someelevatedcmdline 1 zsh: killed sudo pkill -9 -cf someelevatedcmdline This is a situation we've actually seen in production, where some poor soul changes how permission management works (for example with Linux's hidepid option), needs to elevate a pgrep or pkill call, and now ends up with more than they bargained for. Even after the issue is noticed, resolving it requires reinventing some of the pgrep logic, which is unfortunate. This commit adds the -A/--ignore-ancestors option which excludes pgrep's ancestors from the results: % sudo ./pkill -9 -Acf someelevatedcmdline 0 We looks at multiple layers of the process hierarchy because, while things like sudo only have one layer of shimming, some mechanisms (like those found in a typical container manager like those found in Docker or Kubernetes) may have many more. Signed-off-by: Chris Down <chris@chrisdown.name>
This commit is contained in:
parent
deeb82411f
commit
4b44ab98c1
10
man/pgrep.1
10
man/pgrep.1
@ -177,6 +177,16 @@ Fail if pidfile (see \fB\-F\fR) not locked.
|
|||||||
\fB\-r\fR, \fB\-\-runstates\fR \fID,R,S,Z,\fP...
|
\fB\-r\fR, \fB\-\-runstates\fR \fID,R,S,Z,\fP...
|
||||||
Match only processes which match the process state.
|
Match only processes which match the process state.
|
||||||
.TP
|
.TP
|
||||||
|
\fB\-A\fR, \fB\-\-ignore-ancestors\fR\fR
|
||||||
|
Ignore all ancestors of
|
||||||
|
.BR pgrep ,
|
||||||
|
.BR pkill ,
|
||||||
|
or
|
||||||
|
.BR pidwait .
|
||||||
|
For example, this can be useful when elevating with
|
||||||
|
.BR sudo
|
||||||
|
or similar tools.
|
||||||
|
.TP
|
||||||
\fB\-\-cgroup \fIname\fP,...
|
\fB\-\-cgroup \fIname\fP,...
|
||||||
Match on provided control group (cgroup) v2 name. See
|
Match on provided control group (cgroup) v2 name. See
|
||||||
.BR cgroups (8)
|
.BR cgroups (8)
|
||||||
|
54
src/pgrep.c
54
src/pgrep.c
@ -124,6 +124,7 @@ static struct el *opt_pgrp = NULL;
|
|||||||
static struct el *opt_rgid = NULL;
|
static struct el *opt_rgid = NULL;
|
||||||
static struct el *opt_pid = NULL;
|
static struct el *opt_pid = NULL;
|
||||||
static struct el *opt_ppid = NULL;
|
static struct el *opt_ppid = NULL;
|
||||||
|
static struct el *opt_ignore_ancestors = NULL;
|
||||||
static struct el *opt_sid = NULL;
|
static struct el *opt_sid = NULL;
|
||||||
static struct el *opt_term = NULL;
|
static struct el *opt_term = NULL;
|
||||||
static struct el *opt_euid = NULL;
|
static struct el *opt_euid = NULL;
|
||||||
@ -180,6 +181,7 @@ static int __attribute__ ((__noreturn__)) usage(int opt)
|
|||||||
fputs(_(" -F, --pidfile <file> read PIDs from file\n"), fp);
|
fputs(_(" -F, --pidfile <file> read PIDs from file\n"), fp);
|
||||||
fputs(_(" -L, --logpidfile fail if PID file is not locked\n"), fp);
|
fputs(_(" -L, --logpidfile fail if PID file is not locked\n"), fp);
|
||||||
fputs(_(" -r, --runstates <state> match runstates [D,S,Z,...]\n"), fp);
|
fputs(_(" -r, --runstates <state> match runstates [D,S,Z,...]\n"), fp);
|
||||||
|
fputs(_(" -A, --ignore-ancestors exclude our ancestors from results\n"), fp);
|
||||||
fputs(_(" --cgroup <grp,...> match by cgroup v2 names\n"), fp);
|
fputs(_(" --cgroup <grp,...> match by cgroup v2 names\n"), fp);
|
||||||
fputs(_(" --ns <PID> match the processes that belong to the same\n"
|
fputs(_(" --ns <PID> match the processes that belong to the same\n"
|
||||||
" namespace as <pid>\n"), fp);
|
" namespace as <pid>\n"), fp);
|
||||||
@ -194,6 +196,50 @@ static int __attribute__ ((__noreturn__)) usage(int opt)
|
|||||||
exit(fp == stderr ? EXIT_USAGE : EXIT_SUCCESS);
|
exit(fp == stderr ? EXIT_USAGE : EXIT_SUCCESS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static struct el *get_our_ancestors(void)
|
||||||
|
{
|
||||||
|
#define PIDS_GETINT(e) PIDS_VAL(EU_##e, s_int, stack, info)
|
||||||
|
struct pids_info *info = NULL;
|
||||||
|
struct el *list = NULL;
|
||||||
|
int i = 0;
|
||||||
|
int size = 0;
|
||||||
|
int done = 0;
|
||||||
|
pid_t search_pid = getpid();
|
||||||
|
struct pids_stack *stack;
|
||||||
|
|
||||||
|
if (procps_pids_new(&info, Items, 15) < 0)
|
||||||
|
xerrx(EXIT_FATAL, _("Unable to create pid info structure"));
|
||||||
|
|
||||||
|
while (!done) {
|
||||||
|
if (search_pid == 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (i == size) {
|
||||||
|
grow_size(size);
|
||||||
|
list = xrealloc(list, (1 + size) * sizeof(*list));
|
||||||
|
}
|
||||||
|
|
||||||
|
while ((stack = procps_pids_get(info, PIDS_FETCH_TASKS_ONLY))) {
|
||||||
|
if (PIDS_GETINT(PID) == search_pid) {
|
||||||
|
list[++i].num = PIDS_GETINT(PPID);
|
||||||
|
search_pid = list[i].num;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
done = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i == 0) {
|
||||||
|
free(list);
|
||||||
|
list = NULL;
|
||||||
|
} else {
|
||||||
|
list[0].num = i;
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
#undef PIDS_GETINT
|
||||||
|
}
|
||||||
|
|
||||||
static struct el *split_list (const char *restrict str, int (*convert)(const char *, struct el *))
|
static struct el *split_list (const char *restrict str, int (*convert)(const char *, struct el *))
|
||||||
{
|
{
|
||||||
char *copy;
|
char *copy;
|
||||||
@ -612,6 +658,8 @@ static struct el * select_procs (int *num)
|
|||||||
|
|
||||||
if (PIDS_GETINT(PID) == myself)
|
if (PIDS_GETINT(PID) == myself)
|
||||||
continue;
|
continue;
|
||||||
|
else if (opt_ignore_ancestors && match_numlist(PIDS_GETINT(PID), opt_ignore_ancestors))
|
||||||
|
continue;
|
||||||
else if (opt_newest && PIDS_GETULL(STARTTIME) < saved_start_time)
|
else if (opt_newest && PIDS_GETULL(STARTTIME) < saved_start_time)
|
||||||
match = 0;
|
match = 0;
|
||||||
else if (opt_oldest && PIDS_GETULL(STARTTIME) > saved_start_time)
|
else if (opt_oldest && PIDS_GETULL(STARTTIME) > saved_start_time)
|
||||||
@ -756,6 +804,7 @@ static void parse_opts (int argc, char **argv)
|
|||||||
};
|
};
|
||||||
static const struct option longopts[] = {
|
static const struct option longopts[] = {
|
||||||
{"signal", required_argument, NULL, SIGNAL_OPTION},
|
{"signal", required_argument, NULL, SIGNAL_OPTION},
|
||||||
|
{"ignore-ancestors", no_argument, NULL, 'A'},
|
||||||
{"count", no_argument, NULL, 'c'},
|
{"count", no_argument, NULL, 'c'},
|
||||||
{"cgroup", required_argument, NULL, CGROUP_OPTION},
|
{"cgroup", required_argument, NULL, CGROUP_OPTION},
|
||||||
{"delimiter", required_argument, NULL, 'd'},
|
{"delimiter", required_argument, NULL, 'd'},
|
||||||
@ -808,7 +857,7 @@ static void parse_opts (int argc, char **argv)
|
|||||||
prog_mode = PGREP;
|
prog_mode = PGREP;
|
||||||
}
|
}
|
||||||
|
|
||||||
strcat (opts, "LF:cfinoxP:O:g:s:u:U:G:t:r:?Vh");
|
strcat (opts, "LF:cfinoxP:O:Ag:s:u:U:G:t:r:?Vh");
|
||||||
|
|
||||||
while ((opt = getopt_long (argc, argv, opts, longopts, NULL)) != -1) {
|
while ((opt = getopt_long (argc, argv, opts, longopts, NULL)) != -1) {
|
||||||
switch (opt) {
|
switch (opt) {
|
||||||
@ -892,6 +941,9 @@ static void parse_opts (int argc, char **argv)
|
|||||||
case 'a':
|
case 'a':
|
||||||
opt_longlong = 1;
|
opt_longlong = 1;
|
||||||
break;
|
break;
|
||||||
|
case 'A':
|
||||||
|
opt_ignore_ancestors = get_our_ancestors();
|
||||||
|
break;
|
||||||
case 'n': /* Solaris: match only the newest */
|
case 'n': /* Solaris: match only the newest */
|
||||||
if (opt_oldest|opt_negate|opt_newest)
|
if (opt_oldest|opt_negate|opt_newest)
|
||||||
usage ('?');
|
usage ('?');
|
||||||
|
Loading…
Reference in New Issue
Block a user