mirror of
https://github.com/systemd/systemd
synced 2024-07-22 10:44:58 +00:00
basic: move a bunch of cmdline-related funcs to new argv-util.c+h
I wanted to move saved_arg[cv] to process-util.c+h, but this causes problems: process-util.h includes format-util.h which includes net/if.h, which conflicts with linux/if.h. So we can't include process-util.h in some files. But process-util.c is very long anyway, so it seems nice to create a new file. rename_process(), invoked_as(), invoked_by_systemd(), and argv_looks_like_help() which lived in process-util.c refer to saved_argc and saved_argv, so it seems reasonable to move them to the new file too. util.c is now empty, so it is removed. util.h remains.
This commit is contained in:
parent
c47511da7e
commit
ee617a4e5c
222
src/basic/argv-util.c
Normal file
222
src/basic/argv-util.c
Normal file
|
@ -0,0 +1,222 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <sched.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "argv-util.h"
|
||||
#include "errno-util.h"
|
||||
#include "missing_sched.h"
|
||||
#include "parse-util.h"
|
||||
#include "path-util.h"
|
||||
#include "process-util.h"
|
||||
#include "string-util.h"
|
||||
#include "strv.h"
|
||||
|
||||
int saved_argc = 0;
|
||||
char **saved_argv = NULL;
|
||||
|
||||
bool invoked_as(char *argv[], const char *token) {
|
||||
if (!argv || isempty(argv[0]))
|
||||
return false;
|
||||
|
||||
if (isempty(token))
|
||||
return false;
|
||||
|
||||
return strstr(last_path_component(argv[0]), token);
|
||||
}
|
||||
|
||||
bool invoked_by_systemd(void) {
|
||||
int r;
|
||||
|
||||
/* If the process is directly executed by PID1 (e.g. ExecStart= or generator), systemd-importd,
|
||||
* or systemd-homed, then $SYSTEMD_EXEC_PID= is set, and read the command line. */
|
||||
const char *e = getenv("SYSTEMD_EXEC_PID");
|
||||
if (!e)
|
||||
return false;
|
||||
|
||||
if (streq(e, "*"))
|
||||
/* For testing. */
|
||||
return true;
|
||||
|
||||
pid_t p;
|
||||
r = parse_pid(e, &p);
|
||||
if (r < 0) {
|
||||
/* We know that systemd sets the variable correctly. Something else must have set it. */
|
||||
log_debug_errno(r, "Failed to parse \"SYSTEMD_EXEC_PID=%s\", ignoring: %m", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
return getpid_cached() == p;
|
||||
}
|
||||
|
||||
bool argv_looks_like_help(int argc, char **argv) {
|
||||
char **l;
|
||||
|
||||
/* Scans the command line for indications the user asks for help. This is supposed to be called by
|
||||
* tools that do not implement getopt() style command line parsing because they are not primarily
|
||||
* user-facing. Detects four ways of asking for help:
|
||||
*
|
||||
* 1. Passing zero arguments
|
||||
* 2. Passing "help" as first argument
|
||||
* 3. Passing --help as any argument
|
||||
* 4. Passing -h as any argument
|
||||
*/
|
||||
|
||||
if (argc <= 1)
|
||||
return true;
|
||||
|
||||
if (streq_ptr(argv[1], "help"))
|
||||
return true;
|
||||
|
||||
l = strv_skip(argv, 1);
|
||||
|
||||
return strv_contains(l, "--help") ||
|
||||
strv_contains(l, "-h");
|
||||
}
|
||||
|
||||
static int update_argv(const char name[], size_t l) {
|
||||
static int can_do = -1;
|
||||
|
||||
if (can_do == 0)
|
||||
return 0;
|
||||
can_do = false; /* We'll set it to true only if the whole process works */
|
||||
|
||||
/* Let's not bother with this if we don't have euid == 0. Strictly speaking we should check for the
|
||||
* CAP_SYS_RESOURCE capability which is independent of the euid. In our own code the capability generally is
|
||||
* present only for euid == 0, hence let's use this as quick bypass check, to avoid calling mmap() if
|
||||
* PR_SET_MM_ARG_{START,END} fails with EPERM later on anyway. After all geteuid() is dead cheap to call, but
|
||||
* mmap() is not. */
|
||||
if (geteuid() != 0)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EPERM),
|
||||
"Skipping PR_SET_MM, as we don't have privileges.");
|
||||
|
||||
static size_t mm_size = 0;
|
||||
static char *mm = NULL;
|
||||
int r;
|
||||
|
||||
if (mm_size < l+1) {
|
||||
size_t nn_size;
|
||||
char *nn;
|
||||
|
||||
nn_size = PAGE_ALIGN(l+1);
|
||||
nn = mmap(NULL, nn_size, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
|
||||
if (nn == MAP_FAILED)
|
||||
return log_debug_errno(errno, "mmap() failed: %m");
|
||||
|
||||
strncpy(nn, name, nn_size);
|
||||
|
||||
/* Now, let's tell the kernel about this new memory */
|
||||
if (prctl(PR_SET_MM, PR_SET_MM_ARG_START, (unsigned long) nn, 0, 0) < 0) {
|
||||
if (ERRNO_IS_PRIVILEGE(errno))
|
||||
return log_debug_errno(errno, "PR_SET_MM_ARG_START failed: %m");
|
||||
|
||||
/* HACK: prctl() API is kind of dumb on this point. The existing end address may already be
|
||||
* below the desired start address, in which case the kernel may have kicked this back due
|
||||
* to a range-check failure (see linux/kernel/sys.c:validate_prctl_map() to see this in
|
||||
* action). The proper solution would be to have a prctl() API that could set both start+end
|
||||
* simultaneously, or at least let us query the existing address to anticipate this condition
|
||||
* and respond accordingly. For now, we can only guess at the cause of this failure and try
|
||||
* a workaround--which will briefly expand the arg space to something potentially huge before
|
||||
* resizing it to what we want. */
|
||||
log_debug_errno(errno, "PR_SET_MM_ARG_START failed, attempting PR_SET_MM_ARG_END hack: %m");
|
||||
|
||||
if (prctl(PR_SET_MM, PR_SET_MM_ARG_END, (unsigned long) nn + l + 1, 0, 0) < 0) {
|
||||
r = log_debug_errno(errno, "PR_SET_MM_ARG_END hack failed, proceeding without: %m");
|
||||
(void) munmap(nn, nn_size);
|
||||
return r;
|
||||
}
|
||||
|
||||
if (prctl(PR_SET_MM, PR_SET_MM_ARG_START, (unsigned long) nn, 0, 0) < 0)
|
||||
return log_debug_errno(errno, "PR_SET_MM_ARG_START still failed, proceeding without: %m");
|
||||
} else {
|
||||
/* And update the end pointer to the new end, too. If this fails, we don't really know what
|
||||
* to do, it's pretty unlikely that we can rollback, hence we'll just accept the failure,
|
||||
* and continue. */
|
||||
if (prctl(PR_SET_MM, PR_SET_MM_ARG_END, (unsigned long) nn + l + 1, 0, 0) < 0)
|
||||
log_debug_errno(errno, "PR_SET_MM_ARG_END failed, proceeding without: %m");
|
||||
}
|
||||
|
||||
if (mm)
|
||||
(void) munmap(mm, mm_size);
|
||||
|
||||
mm = nn;
|
||||
mm_size = nn_size;
|
||||
} else {
|
||||
strncpy(mm, name, mm_size);
|
||||
|
||||
/* Update the end pointer, continuing regardless of any failure. */
|
||||
if (prctl(PR_SET_MM, PR_SET_MM_ARG_END, (unsigned long) mm + l + 1, 0, 0) < 0)
|
||||
log_debug_errno(errno, "PR_SET_MM_ARG_END failed, proceeding without: %m");
|
||||
}
|
||||
|
||||
can_do = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rename_process(const char name[]) {
|
||||
bool truncated = false;
|
||||
|
||||
/* This is a like a poor man's setproctitle(). It changes the comm field, argv[0], and also the glibc's
|
||||
* internally used name of the process. For the first one a limit of 16 chars applies; to the second one in
|
||||
* many cases one of 10 (i.e. length of "/sbin/init") — however if we have CAP_SYS_RESOURCES it is unbounded;
|
||||
* to the third one 7 (i.e. the length of "systemd". If you pass a longer string it will likely be
|
||||
* truncated.
|
||||
*
|
||||
* Returns 0 if a name was set but truncated, > 0 if it was set but not truncated. */
|
||||
|
||||
if (isempty(name))
|
||||
return -EINVAL; /* let's not confuse users unnecessarily with an empty name */
|
||||
|
||||
if (!is_main_thread())
|
||||
return -EPERM; /* Let's not allow setting the process name from other threads than the main one, as we
|
||||
* cache things without locking, and we make assumptions that PR_SET_NAME sets the
|
||||
* process name that isn't correct on any other threads */
|
||||
|
||||
size_t l = strlen(name);
|
||||
|
||||
/* First step, change the comm field. The main thread's comm is identical to the process comm. This means we
|
||||
* can use PR_SET_NAME, which sets the thread name for the calling thread. */
|
||||
if (prctl(PR_SET_NAME, name) < 0)
|
||||
log_debug_errno(errno, "PR_SET_NAME failed: %m");
|
||||
if (l >= TASK_COMM_LEN) /* Linux userspace process names can be 15 chars at max */
|
||||
truncated = true;
|
||||
|
||||
/* Second step, change glibc's ID of the process name. */
|
||||
if (program_invocation_name) {
|
||||
size_t k;
|
||||
|
||||
k = strlen(program_invocation_name);
|
||||
strncpy(program_invocation_name, name, k);
|
||||
if (l > k)
|
||||
truncated = true;
|
||||
}
|
||||
|
||||
/* Third step, completely replace the argv[] array the kernel maintains for us. This requires privileges, but
|
||||
* has the advantage that the argv[] array is exactly what we want it to be, and not filled up with zeros at
|
||||
* the end. This is the best option for changing /proc/self/cmdline. */
|
||||
(void) update_argv(name, l);
|
||||
|
||||
/* Fourth step: in all cases we'll also update the original argv[], so that our own code gets it right too if
|
||||
* it still looks here */
|
||||
if (saved_argc > 0) {
|
||||
if (saved_argv[0]) {
|
||||
size_t k;
|
||||
|
||||
k = strlen(saved_argv[0]);
|
||||
strncpy(saved_argv[0], name, k);
|
||||
if (l > k)
|
||||
truncated = true;
|
||||
}
|
||||
|
||||
for (int i = 1; i < saved_argc; i++) {
|
||||
if (!saved_argv[i])
|
||||
break;
|
||||
|
||||
memzero(saved_argv[i], strlen(saved_argv[i]));
|
||||
}
|
||||
}
|
||||
|
||||
return !truncated;
|
||||
}
|
25
src/basic/argv-util.h
Normal file
25
src/basic/argv-util.h
Normal file
|
@ -0,0 +1,25 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "macro.h"
|
||||
|
||||
extern int saved_argc;
|
||||
extern char **saved_argv;
|
||||
|
||||
static inline void save_argc_argv(int argc, char **argv) {
|
||||
/* Protect against CVE-2021-4034 style attacks */
|
||||
assert_se(argc > 0);
|
||||
assert_se(argv);
|
||||
assert_se(argv[0]);
|
||||
|
||||
saved_argc = argc;
|
||||
saved_argv = argv;
|
||||
}
|
||||
|
||||
bool invoked_as(char *argv[], const char *token);
|
||||
bool invoked_by_systemd(void);
|
||||
bool argv_looks_like_help(int argc, char **argv);
|
||||
|
||||
int rename_process(const char name[]);
|
|
@ -16,6 +16,7 @@
|
|||
#include "sd-messages.h"
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "argv-util.h"
|
||||
#include "errno-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "format-util.h"
|
||||
|
|
|
@ -9,6 +9,8 @@ basic_sources = files(
|
|||
'alloc-util.h',
|
||||
'architecture.c',
|
||||
'architecture.h',
|
||||
'argv-util.c',
|
||||
'argv-util.h',
|
||||
'arphrd-util.c',
|
||||
'arphrd-util.h',
|
||||
'async.c',
|
||||
|
@ -256,7 +258,6 @@ basic_sources = files(
|
|||
'user-util.h',
|
||||
'utf8.c',
|
||||
'utf8.h',
|
||||
'util.c',
|
||||
'util.h',
|
||||
'virt.c',
|
||||
'virt.h',
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/personality.h>
|
||||
#include <sys/prctl.h>
|
||||
|
@ -22,6 +21,7 @@
|
|||
|
||||
#include "alloc-util.h"
|
||||
#include "architecture.h"
|
||||
#include "argv-util.h"
|
||||
#include "env-file.h"
|
||||
#include "env-util.h"
|
||||
#include "errno-util.h"
|
||||
|
@ -297,151 +297,6 @@ int container_get_leader(const char *machine, pid_t *pid) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int update_argv(const char name[], size_t l) {
|
||||
static int can_do = -1;
|
||||
|
||||
if (can_do == 0)
|
||||
return 0;
|
||||
can_do = false; /* We'll set it to true only if the whole process works */
|
||||
|
||||
/* Let's not bother with this if we don't have euid == 0. Strictly speaking we should check for the
|
||||
* CAP_SYS_RESOURCE capability which is independent of the euid. In our own code the capability generally is
|
||||
* present only for euid == 0, hence let's use this as quick bypass check, to avoid calling mmap() if
|
||||
* PR_SET_MM_ARG_{START,END} fails with EPERM later on anyway. After all geteuid() is dead cheap to call, but
|
||||
* mmap() is not. */
|
||||
if (geteuid() != 0)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EPERM),
|
||||
"Skipping PR_SET_MM, as we don't have privileges.");
|
||||
|
||||
static size_t mm_size = 0;
|
||||
static char *mm = NULL;
|
||||
int r;
|
||||
|
||||
if (mm_size < l+1) {
|
||||
size_t nn_size;
|
||||
char *nn;
|
||||
|
||||
nn_size = PAGE_ALIGN(l+1);
|
||||
nn = mmap(NULL, nn_size, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
|
||||
if (nn == MAP_FAILED)
|
||||
return log_debug_errno(errno, "mmap() failed: %m");
|
||||
|
||||
strncpy(nn, name, nn_size);
|
||||
|
||||
/* Now, let's tell the kernel about this new memory */
|
||||
if (prctl(PR_SET_MM, PR_SET_MM_ARG_START, (unsigned long) nn, 0, 0) < 0) {
|
||||
if (ERRNO_IS_PRIVILEGE(errno))
|
||||
return log_debug_errno(errno, "PR_SET_MM_ARG_START failed: %m");
|
||||
|
||||
/* HACK: prctl() API is kind of dumb on this point. The existing end address may already be
|
||||
* below the desired start address, in which case the kernel may have kicked this back due
|
||||
* to a range-check failure (see linux/kernel/sys.c:validate_prctl_map() to see this in
|
||||
* action). The proper solution would be to have a prctl() API that could set both start+end
|
||||
* simultaneously, or at least let us query the existing address to anticipate this condition
|
||||
* and respond accordingly. For now, we can only guess at the cause of this failure and try
|
||||
* a workaround--which will briefly expand the arg space to something potentially huge before
|
||||
* resizing it to what we want. */
|
||||
log_debug_errno(errno, "PR_SET_MM_ARG_START failed, attempting PR_SET_MM_ARG_END hack: %m");
|
||||
|
||||
if (prctl(PR_SET_MM, PR_SET_MM_ARG_END, (unsigned long) nn + l + 1, 0, 0) < 0) {
|
||||
r = log_debug_errno(errno, "PR_SET_MM_ARG_END hack failed, proceeding without: %m");
|
||||
(void) munmap(nn, nn_size);
|
||||
return r;
|
||||
}
|
||||
|
||||
if (prctl(PR_SET_MM, PR_SET_MM_ARG_START, (unsigned long) nn, 0, 0) < 0)
|
||||
return log_debug_errno(errno, "PR_SET_MM_ARG_START still failed, proceeding without: %m");
|
||||
} else {
|
||||
/* And update the end pointer to the new end, too. If this fails, we don't really know what
|
||||
* to do, it's pretty unlikely that we can rollback, hence we'll just accept the failure,
|
||||
* and continue. */
|
||||
if (prctl(PR_SET_MM, PR_SET_MM_ARG_END, (unsigned long) nn + l + 1, 0, 0) < 0)
|
||||
log_debug_errno(errno, "PR_SET_MM_ARG_END failed, proceeding without: %m");
|
||||
}
|
||||
|
||||
if (mm)
|
||||
(void) munmap(mm, mm_size);
|
||||
|
||||
mm = nn;
|
||||
mm_size = nn_size;
|
||||
} else {
|
||||
strncpy(mm, name, mm_size);
|
||||
|
||||
/* Update the end pointer, continuing regardless of any failure. */
|
||||
if (prctl(PR_SET_MM, PR_SET_MM_ARG_END, (unsigned long) mm + l + 1, 0, 0) < 0)
|
||||
log_debug_errno(errno, "PR_SET_MM_ARG_END failed, proceeding without: %m");
|
||||
}
|
||||
|
||||
can_do = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rename_process(const char name[]) {
|
||||
bool truncated = false;
|
||||
|
||||
/* This is a like a poor man's setproctitle(). It changes the comm field, argv[0], and also the glibc's
|
||||
* internally used name of the process. For the first one a limit of 16 chars applies; to the second one in
|
||||
* many cases one of 10 (i.e. length of "/sbin/init") — however if we have CAP_SYS_RESOURCES it is unbounded;
|
||||
* to the third one 7 (i.e. the length of "systemd". If you pass a longer string it will likely be
|
||||
* truncated.
|
||||
*
|
||||
* Returns 0 if a name was set but truncated, > 0 if it was set but not truncated. */
|
||||
|
||||
if (isempty(name))
|
||||
return -EINVAL; /* let's not confuse users unnecessarily with an empty name */
|
||||
|
||||
if (!is_main_thread())
|
||||
return -EPERM; /* Let's not allow setting the process name from other threads than the main one, as we
|
||||
* cache things without locking, and we make assumptions that PR_SET_NAME sets the
|
||||
* process name that isn't correct on any other threads */
|
||||
|
||||
size_t l = strlen(name);
|
||||
|
||||
/* First step, change the comm field. The main thread's comm is identical to the process comm. This means we
|
||||
* can use PR_SET_NAME, which sets the thread name for the calling thread. */
|
||||
if (prctl(PR_SET_NAME, name) < 0)
|
||||
log_debug_errno(errno, "PR_SET_NAME failed: %m");
|
||||
if (l >= TASK_COMM_LEN) /* Linux userspace process names can be 15 chars at max */
|
||||
truncated = true;
|
||||
|
||||
/* Second step, change glibc's ID of the process name. */
|
||||
if (program_invocation_name) {
|
||||
size_t k;
|
||||
|
||||
k = strlen(program_invocation_name);
|
||||
strncpy(program_invocation_name, name, k);
|
||||
if (l > k)
|
||||
truncated = true;
|
||||
}
|
||||
|
||||
/* Third step, completely replace the argv[] array the kernel maintains for us. This requires privileges, but
|
||||
* has the advantage that the argv[] array is exactly what we want it to be, and not filled up with zeros at
|
||||
* the end. This is the best option for changing /proc/self/cmdline. */
|
||||
(void) update_argv(name, l);
|
||||
|
||||
/* Fourth step: in all cases we'll also update the original argv[], so that our own code gets it right too if
|
||||
* it still looks here */
|
||||
if (saved_argc > 0) {
|
||||
if (saved_argv[0]) {
|
||||
size_t k;
|
||||
|
||||
k = strlen(saved_argv[0]);
|
||||
strncpy(saved_argv[0], name, k);
|
||||
if (l > k)
|
||||
truncated = true;
|
||||
}
|
||||
|
||||
for (int i = 1; i < saved_argc; i++) {
|
||||
if (!saved_argv[i])
|
||||
break;
|
||||
|
||||
memzero(saved_argv[i], strlen(saved_argv[i]));
|
||||
}
|
||||
}
|
||||
|
||||
return !truncated;
|
||||
}
|
||||
|
||||
int is_kernel_thread(pid_t pid) {
|
||||
_cleanup_free_ char *line = NULL;
|
||||
unsigned long long flags;
|
||||
|
@ -1627,40 +1482,6 @@ int setpriority_closest(int priority) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
bool invoked_as(char *argv[], const char *token) {
|
||||
if (!argv || isempty(argv[0]))
|
||||
return false;
|
||||
|
||||
if (isempty(token))
|
||||
return false;
|
||||
|
||||
return strstr(last_path_component(argv[0]), token);
|
||||
}
|
||||
|
||||
bool invoked_by_systemd(void) {
|
||||
int r;
|
||||
|
||||
/* If the process is directly executed by PID1 (e.g. ExecStart= or generator), systemd-importd,
|
||||
* or systemd-homed, then $SYSTEMD_EXEC_PID= is set, and read the command line. */
|
||||
const char *e = getenv("SYSTEMD_EXEC_PID");
|
||||
if (!e)
|
||||
return false;
|
||||
|
||||
if (streq(e, "*"))
|
||||
/* For testing. */
|
||||
return true;
|
||||
|
||||
pid_t p;
|
||||
r = parse_pid(e, &p);
|
||||
if (r < 0) {
|
||||
/* We know that systemd sets the variable correctly. Something else must have set it. */
|
||||
log_debug_errno(r, "Failed to parse \"SYSTEMD_EXEC_PID=%s\", ignoring: %m", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
return getpid_cached() == p;
|
||||
}
|
||||
|
||||
_noreturn_ void freeze(void) {
|
||||
log_close();
|
||||
|
||||
|
@ -1682,31 +1503,6 @@ _noreturn_ void freeze(void) {
|
|||
pause();
|
||||
}
|
||||
|
||||
bool argv_looks_like_help(int argc, char **argv) {
|
||||
char **l;
|
||||
|
||||
/* Scans the command line for indications the user asks for help. This is supposed to be called by
|
||||
* tools that do not implement getopt() style command line parsing because they are not primarily
|
||||
* user-facing. Detects four ways of asking for help:
|
||||
*
|
||||
* 1. Passing zero arguments
|
||||
* 2. Passing "help" as first argument
|
||||
* 3. Passing --help as any argument
|
||||
* 4. Passing -h as any argument
|
||||
*/
|
||||
|
||||
if (argc <= 1)
|
||||
return true;
|
||||
|
||||
if (streq_ptr(argv[1], "help"))
|
||||
return true;
|
||||
|
||||
l = strv_skip(argv, 1);
|
||||
|
||||
return strv_contains(l, "--help") ||
|
||||
strv_contains(l, "-h");
|
||||
}
|
||||
|
||||
static const char *const sigchld_code_table[] = {
|
||||
[CLD_EXITED] = "exited",
|
||||
[CLD_KILLED] = "killed",
|
||||
|
|
|
@ -71,7 +71,6 @@ void sigterm_wait(pid_t pid);
|
|||
|
||||
int kill_and_sigcont(pid_t pid, int sig);
|
||||
|
||||
int rename_process(const char name[]);
|
||||
int is_kernel_thread(pid_t pid);
|
||||
|
||||
int getenv_for_pid(pid_t pid, const char *field, char **_value);
|
||||
|
@ -191,10 +190,4 @@ int pidfd_get_pid(int fd, pid_t *ret);
|
|||
|
||||
int setpriority_closest(int priority);
|
||||
|
||||
bool invoked_as(char *argv[], const char *token);
|
||||
|
||||
bool invoked_by_systemd(void);
|
||||
|
||||
_noreturn_ void freeze(void);
|
||||
|
||||
bool argv_looks_like_help(int argc, char **argv);
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "build.h"
|
||||
#include "env-file.h"
|
||||
#include "fd-util.h"
|
||||
#include "fileio.h"
|
||||
#include "hostname-util.h"
|
||||
#include "log.h"
|
||||
#include "macro.h"
|
||||
#include "parse-util.h"
|
||||
#include "stat-util.h"
|
||||
#include "string-util.h"
|
||||
#include "util.h"
|
||||
#include "virt.h"
|
||||
|
||||
int saved_argc = 0;
|
||||
char **saved_argv = NULL;
|
|
@ -5,20 +5,6 @@
|
|||
|
||||
#include "macro.h"
|
||||
|
||||
extern int saved_argc;
|
||||
extern char **saved_argv;
|
||||
|
||||
static inline void save_argc_argv(int argc, char **argv) {
|
||||
|
||||
/* Protect against CVE-2021-4034 style attacks */
|
||||
assert_se(argc > 0);
|
||||
assert_se(argv);
|
||||
assert_se(argv[0]);
|
||||
|
||||
saved_argc = argc;
|
||||
saved_argv = argv;
|
||||
}
|
||||
|
||||
/* Note: log2(0) == log2(1) == 0 here and below. */
|
||||
|
||||
#define CONST_LOG2ULL(x) ((x) > 1 ? (unsigned) __builtin_clzll(x) ^ 63U : 0)
|
||||
|
|
|
@ -39,6 +39,7 @@
|
|||
#if HAVE_APPARMOR
|
||||
#include "apparmor-util.h"
|
||||
#endif
|
||||
#include "argv-util.h"
|
||||
#include "async.h"
|
||||
#include "barrier.h"
|
||||
#include "bpf-lsm.h"
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#include "alloc-util.h"
|
||||
#include "apparmor-setup.h"
|
||||
#include "architecture.h"
|
||||
#include "argv-util.h"
|
||||
#if HAVE_LIBBPF
|
||||
#include "bpf-lsm.h"
|
||||
#endif
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include <sys/prctl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "argv-util.h"
|
||||
#include "def.h"
|
||||
#include "exit-status.h"
|
||||
#include "fd-util.h"
|
||||
|
|
|
@ -5,12 +5,12 @@
|
|||
|
||||
#include "sd-daemon.h"
|
||||
|
||||
#include "argv-util.h"
|
||||
#include "pager.h"
|
||||
#include "selinux-util.h"
|
||||
#include "spawn-ask-password-agent.h"
|
||||
#include "spawn-polkit-agent.h"
|
||||
#include "static-destruct.h"
|
||||
#include "util.h"
|
||||
|
||||
#define _DEFINE_MAIN_FUNCTION(intro, impl, ret) \
|
||||
int main(int argc, char *argv[]) { \
|
||||
|
|
|
@ -5,9 +5,9 @@
|
|||
|
||||
#include "sd-daemon.h"
|
||||
|
||||
#include "argv-util.h"
|
||||
#include "macro.h"
|
||||
#include "static-destruct.h"
|
||||
#include "util.h"
|
||||
|
||||
static inline bool manager_errno_skip_test(int r) {
|
||||
return IN_SET(abs(r),
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "argv-util.h"
|
||||
#include "bus-error.h"
|
||||
#include "bus-locator.h"
|
||||
#include "parse-util.h"
|
||||
|
|
|
@ -41,6 +41,8 @@ test_dlopen_c = files('test-dlopen.c')
|
|||
############################################################
|
||||
|
||||
tests += [
|
||||
[files('test-argv-util.c')],
|
||||
|
||||
[files('test-device-nodes.c')],
|
||||
|
||||
[files('test-ether-addr-util.c')],
|
||||
|
|
123
src/test/test-argv-util.c
Normal file
123
src/test/test-argv-util.c
Normal file
|
@ -0,0 +1,123 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <sched.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#if HAVE_VALGRIND_VALGRIND_H
|
||||
# include <valgrind/valgrind.h>
|
||||
#endif
|
||||
|
||||
#include "argv-util.h"
|
||||
#include "missing_sched.h"
|
||||
#include "process-util.h"
|
||||
#include "tests.h"
|
||||
#include "virt.h"
|
||||
|
||||
static void test_rename_process_now(const char *p, int ret) {
|
||||
_cleanup_free_ char *comm = NULL, *cmdline = NULL;
|
||||
int r;
|
||||
|
||||
log_info("/* %s(%s) */", __func__, p);
|
||||
|
||||
r = rename_process(p);
|
||||
assert_se(r == ret ||
|
||||
(ret == 0 && r >= 0) ||
|
||||
(ret > 0 && r > 0));
|
||||
|
||||
log_debug_errno(r, "rename_process(%s): %m", p);
|
||||
|
||||
if (r < 0)
|
||||
return;
|
||||
|
||||
#if HAVE_VALGRIND_VALGRIND_H
|
||||
/* see above, valgrind is weird, we can't verify what we are doing here */
|
||||
if (RUNNING_ON_VALGRIND)
|
||||
return;
|
||||
#endif
|
||||
|
||||
assert_se(get_process_comm(0, &comm) >= 0);
|
||||
log_debug("comm = <%s>", comm);
|
||||
assert_se(strneq(comm, p, TASK_COMM_LEN-1));
|
||||
/* We expect comm to be at most 16 bytes (TASK_COMM_LEN). The kernel may raise this limit in the
|
||||
* future. We'd only check the initial part, at least until we recompile, but this will still pass. */
|
||||
|
||||
r = get_process_cmdline(0, SIZE_MAX, 0, &cmdline);
|
||||
assert_se(r >= 0);
|
||||
/* we cannot expect cmdline to be renamed properly without privileges */
|
||||
if (geteuid() == 0) {
|
||||
if (r == 0 && detect_container() > 0)
|
||||
log_info("cmdline = <%s> (not verified, Running in unprivileged container?)", cmdline);
|
||||
else {
|
||||
log_info("cmdline = <%s> (expected <%.*s>)", cmdline, (int) strlen("test-process-util"), p);
|
||||
|
||||
bool skip = cmdline[0] == '"'; /* A shortcut to check if the string is quoted */
|
||||
|
||||
assert_se(strneq(cmdline + skip, p, strlen("test-process-util")));
|
||||
assert_se(startswith(cmdline + skip, p));
|
||||
}
|
||||
} else
|
||||
log_info("cmdline = <%s> (not verified)", cmdline);
|
||||
}
|
||||
|
||||
static void test_rename_process_one(const char *p, int ret) {
|
||||
siginfo_t si;
|
||||
pid_t pid;
|
||||
|
||||
log_info("/* %s(%s) */", __func__, p);
|
||||
|
||||
pid = fork();
|
||||
assert_se(pid >= 0);
|
||||
|
||||
if (pid == 0) {
|
||||
/* child */
|
||||
test_rename_process_now(p, ret);
|
||||
_exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
assert_se(wait_for_terminate(pid, &si) >= 0);
|
||||
assert_se(si.si_code == CLD_EXITED);
|
||||
assert_se(si.si_status == EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
TEST(rename_process_invalid) {
|
||||
assert_se(rename_process(NULL) == -EINVAL);
|
||||
assert_se(rename_process("") == -EINVAL);
|
||||
}
|
||||
|
||||
TEST(rename_process_multi) {
|
||||
pid_t pid;
|
||||
|
||||
pid = fork();
|
||||
assert_se(pid >= 0);
|
||||
|
||||
if (pid > 0) {
|
||||
siginfo_t si;
|
||||
|
||||
assert_se(wait_for_terminate(pid, &si) >= 0);
|
||||
assert_se(si.si_code == CLD_EXITED);
|
||||
assert_se(si.si_status == EXIT_SUCCESS);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* child */
|
||||
test_rename_process_now("one", 1);
|
||||
test_rename_process_now("more", 0); /* longer than "one", hence truncated */
|
||||
(void) setresuid(99, 99, 99); /* change uid when running privileged */
|
||||
test_rename_process_now("time!", 0);
|
||||
test_rename_process_now("0", 1); /* shorter than "one", should fit */
|
||||
_exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
TEST(rename_process) {
|
||||
test_rename_process_one("foo", 1); /* should always fit */
|
||||
test_rename_process_one("this is a really really long process name, followed by some more words", 0); /* unlikely to fit */
|
||||
test_rename_process_one("1234567", 1); /* should always fit */
|
||||
}
|
||||
|
||||
static int intro(void) {
|
||||
log_show_color(true);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
DEFINE_TEST_MAIN_WITH_INTRO(LOG_INFO, intro);
|
|
@ -11,7 +11,6 @@
|
|||
#include "macro.h"
|
||||
#include "module-util.h"
|
||||
#include "tests.h"
|
||||
#include "util.h"
|
||||
|
||||
static int load_module(const char *mod_name) {
|
||||
_cleanup_(kmod_unrefp) struct kmod_ctx *ctx = NULL;
|
||||
|
|
|
@ -524,108 +524,6 @@ TEST(get_process_cmdline_harder) {
|
|||
_exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
static void test_rename_process_now(const char *p, int ret) {
|
||||
_cleanup_free_ char *comm = NULL, *cmdline = NULL;
|
||||
int r;
|
||||
|
||||
log_info("/* %s(%s) */", __func__, p);
|
||||
|
||||
r = rename_process(p);
|
||||
assert_se(r == ret ||
|
||||
(ret == 0 && r >= 0) ||
|
||||
(ret > 0 && r > 0));
|
||||
|
||||
log_debug_errno(r, "rename_process(%s): %m", p);
|
||||
|
||||
if (r < 0)
|
||||
return;
|
||||
|
||||
#if HAVE_VALGRIND_VALGRIND_H
|
||||
/* see above, valgrind is weird, we can't verify what we are doing here */
|
||||
if (RUNNING_ON_VALGRIND)
|
||||
return;
|
||||
#endif
|
||||
|
||||
assert_se(get_process_comm(0, &comm) >= 0);
|
||||
log_debug("comm = <%s>", comm);
|
||||
assert_se(strneq(comm, p, TASK_COMM_LEN-1));
|
||||
/* We expect comm to be at most 16 bytes (TASK_COMM_LEN). The kernel may raise this limit in the
|
||||
* future. We'd only check the initial part, at least until we recompile, but this will still pass. */
|
||||
|
||||
r = get_process_cmdline(0, SIZE_MAX, 0, &cmdline);
|
||||
assert_se(r >= 0);
|
||||
/* we cannot expect cmdline to be renamed properly without privileges */
|
||||
if (geteuid() == 0) {
|
||||
if (r == 0 && detect_container() > 0)
|
||||
log_info("cmdline = <%s> (not verified, Running in unprivileged container?)", cmdline);
|
||||
else {
|
||||
log_info("cmdline = <%s> (expected <%.*s>)", cmdline, (int) strlen("test-process-util"), p);
|
||||
|
||||
bool skip = cmdline[0] == '"'; /* A shortcut to check if the string is quoted */
|
||||
|
||||
assert_se(strneq(cmdline + skip, p, strlen("test-process-util")));
|
||||
assert_se(startswith(cmdline + skip, p));
|
||||
}
|
||||
} else
|
||||
log_info("cmdline = <%s> (not verified)", cmdline);
|
||||
}
|
||||
|
||||
static void test_rename_process_one(const char *p, int ret) {
|
||||
siginfo_t si;
|
||||
pid_t pid;
|
||||
|
||||
log_info("/* %s(%s) */", __func__, p);
|
||||
|
||||
pid = fork();
|
||||
assert_se(pid >= 0);
|
||||
|
||||
if (pid == 0) {
|
||||
/* child */
|
||||
test_rename_process_now(p, ret);
|
||||
_exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
assert_se(wait_for_terminate(pid, &si) >= 0);
|
||||
assert_se(si.si_code == CLD_EXITED);
|
||||
assert_se(si.si_status == EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
TEST(rename_process_invalid) {
|
||||
assert_se(rename_process(NULL) == -EINVAL);
|
||||
assert_se(rename_process("") == -EINVAL);
|
||||
}
|
||||
|
||||
TEST(rename_process_multi) {
|
||||
pid_t pid;
|
||||
|
||||
pid = fork();
|
||||
assert_se(pid >= 0);
|
||||
|
||||
if (pid > 0) {
|
||||
siginfo_t si;
|
||||
|
||||
assert_se(wait_for_terminate(pid, &si) >= 0);
|
||||
assert_se(si.si_code == CLD_EXITED);
|
||||
assert_se(si.si_status == EXIT_SUCCESS);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* child */
|
||||
test_rename_process_now("one", 1);
|
||||
test_rename_process_now("more", 0); /* longer than "one", hence truncated */
|
||||
(void) setresuid(99, 99, 99); /* change uid when running privileged */
|
||||
test_rename_process_now("time!", 0);
|
||||
test_rename_process_now("0", 1); /* shorter than "one", should fit */
|
||||
_exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
TEST(rename_process) {
|
||||
test_rename_process_one("foo", 1); /* should always fit */
|
||||
test_rename_process_one("this is a really really long process name, followed by some more words", 0); /* unlikely to fit */
|
||||
test_rename_process_one("1234567", 1); /* should always fit */
|
||||
}
|
||||
|
||||
TEST(getpid_cached) {
|
||||
siginfo_t si;
|
||||
pid_t a, b, c, d, e, f, child;
|
||||
|
|
Loading…
Reference in a new issue