From 71f96c2e6ff755122e298c25dca5b9b6e4f07c6d Mon Sep 17 00:00:00 2001 From: Giuseppe Scrivano Date: Thu, 18 Aug 2022 11:34:39 +0200 Subject: [PATCH 1/2] rootless: define LIBEXECPODMAN Signed-off-by: Giuseppe Scrivano --- pkg/rootless/rootless_linux.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/rootless/rootless_linux.c b/pkg/rootless/rootless_linux.c index b47f3d2b9e..457db1881d 100644 --- a/pkg/rootless/rootless_linux.c +++ b/pkg/rootless/rootless_linux.c @@ -19,6 +19,8 @@ #include #include +#define LIBEXECPODMAN "/usr/libexec/podman" + #ifndef TEMP_FAILURE_RETRY #define TEMP_FAILURE_RETRY(expression) \ (__extension__ \ @@ -134,7 +136,7 @@ do_pause () sigaction (sig[i], &act, NULL); /* Attempt to execv catatonit to keep the pause process alive. */ - execl ("/usr/libexec/podman/catatonit", "catatonit", "-P", NULL); + execl (LIBEXECPODMAN "catatonit", "catatonit", "-P", NULL); execl ("/usr/bin/catatonit", "catatonit", "-P", NULL); /* and if the catatonit executable could not be found, fallback here... */ From 290019c486a720c29ae8344f64cbf4d76a637522 Mon Sep 17 00:00:00 2001 From: Giuseppe Scrivano Date: Thu, 18 Aug 2022 15:05:23 +0200 Subject: [PATCH 2/2] rootless: add cli validator whenever the podman process is launched, it runs any file found in these directories: - /etc/containers/auth-scripts - /usr/libexec/podman/auth-scripts The current podman command line is passed as arguments to the process. If any of the processes fail, the error is immediately reported back from podman that exits with the same error code. [NO NEW TESTS NEEDED] requires a system-wide configuration. Signed-off-by: Giuseppe Scrivano --- pkg/rootless/rootless_linux.c | 217 ++++++++++++++++++++++++++---- test/system/950-auth-scripts.bats | 33 +++++ 2 files changed, 222 insertions(+), 28 deletions(-) create mode 100644 test/system/950-auth-scripts.bats diff --git a/pkg/rootless/rootless_linux.c b/pkg/rootless/rootless_linux.c index 457db1881d..d6f09ded1f 100644 --- a/pkg/rootless/rootless_linux.c +++ b/pkg/rootless/rootless_linux.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -19,6 +20,7 @@ #include #include +#define ETC_AUTH_SCRIPTS "/etc/containers/auth-scripts" #define LIBEXECPODMAN "/usr/libexec/podman" #ifndef TEMP_FAILURE_RETRY @@ -119,6 +121,155 @@ rootless_gid () return rootless_gid_init; } +/* exec the specified executable and exit if it fails. */ +static void +exec_binary (const char *path, char **argv, int argc) +{ + int r, status = 0; + pid_t pid; + + pid = fork (); + if (pid < 0) + { + fprintf (stderr, "fork: %m\n"); + exit (EXIT_FAILURE); + } + if (pid == 0) + { + size_t i; + char **newargv = malloc ((argc + 2) * sizeof(char *)); + if (!newargv) + { + fprintf (stderr, "malloc: %m\n"); + exit (EXIT_FAILURE); + } + newargv[0] = (char*) path; + for (i = 0; i < argc; i++) + newargv[i+1] = argv[i]; + + newargv[i+1] = NULL; + errno = 0; + execv (path, newargv); + /* If the file was deleted in the meanwhile, return success. */ + if (errno == ENOENT) + exit (EXIT_SUCCESS); + exit (EXIT_FAILURE); + } + + r = TEMP_FAILURE_RETRY (waitpid (pid, &status, 0)); + if (r < 0) + { + fprintf (stderr, "waitpid: %m\n"); + exit (EXIT_FAILURE); + } + if (WIFEXITED(status) && WEXITSTATUS (status)) + { + fprintf (stderr, "external auth script %s failed\n", path); + exit (WEXITSTATUS(status)); + } + if (WIFSIGNALED (status)) + { + fprintf (stderr, "external auth script %s failed\n", path); + exit (127+WTERMSIG (status)); + } + if (WIFSTOPPED (status)) + { + fprintf (stderr, "external auth script %s failed\n", path); + exit (EXIT_FAILURE); + } +} + +static void +do_auth_scripts_dir (const char *dir, char **argv, int argc) +{ + cleanup_free char *buffer = NULL; + cleanup_dir DIR *d = NULL; + size_t i, nfiles = 0; + struct dirent *de; + + /* Store how many FDs were open before the Go runtime kicked in. */ + d = opendir (dir); + if (!d) + { + if (errno != ENOENT) + { + fprintf (stderr, "opendir %s: %m\n", dir); + exit (EXIT_FAILURE); + } + return; + } + + errno = 0; + + for (de = readdir (d); de; de = readdir (d)) + { + buffer = realloc (buffer, (nfiles + 1) * (NAME_MAX + 1)); + if (buffer == NULL) + { + fprintf (stderr, "realloc buffer: %m\n"); + exit (EXIT_FAILURE); + } + + if (de->d_type != DT_REG) + continue; + + strncpy (buffer + nfiles * (NAME_MAX + 1), de->d_name, NAME_MAX + 1); + nfiles++; + buffer[nfiles * (NAME_MAX + 1)] = '\0'; + } + + qsort (buffer, nfiles, NAME_MAX + 1, (int (*)(const void *, const void *)) strcmp); + + for (i = 0; i < nfiles; i++) + { + const char *fname = buffer + i * (NAME_MAX + 1); + char path[PATH_MAX]; + struct stat st; + int ret; + + ret = snprintf (path, PATH_MAX, "%s/%s", dir, fname); + if (ret == PATH_MAX) + { + fprintf (stderr, "internal error: path too long\n"); + exit (EXIT_FAILURE); + } + + ret = stat (path, &st); + if (ret < 0) + { + /* Ignore the failure if the file was deleted. */ + if (errno == ENOENT) + continue; + + fprintf (stderr, "stat %s: %m\n", path); + exit (EXIT_FAILURE); + } + + /* Not an executable. */ + if ((st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) == 0) + continue; + + exec_binary (path, argv, argc); + errno = 0; + } + + if (errno) + { + fprintf (stderr, "readdir %s: %m\n", dir); + exit (EXIT_FAILURE); + } +} + +static void +do_auth_scripts (char **argv, int argc) +{ + char *auth_scripts = getenv ("PODMAN_AUTH_SCRIPTS_DIR"); + do_auth_scripts_dir (LIBEXECPODMAN "/auth-scripts", argv, argc); + do_auth_scripts_dir (ETC_AUTH_SCRIPTS, argv, argc); + if (auth_scripts && auth_scripts[0]) + do_auth_scripts_dir (auth_scripts, argv, argc); +} + static void do_pause () { @@ -146,7 +297,7 @@ do_pause () } static char ** -get_cmd_line_args () +get_cmd_line_args (int *argc_out) { cleanup_free char *buffer = NULL; cleanup_close int fd = -1; @@ -206,13 +357,15 @@ get_cmd_line_args () /* Move ownership. */ buffer = NULL; + if (argc_out) + *argc_out = argc; + return argv; } static bool -can_use_shortcut () +can_use_shortcut (char **argv) { - cleanup_free char **argv = NULL; cleanup_free char *argv0 = NULL; bool ret = true; int argc; @@ -221,10 +374,6 @@ can_use_shortcut () return false; #endif - argv = get_cmd_line_args (); - if (argv == NULL) - return false; - argv0 = argv[0]; if (strstr (argv[0], "podman") == NULL) @@ -289,7 +438,9 @@ static void __attribute__((constructor)) init() const char *listen_pid; const char *listen_fds; const char *listen_fdnames; + cleanup_free char **argv = NULL; cleanup_dir DIR *d = NULL; + int argc; pause = getenv ("_PODMAN_PAUSE"); if (pause && pause[0]) @@ -339,30 +490,40 @@ static void __attribute__((constructor)) init() } } - listen_pid = getenv("LISTEN_PID"); - listen_fds = getenv("LISTEN_FDS"); - listen_fdnames = getenv("LISTEN_FDNAMES"); + argv = get_cmd_line_args (&argc); + if (argv == NULL) + { + fprintf(stderr, "cannot retrieve cmd line"); + _exit (EXIT_FAILURE); + } - if (listen_pid != NULL && listen_fds != NULL && strtol(listen_pid, NULL, 10) == getpid()) - { - // save systemd socket environment for rootless child - do_socket_activation = true; - saved_systemd_listen_pid = strdup(listen_pid); - saved_systemd_listen_fds = strdup(listen_fds); - if (listen_fdnames != NULL) - saved_systemd_listen_fdnames = strdup(listen_fdnames); - if (saved_systemd_listen_pid == NULL - || saved_systemd_listen_fds == NULL) - { - fprintf (stderr, "save socket listen environments error: %m\n"); - _exit (EXIT_FAILURE); - } - } + if (geteuid () != 0 || getenv ("_CONTAINERS_USERNS_CONFIGURED") == NULL) + do_auth_scripts(argv, argc); + + listen_pid = getenv("LISTEN_PID"); + listen_fds = getenv("LISTEN_FDS"); + listen_fdnames = getenv("LISTEN_FDNAMES"); + + if (listen_pid != NULL && listen_fds != NULL && strtol(listen_pid, NULL, 10) == getpid()) + { + // save systemd socket environment for rootless child + do_socket_activation = true; + saved_systemd_listen_pid = strdup(listen_pid); + saved_systemd_listen_fds = strdup(listen_fds); + if (listen_fdnames != NULL) + saved_systemd_listen_fdnames = strdup(listen_fdnames); + if (saved_systemd_listen_pid == NULL + || saved_systemd_listen_fds == NULL) + { + fprintf (stderr, "save socket listen environments error: %m\n"); + _exit (EXIT_FAILURE); + } + } /* Shortcut. If we are able to join the pause pid file, do it now so we don't need to re-exec. */ xdg_runtime_dir = getenv ("XDG_RUNTIME_DIR"); - if (geteuid () != 0 && xdg_runtime_dir && xdg_runtime_dir[0] && can_use_shortcut ()) + if (geteuid () != 0 && xdg_runtime_dir && xdg_runtime_dir[0] && can_use_shortcut (argv)) { cleanup_free char *cwd = NULL; cleanup_close int userns_fd = -1; @@ -650,7 +811,7 @@ reexec_userns_join (int pid_to_join, char *pause_pid_file_path) sprintf (uid, "%d", geteuid ()); sprintf (gid, "%d", getegid ()); - argv = get_cmd_line_args (); + argv = get_cmd_line_args (NULL); if (argv == NULL) { fprintf (stderr, "cannot read argv: %m\n"); @@ -900,7 +1061,7 @@ reexec_in_user_namespace (int ready, char *pause_pid_file_path, char *file_to_re _exit (EXIT_FAILURE); } - argv = get_cmd_line_args (); + argv = get_cmd_line_args (NULL); if (argv == NULL) { fprintf (stderr, "cannot read argv: %m\n"); diff --git a/test/system/950-auth-scripts.bats b/test/system/950-auth-scripts.bats new file mode 100644 index 0000000000..17ce717998 --- /dev/null +++ b/test/system/950-auth-scripts.bats @@ -0,0 +1,33 @@ +#!/usr/bin/env bats +# +# Tests for podman auth scripts +# + +load helpers +load helpers.network + +function setup() { + basic_setup +} + +function teardown() { + basic_teardown +} + +@test "podman auth script" { + auth_dir=$PODMAN_TMPDIR/auth + mkdir -p $auth_dir + auth_script=$auth_dir/pull_check.sh + + cat > $auth_script <