#############################################################################*/
#include <errno.h>
+#include <fcntl.h>
#include <linux/capability.h>
-#include <linux/fcntl.h>
#include <linux/sched.h>
+#include <sys/wait.h>
#include <linux/wait.h>
#include <sched.h>
#include <signal.h>
#include <sys/capability.h>
#include <sys/epoll.h>
#include <sys/eventfd.h>
+#include <sys/mount.h>
#include <sys/personality.h>
#include <sys/prctl.h>
#include <sys/resource.h>
+#include <sys/timerfd.h>
#include <sys/types.h>
#include <sys/wait.h>
+// libnl3
+#include <net/if.h>
+#include <netlink/route/link.h>
+
// libseccomp
#include <seccomp.h>
#include <pakfire/logging.h>
#include <pakfire/mount.h>
#include <pakfire/pakfire.h>
+#include <pakfire/path.h>
#include <pakfire/private.h>
#include <pakfire/pwd.h>
+#include <pakfire/string.h>
#include <pakfire/util.h>
#define BUFFER_SIZE 1024 * 64
const char* key;
const char* val;
} ENV[] = {
- { "LANG", "en_US.utf-8" },
+ { "HOME", "/root" },
+ { "LANG", "C.utf-8" },
+ { "PATH", "/usr/local/sbin:/usr/sbin:/sbin:/usr/local/bin:/usr/bin:/bin", },
{ "TERM", "vt100" },
+
+ // Tell everything that it is running inside a Pakfire container
+ { "container", "pakfire" },
{ NULL, NULL },
};
};
struct pakfire_jail {
+ struct pakfire_ctx* ctx;
struct pakfire* pakfire;
int nrefs;
uuid_t uuid;
char __uuid[UUID_STR_LEN];
- // Flags
- int flags;
-
// Resource Limits
int nice;
+ // Timeout
+ struct itimerspec timeout;
+
// CGroup
struct pakfire_cgroup* cgroup;
// Environment
char* env[ENVIRON_SIZE];
- // Logging
- pakfire_jail_log_callback log_callback;
- void* log_data;
-
// Mountpoints
struct pakfire_jail_mountpoint mountpoints[MAX_MOUNTPOINTS];
unsigned int num_mountpoints;
+
+ // Callbacks
+ struct pakfire_jail_callbacks {
+ // Log
+ pakfire_jail_log_callback log;
+ void* log_data;
+ } callbacks;
};
struct pakfire_log_buffer {
};
struct pakfire_jail_exec {
+ int flags;
+
// PID (of the child)
pid_t pid;
int pidfd;
// Log pipes
struct pakfire_jail_pipes {
+ int stdin[2];
int stdout[2];
int stderr[2];
// Logging
int log_INFO[2];
int log_ERROR[2];
+#ifdef ENABLE_DEBUG
int log_DEBUG[2];
+#endif /* ENABLE_DEBUG */
} pipes;
+ // Communicate
+ struct pakfire_jail_communicate {
+ pakfire_jail_communicate_in in;
+ pakfire_jail_communicate_out out;
+ void* data;
+ } communicate;
+
// Log buffers
struct pakfire_jail_buffers {
struct pakfire_log_buffer stdout;
// Logging
struct pakfire_log_buffer log_INFO;
struct pakfire_log_buffer log_ERROR;
+#ifdef ENABLE_DEBUG
struct pakfire_log_buffer log_DEBUG;
+#endif /* ENABLE_DEBUG */
} buffers;
struct pakfire_cgroup* cgroup;
struct pakfire_cgroup_stats cgroup_stats;
+
+ // Console
+ char console[PATH_MAX];
+ int consolefd;
};
static int clone3(struct clone_args* args, size_t size) {
return syscall(__NR_clone3, args, size);
}
+static int pidfd_send_signal(int pidfd, int sig, siginfo_t* info, unsigned int flags) {
+ return syscall(SYS_pidfd_send_signal, pidfd, sig, info, flags);
+}
+
+static int pivot_root(const char* new_root, const char* old_root) {
+ return syscall(SYS_pivot_root, new_root, old_root);
+}
+
+static int pakfire_jail_exec_has_flag(
+ const struct pakfire_jail_exec* ctx, const enum pakfire_jail_exec_flags flag) {
+ return ctx->flags & flag;
+}
+
static void pakfire_jail_free(struct pakfire_jail* jail) {
DEBUG(jail->pakfire, "Freeing jail at %p\n", jail);
if (jail->cgroup)
pakfire_cgroup_unref(jail->cgroup);
-
- pakfire_unref(jail->pakfire);
+ if (jail->pakfire)
+ pakfire_unref(jail->pakfire);
+ if (jail->ctx)
+ pakfire_ctx_unref(jail->ctx);
free(jail);
}
return 0;
}
+static const char* pakfire_jail_uuid(struct pakfire_jail* jail) {
+ if (!*jail->__uuid)
+ uuid_unparse_lower(jail->uuid, jail->__uuid);
+
+ return jail->__uuid;
+}
+
static int pakfire_jail_setup_interactive_env(struct pakfire_jail* jail) {
// Set PS1
int r = pakfire_jail_set_env(jail, "PS1", "pakfire-jail \\w> ");
return 0;
}
-PAKFIRE_EXPORT int pakfire_jail_create(struct pakfire_jail** jail,
- struct pakfire* pakfire, int flags) {
+PAKFIRE_EXPORT int pakfire_jail_create(struct pakfire_jail** jail, struct pakfire* pakfire) {
int r;
+ const char* arch = pakfire_get_effective_arch(pakfire);
+
// Allocate a new jail
struct pakfire_jail* j = calloc(1, sizeof(*j));
if (!j)
return 1;
+ // Reference context
+ j->ctx = pakfire_ctx(pakfire);
+
// Reference Pakfire
j->pakfire = pakfire_ref(pakfire);
// Initialize reference counter
j->nrefs = 1;
- // Store flags
- j->flags = flags;
-
// Generate a random UUID
uuid_generate_random(j->uuid);
DEBUG(j->pakfire, "Allocated new jail at %p\n", j);
- // Set default log callback
- r = pakfire_jail_set_log_callback(j, pakfire_jail_default_log_callback, NULL);
- if (r)
- goto ERROR;
+ // Set the default logging callback
+ pakfire_jail_set_log_callback(j, pakfire_jail_default_log_callback, NULL);
// Set default environment
for (const struct environ* e = ENV; e->key; e++) {
goto ERROR;
}
- // Setup interactive stuff
- if (j->flags & PAKFIRE_JAIL_INTERACTIVE) {
- r = pakfire_jail_setup_interactive_env(j);
+ // Enable all CPU features that CPU has to offer
+ if (!pakfire_arch_is_supported_by_host(arch)) {
+ r = pakfire_jail_set_env(j, "QEMU_CPU", "max");
+ if (r)
+ goto ERROR;
+ }
+
+ // Set container UUID
+ r = pakfire_jail_set_env(j, "container_uuid", pakfire_jail_uuid(j));
+ if (r)
+ goto ERROR;
+
+ // Disable systemctl to talk to systemd
+ if (!pakfire_on_root(j->pakfire)) {
+ r = pakfire_jail_set_env(j, "SYSTEMD_OFFLINE", "1");
if (r)
goto ERROR;
}
return NULL;
}
-static int pakfire_jail_has_flag(struct pakfire_jail* jail, int flag) {
- return jail->flags & flag;
-}
-
-static const char* pakfire_jail_uuid(struct pakfire_jail* jail) {
- if (*jail->__uuid)
- uuid_unparse_lower(jail->uuid, jail->__uuid);
+// Logging Callback
- return jail->__uuid;
+PAKFIRE_EXPORT void pakfire_jail_set_log_callback(struct pakfire_jail* jail,
+ pakfire_jail_log_callback callback, void* data) {
+ jail->callbacks.log = callback;
+ jail->callbacks.log_data = data;
}
// Resource Limits
return -1;
}
- char buffer[strlen(key) + 2];
- pakfire_string_format(buffer, "%s=", key);
+ const size_t length = strlen(key);
for (unsigned int i = 0; jail->env[i]; i++) {
- if (pakfire_string_startswith(jail->env[i], buffer))
+ if ((pakfire_string_startswith(jail->env[i], key)
+ && *(jail->env[i] + length) == '=')) {
return i;
+ }
}
// Nothing found
return 0;
}
-// Logging
+// Timeout
-PAKFIRE_EXPORT int pakfire_jail_set_log_callback(struct pakfire_jail* jail,
- pakfire_jail_log_callback callback, void* data) {
- jail->log_callback = callback;
- jail->log_data = data;
+PAKFIRE_EXPORT int pakfire_jail_set_timeout(
+ struct pakfire_jail* jail, unsigned int timeout) {
+ // Store value
+ jail->timeout.it_value.tv_sec = timeout;
+
+ if (timeout > 0)
+ DEBUG(jail->pakfire, "Timeout set to %u second(s)\n", timeout);
+ else
+ DEBUG(jail->pakfire, "Timeout disabled\n");
return 0;
}
+static int pakfire_jail_create_timer(struct pakfire_jail* jail) {
+ int r;
+
+ // Nothing to do if no timeout has been set
+ if (!jail->timeout.it_value.tv_sec)
+ return -1;
+
+ // Create a new timer
+ const int fd = timerfd_create(CLOCK_MONOTONIC, 0);
+ if (fd < 0) {
+ ERROR(jail->pakfire, "Could not create timer: %m\n");
+ goto ERROR;
+ }
+
+ // Arm timer
+ r = timerfd_settime(fd, 0, &jail->timeout, NULL);
+ if (r) {
+ ERROR(jail->pakfire, "Could not arm timer: %m\n");
+ goto ERROR;
+ }
+
+ return fd;
+
+ERROR:
+ if (fd >= 0)
+ close(fd);
+
+ return -1;
+}
+
/*
This function replaces any logging in the child process.
All log messages will be sent to the parent process through their respective pipes.
*/
-static void pakfire_jail_log(void* data, int priority, const char* file,
+static void pakfire_jail_log_redirect(void* data, int priority, const char* file,
int line, const char* fn, const char* format, va_list args) {
struct pakfire_jail_pipes* pipes = (struct pakfire_jail_pipes*)data;
int fd;
}
// Send the log message
- if (fd)
+ if (fd >= 0)
vdprintf(fd, format, args);
}
*/
static int pakfire_jail_handle_log(struct pakfire_jail* jail,
struct pakfire_jail_exec* ctx, int priority, int fd,
- struct pakfire_log_buffer* buffer, pakfire_jail_log_callback callback, void* data) {
+ struct pakfire_log_buffer* buffer, pakfire_jail_communicate_out callback, void* data) {
char line[BUFFER_SIZE + 1];
// Fill up buffer from fd
return 0;
}
+static int pakfire_jail_stream_stdin(struct pakfire_jail* jail,
+ struct pakfire_jail_exec* ctx, const int fd) {
+ int r;
+
+ // Nothing to do if there is no stdin callback set
+ if (!ctx->communicate.in) {
+ DEBUG(jail->pakfire, "Callback for standard input is not set\n");
+ return 0;
+ }
+
+ // Skip if the writing pipe has already been closed
+ if (!ctx->pipes.stdin[1])
+ return 0;
+
+ DEBUG(jail->pakfire, "Streaming standard input...\n");
+
+ // Calling the callback
+ r = ctx->communicate.in(jail->pakfire, ctx->communicate.data, fd);
+
+ DEBUG(jail->pakfire, "Standard input callback finished: %d\n", r);
+
+ // The callback signaled that it has written everything
+ if (r == EOF) {
+ DEBUG(jail->pakfire, "Closing standard input pipe\n");
+
+ // Close the file-descriptor
+ close(fd);
+
+ // Reset the file-descriptor so it won't be closed again later
+ ctx->pipes.stdin[1] = -1;
+
+ // Report success
+ r = 0;
+ }
+
+ return r;
+}
+
static int pakfire_jail_setup_pipe(struct pakfire_jail* jail, int (*fds)[2], const int flags) {
int r = pipe2(*fds, flags);
if (r < 0) {
static void pakfire_jail_close_pipe(struct pakfire_jail* jail, int fds[2]) {
for (unsigned int i = 0; i < 2; i++)
- if (fds[i])
+ if (fds[i] >= 0)
close(fds[i]);
}
This is a convenience function to fetch the reading end of a pipe and
closes the write end.
*/
-static int pakfire_jail_get_pipe(struct pakfire_jail* jail, int (*fds)[2]) {
+static int pakfire_jail_get_pipe_to_read(struct pakfire_jail* jail, int (*fds)[2]) {
// Give the variables easier names to avoid confusion
int* fd_read = &(*fds)[0];
int* fd_write = &(*fds)[1];
// Close the write end of the pipe
- if (*fd_write) {
+ if (*fd_write >= 0) {
close(*fd_write);
- *fd_write = 0;
+ *fd_write = -1;
}
// Return the read end
- return *fd_read;
+ if (*fd_read >= 0)
+ return *fd_read;
+
+ return -1;
+}
+
+static int pakfire_jail_get_pipe_to_write(struct pakfire_jail* jail, int (*fds)[2]) {
+ // Give the variables easier names to avoid confusion
+ int* fd_read = &(*fds)[0];
+ int* fd_write = &(*fds)[1];
+
+ // Close the read end of the pipe
+ if (*fd_read >= 0) {
+ close(*fd_read);
+ *fd_read = -1;
+ }
+
+ // Return the write end
+ if (*fd_write >= 0)
+ return *fd_write;
+
+ return -1;
+}
+
+static int pakfire_jail_log(struct pakfire* pakfire, void* data, int priority,
+ const char* line, const size_t length) {
+ // Pass everything to the parent logger
+ pakfire_log_condition(pakfire, priority, 0, "%.*s", (int)length, line);
+
+ return 0;
+}
+
+static int pakfire_jail_epoll_add_fd(struct pakfire_jail* jail, int epollfd, int fd, int events) {
+ struct epoll_event event = {
+ .events = events|EPOLLHUP,
+ .data = {
+ .fd = fd,
+ },
+ };
+ int r;
+
+ // Read flags
+ int flags = fcntl(fd, F_GETFL, 0);
+
+ // Set modified flags
+ r = fcntl(fd, F_SETFL, flags|O_NONBLOCK);
+ if (r < 0) {
+ CTX_ERROR(jail->ctx, "Could not set file descriptor %d into non-blocking mode: %s\n",
+ fd, strerror(errno));
+ return -errno;
+ }
+
+ // Add the file descriptor to the loop
+ r = epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);
+ if (r < 0) {
+ ERROR(jail->pakfire, "Could not add file descriptor %d to epoll(): %s\n",
+ fd, strerror(errno));
+ return -errno;
+ }
+
+ return 0;
}
static int pakfire_jail_wait(struct pakfire_jail* jail, struct pakfire_jail_exec* ctx) {
int epollfd = -1;
- struct epoll_event ev;
struct epoll_event events[EPOLL_MAX_EVENTS];
+ char garbage[8];
int r = 0;
// Fetch file descriptors from context
- const int stdout = pakfire_jail_get_pipe(jail, &ctx->pipes.stdout);
- const int stderr = pakfire_jail_get_pipe(jail, &ctx->pipes.stderr);
+ const int stdin = pakfire_jail_get_pipe_to_write(jail, &ctx->pipes.stdin);
+ const int stdout = pakfire_jail_get_pipe_to_read(jail, &ctx->pipes.stdout);
+ const int stderr = pakfire_jail_get_pipe_to_read(jail, &ctx->pipes.stderr);
const int pidfd = ctx->pidfd;
+ // Timer
+ const int timerfd = pakfire_jail_create_timer(jail);
+
// Logging
- const int log_INFO = pakfire_jail_get_pipe(jail, &ctx->pipes.log_INFO);
- const int log_ERROR = pakfire_jail_get_pipe(jail, &ctx->pipes.log_ERROR);
- const int log_DEBUG = pakfire_jail_get_pipe(jail, &ctx->pipes.log_DEBUG);
+ const int log_INFO = pakfire_jail_get_pipe_to_read(jail, &ctx->pipes.log_INFO);
+ const int log_ERROR = pakfire_jail_get_pipe_to_read(jail, &ctx->pipes.log_ERROR);
+#ifdef ENABLE_DEBUG
+ const int log_DEBUG = pakfire_jail_get_pipe_to_read(jail, &ctx->pipes.log_DEBUG);
+#endif /* ENABLE_DEBUG */
// Make a list of all file descriptors we are interested in
- int fds[] = {
- stdout, stderr, pidfd, log_INFO, log_ERROR, log_DEBUG,
+ const struct pakfire_wait_fds {
+ const int fd;
+ const int events;
+ } fds[] = {
+ // Standard input/output
+ { stdin, EPOLLOUT },
+ { stdout, EPOLLIN },
+ { stderr, EPOLLIN },
+
+ // Timer
+ { timerfd, EPOLLIN },
+
+ // Child Process
+ { ctx->pidfd, EPOLLIN },
+
+ // Log Pipes
+ { log_INFO, EPOLLIN },
+ { log_ERROR, EPOLLIN },
+#ifdef ENABLE_DEBUG
+ { log_DEBUG, EPOLLIN },
+#endif /* ENABLE_DEBUG */
+
+ // Sentinel
+ { -1, 0 },
};
// Setup epoll
goto ERROR;
}
- ev.events = EPOLLIN;
-
// Turn file descriptors into non-blocking mode and add them to epoll()
- for (unsigned int i = 0; i < sizeof(fds) / sizeof(*fds); i++) {
- int fd = fds[i];
-
+ for (const struct pakfire_wait_fds* fd = fds; fd->events; fd++) {
// Skip fds which were not initialized
- if (fd <= 0)
+ if (fd->fd < 0)
continue;
- ev.data.fd = fd;
-
- if (epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev) < 0) {
- ERROR(jail->pakfire, "Could not add file descriptor %d to epoll(): %m\n", fd);
- r = 1;
+ // Add the FD to the event loop
+ r = pakfire_jail_epoll_add_fd(jail, epollfd, fd->fd, fd->events);
+ if (r)
goto ERROR;
- }
}
int ended = 0;
}
for (int i = 0; i < num; i++) {
+ int e = events[i].events;
int fd = events[i].data.fd;
struct pakfire_log_buffer* buffer = NULL;
- pakfire_jail_log_callback callback = NULL;
+ pakfire_jail_communicate_out callback = NULL;
void* data = NULL;
int priority;
- // Handle any changes to the PIDFD
- if (fd == pidfd) {
- // Call waidid() and store the result
- r = waitid(P_PIDFD, ctx->pidfd, &ctx->status, WEXITED);
- if (r) {
- ERROR(jail->pakfire, "waitid() failed: %m\n");
- goto ERROR;
- }
-
- // Mark that we have ended so that we will process the remaining
- // events from epoll() now, but won't restart the outer loop.
- ended = 1;
- continue;
-
- // Handle logging messages
- } else if (fd == log_INFO) {
- buffer = &ctx->buffers.log_INFO;
- priority = LOG_INFO;
+ // Check if there is any data to be read
+ if (e & EPOLLIN) {
+ // Handle any changes to the PIDFD
+ if (fd == pidfd) {
+ // Call waidid() and store the result
+ r = waitid(P_PIDFD, ctx->pidfd, &ctx->status, WEXITED);
+ if (r) {
+ ERROR(jail->pakfire, "waitid() failed: %m\n");
+ goto ERROR;
+ }
+
+ // Mark that we have ended so that we will process the remaining
+ // events from epoll() now, but won't restart the outer loop.
+ ended = 1;
+ continue;
+
+ // Handle timer events
+ } else if (fd == timerfd) {
+ DEBUG(jail->pakfire, "Timer event received\n");
+
+ // Disarm the timer
+ r = read(timerfd, garbage, sizeof(garbage));
+ if (r < 1) {
+ ERROR(jail->pakfire, "Could not disarm timer: %m\n");
+ r = 1;
+ goto ERROR;
+ }
+
+ // Terminate the process if it hasn't already ended
+ if (!ended) {
+ DEBUG(jail->pakfire, "Terminating process...\n");
+
+ // Send SIGTERM to the process
+ r = pidfd_send_signal(pidfd, SIGKILL, NULL, 0);
+ if (r) {
+ ERROR(jail->pakfire, "Could not kill process: %m\n");
+ goto ERROR;
+ }
+ }
+
+ // Don't fall through to log processing
+ continue;
+
+ // Handle logging messages
+ } else if (fd == log_INFO) {
+ buffer = &ctx->buffers.log_INFO;
+ priority = LOG_INFO;
+
+ callback = pakfire_jail_log;
+
+ } else if (fd == log_ERROR) {
+ buffer = &ctx->buffers.log_ERROR;
+ priority = LOG_ERR;
+
+ callback = pakfire_jail_log;
- callback = pakfire_jail_default_log_callback;
-
- } else if (fd == log_ERROR) {
- buffer = &ctx->buffers.log_ERROR;
- priority = LOG_ERR;
-
- callback = pakfire_jail_default_log_callback;
-
- } else if (fd == log_DEBUG) {
- buffer = &ctx->buffers.log_DEBUG;
- priority = LOG_DEBUG;
-
- callback = pakfire_jail_default_log_callback;
-
- // Handle anything from the log pipes
- } else if (fd == stdout) {
- buffer = &ctx->buffers.stdout;
- priority = LOG_INFO;
+#ifdef ENABLE_DEBUG
+ } else if (fd == log_DEBUG) {
+ buffer = &ctx->buffers.log_DEBUG;
+ priority = LOG_DEBUG;
- callback = jail->log_callback;
- data = jail->log_data;
+ callback = pakfire_jail_log;
+#endif /* ENABLE_DEBUG */
- } else if (fd == stderr) {
- buffer = &ctx->buffers.stderr;
- priority = LOG_ERR;
+ // Handle anything from the log pipes
+ } else if (fd == stdout) {
+ buffer = &ctx->buffers.stdout;
+ priority = LOG_INFO;
+
+ // Send any output to the default logger if no callback is set
+ if (ctx->communicate.out) {
+ callback = ctx->communicate.out;
+ data = ctx->communicate.data;
+ } else {
+ callback = jail->callbacks.log;
+ data = jail->callbacks.log_data;
+ }
+
+ } else if (fd == stderr) {
+ buffer = &ctx->buffers.stderr;
+ priority = LOG_ERR;
+
+ // Send any output to the default logger if no callback is set
+ if (ctx->communicate.out) {
+ callback = ctx->communicate.out;
+ data = ctx->communicate.data;
+ } else {
+ callback = jail->callbacks.log;
+ data = jail->callbacks.log_data;
+ }
+
+ } else {
+ DEBUG(jail->pakfire, "Received invalid file descriptor %d\n", fd);
+ continue;
+ }
- callback = jail->log_callback;
- data = jail->log_data;
+ // Handle log event
+ r = pakfire_jail_handle_log(jail, ctx, priority, fd, buffer, callback, data);
+ if (r)
+ goto ERROR;
+ }
- } else {
- DEBUG(jail->pakfire, "Received invalid file descriptor %d\n", fd);
- continue;
+ if (e & EPOLLOUT) {
+ // Handle standard input
+ if (fd == stdin) {
+ r = pakfire_jail_stream_stdin(jail, ctx, fd);
+ if (r) {
+ switch (errno) {
+ // Ignore if we filled up the buffer
+ case EAGAIN:
+ break;
+
+ default:
+ ERROR(jail->pakfire, "Could not write to stdin: %m\n");
+ goto ERROR;
+ }
+ }
+ }
}
- // Handle log event
- r = pakfire_jail_handle_log(jail, ctx, priority, fd, buffer, callback, data);
- if (r)
- goto ERROR;
+ // Check if any file descriptors have been closed
+ if (e & EPOLLHUP) {
+ // Remove the file descriptor
+ r = epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, NULL);
+ if (r) {
+ ERROR(jail->pakfire, "Could not remove closed file-descriptor %d: %m\n", fd);
+ goto ERROR;
+ }
+ }
}
}
ERROR:
- if (epollfd > 0)
+ if (epollfd >= 0)
close(epollfd);
+ if (timerfd >= 0)
+ close(timerfd);
return r;
}
-static int pakfire_jail_capture_stdout(struct pakfire* pakfire, void* data, int priority,
- const char* line, size_t length) {
+int pakfire_jail_capture_stdout(struct pakfire* pakfire, void* data,
+ int priority, const char* line, size_t length) {
char** output = (char**)data;
int r;
- // Append everything from stdout to a buffer
- if (priority == LOG_INFO) {
+ // Append everything from stdout to a buffer
+ if (output && priority == LOG_INFO) {
r = asprintf(output, "%s%s", (output && *output) ? *output : "", line);
if (r < 0)
return 1;
// Capabilities
-static int pakfire_jail_drop_capabilities(struct pakfire_jail* jail) {
- const int capabilities[] = {
- // Deny access to the kernel's audit system
- CAP_AUDIT_CONTROL,
- CAP_AUDIT_READ,
- CAP_AUDIT_WRITE,
-
- // Deny suspending block devices
- CAP_BLOCK_SUSPEND,
-
- // Deny any stuff with BPF
- CAP_BPF,
-
- // Deny checkpoint restore
- CAP_CHECKPOINT_RESTORE,
-
- // Deny opening files by inode number (open_by_handle_at)
- CAP_DAC_READ_SEARCH,
-
- // Deny setting SUID bits
- CAP_FSETID,
+// Logs all capabilities of the current process
+static int pakfire_jail_show_capabilities(struct pakfire_jail* jail) {
+ cap_t caps = NULL;
+ char* name = NULL;
+ cap_flag_value_t value_e;
+ cap_flag_value_t value_i;
+ cap_flag_value_t value_p;
+ int r;
- // Deny locking more memory
- CAP_IPC_LOCK,
+ // Fetch PID
+ pid_t pid = getpid();
- // Deny modifying any Apparmor/SELinux/SMACK configuration
- CAP_MAC_ADMIN,
- CAP_MAC_OVERRIDE,
+ // Fetch all capabilities
+ caps = cap_get_proc();
+ if (!caps) {
+ ERROR(jail->pakfire, "Could not fetch capabilities: %m\n");
+ r = 1;
+ goto ERROR;
+ }
- // Deny creating any special devices
- CAP_MKNOD,
+ DEBUG(jail->pakfire, "Capabilities of PID %d:\n", pid);
- // Deny setting any capabilities
- CAP_SETFCAP,
+ // Iterate over all capabilities
+ for (unsigned int cap = 0; cap_valid(cap); cap++) {
+ name = cap_to_name(cap);
- // Deny reading from syslog
- CAP_SYSLOG,
+ // Fetch effective value
+ r = cap_get_flag(caps, cap, CAP_EFFECTIVE, &value_e);
+ if (r)
+ goto ERROR;
- // Deny any admin actions (mount, sethostname, ...)
- CAP_SYS_ADMIN,
+ // Fetch inheritable value
+ r = cap_get_flag(caps, cap, CAP_INHERITABLE, &value_i);
+ if (r)
+ goto ERROR;
- // Deny rebooting the system
- CAP_SYS_BOOT,
+ // Fetch permitted value
+ r = cap_get_flag(caps, cap, CAP_PERMITTED, &value_p);
+ if (r)
+ goto ERROR;
- // Deny loading kernel modules
- CAP_SYS_MODULE,
+ DEBUG(jail->pakfire,
+ " %-24s : %c%c%c\n",
+ name,
+ (value_e == CAP_SET) ? 'e' : '-',
+ (value_i == CAP_SET) ? 'i' : '-',
+ (value_p == CAP_SET) ? 'p' : '-'
+ );
+
+ // Free name
+ cap_free(name);
+ name = NULL;
+ }
- // Deny setting nice level
- CAP_SYS_NICE,
+ // Success
+ r = 0;
- // Deny access to /proc/kcore, /dev/mem, /dev/kmem
- CAP_SYS_RAWIO,
+ERROR:
+ if (name)
+ cap_free(name);
+ if (caps)
+ cap_free(caps);
- // Deny circumventing any resource limits
- CAP_SYS_RESOURCE,
+ return r;
+}
- // Deny setting the system time
- CAP_SYS_TIME,
+static int pakfire_jail_set_capabilities(struct pakfire_jail* jail) {
+ cap_t caps = NULL;
+ char* name = NULL;
+ int r;
- // Deny playing with suspend
- CAP_WAKE_ALARM,
+ // Fetch capabilities
+ caps = cap_get_proc();
+ if (!caps) {
+ ERROR(jail->pakfire, "Could not read capabilities: %m\n");
+ r = 1;
+ goto ERROR;
+ }
- 0,
- };
+ // Walk through all capabilities
+ for (cap_value_t cap = 0; cap_valid(cap); cap++) {
+ cap_value_t _caps[] = { cap };
- DEBUG(jail->pakfire, "Dropping capabilities...\n");
+ // Fetch the name of the capability
+ name = cap_to_name(cap);
- size_t num_caps = 0;
- int r;
+ r = cap_set_flag(caps, CAP_EFFECTIVE, 1, _caps, CAP_SET);
+ if (r) {
+ ERROR(jail->pakfire, "Could not set %s: %m\n", name);
+ goto ERROR;
+ }
- // Drop any capabilities
- for (const int* cap = capabilities; *cap; cap++) {
- r = prctl(PR_CAPBSET_DROP, *cap, 0, 0, 0);
+ r = cap_set_flag(caps, CAP_INHERITABLE, 1, _caps, CAP_SET);
if (r) {
- ERROR(jail->pakfire, "Could not drop capability %d: %m\n", *cap);
- return r;
+ ERROR(jail->pakfire, "Could not set %s: %m\n", name);
+ goto ERROR;
}
- num_caps++;
- }
+ r = cap_set_flag(caps, CAP_PERMITTED, 1, _caps, CAP_SET);
+ if (r) {
+ ERROR(jail->pakfire, "Could not set %s: %m\n", name);
+ goto ERROR;
+ }
- // Fetch any capabilities
- cap_t caps = cap_get_proc();
- if (!caps) {
- ERROR(jail->pakfire, "Could not read capabilities: %m\n");
- return 1;
+ // Free name
+ cap_free(name);
+ name = NULL;
}
- /*
- Set inheritable capabilities
-
- This ensures that no processes will be able to gain any of the listed
- capabilities again.
- */
- r = cap_set_flag(caps, CAP_INHERITABLE, num_caps, capabilities, CAP_CLEAR);
+ // Restore all capabilities
+ r = cap_set_proc(caps);
if (r) {
- ERROR(jail->pakfire, "cap_set_flag() failed: %m\n");
+ ERROR(jail->pakfire, "Restoring capabilities failed: %m\n");
goto ERROR;
}
- // Restore capabilities
- r = cap_set_proc(caps);
- if (r) {
- ERROR(jail->pakfire, "Could not restore capabilities: %m\n");
- goto ERROR;
+ // Add all capabilities to the ambient set
+ for (unsigned int cap = 0; cap_valid(cap); cap++) {
+ name = cap_to_name(cap);
+
+ // Raise the capability
+ r = prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, cap, 0, 0);
+ if (r) {
+ ERROR(jail->pakfire, "Could not set ambient capability %s: %m\n", name);
+ goto ERROR;
+ }
+
+ // Free name
+ cap_free(name);
+ name = NULL;
}
+ // Success
+ r = 0;
+
ERROR:
+ if (name)
+ cap_free(name);
if (caps)
cap_free(caps);
// Copy source
r = pakfire_string_set(mp->source, source);
- if (r < 0) {
+ if (r) {
ERROR(jail->pakfire, "Could not copy source: %m\n");
- return 1;
+ return r;
}
// Copy target
r = pakfire_string_set(mp->target, target);
- if (r < 0) {
+ if (r) {
ERROR(jail->pakfire, "Could not copy target: %m\n");
- return 1;
+ return r;
}
// Copy flags
return 0;
}
+static int pakfire_jail_mount_networking(struct pakfire_jail* jail) {
+ int r;
+
+ const char* paths[] = {
+ "/etc/hosts",
+ "/etc/resolv.conf",
+ NULL,
+ };
+
+ // Bind-mount all paths read-only
+ for (const char** path = paths; *path; path++) {
+ r = pakfire_bind(jail->pakfire, *path, NULL, MS_RDONLY);
+ if (r) {
+ switch (errno) {
+ // Ignore if we don't have permission
+ case EPERM:
+ continue;
+
+ default:
+ break;
+ }
+ return r;
+ }
+ }
+
+ return 0;
+}
+
/*
Mounts everything that we require in the new namespace
*/
-static int pakfire_jail_mount(struct pakfire_jail* jail) {
+static int pakfire_jail_mount(struct pakfire_jail* jail, struct pakfire_jail_exec* ctx) {
struct pakfire_jail_mountpoint* mp = NULL;
+ int flags = 0;
int r;
+ // Enable loop devices
+ if (pakfire_jail_exec_has_flag(ctx, PAKFIRE_JAIL_HAS_LOOP_DEVICES))
+ flags |= PAKFIRE_MOUNT_LOOP_DEVICES;
+
// Mount all default stuff
- r = pakfire_mount_all(jail->pakfire);
+ r = pakfire_mount_all(jail->pakfire, PAKFIRE_MNTNS_OUTER, flags);
+ if (r)
+ return r;
+
+ // Populate /dev
+ r = pakfire_populate_dev(jail->pakfire, flags);
+ if (r)
+ return r;
+
+ // Mount the interpreter (if needed)
+ r = pakfire_mount_interpreter(jail->pakfire);
if (r)
return r;
+ // Mount networking stuff
+ if (pakfire_jail_exec_has_flag(ctx, PAKFIRE_JAIL_HAS_NETWORKING)) {
+ r = pakfire_jail_mount_networking(jail);
+ if (r)
+ return r;
+ }
+
// Mount all custom stuff
for (unsigned int i = 0; i < jail->num_mountpoints; i++) {
// Fetch mountpoint
return r;
}
- // Log all mountpoints
- pakfire_mount_list(jail->pakfire);
-
return 0;
}
-// UID/GID Mapping
+// Networking
-static int pakfire_jail_write_uidgid_mapping(struct pakfire_jail* jail,
- const char* path, const struct pakfire_subid* subid) {
- int r = 1;
+static int pakfire_jail_setup_loopback(struct pakfire_jail* jail) {
+ struct nl_sock* nl = NULL;
+ struct nl_cache* cache = NULL;
+ struct rtnl_link* link = NULL;
+ struct rtnl_link* change = NULL;
+ int r;
- // Open file for writing
- FILE* f = fopen(path, "w");
- if (!f) {
- ERROR(jail->pakfire, "Could not open %s for writing: %m\n", path);
- goto ERROR;
- }
+ DEBUG(jail->pakfire, "Setting up loopback...\n");
- // Write configuration
- int bytes_written = fprintf(f, "%d %u %lu\n", 0, subid->id, subid->length);
- if (bytes_written <= 0) {
- ERROR(jail->pakfire, "Could not write UID/GID mapping: %m\n");
+ // Allocate a netlink socket
+ nl = nl_socket_alloc();
+ if (!nl) {
+ ERROR(jail->pakfire, "Could not allocate a netlink socket: %m\n");
+ r = 1;
goto ERROR;
}
- // Close the file
- r = fclose(f);
- f = NULL;
+ // Connect the socket
+ r = nl_connect(nl, NETLINK_ROUTE);
if (r) {
- ERROR(jail->pakfire, "Could not write UID/GID mapping: %m\n");
+ ERROR(jail->pakfire, "Could not connect netlink socket: %s\n", nl_geterror(r));
+ goto ERROR;
+ }
+
+ // Allocate the netlink cache
+ r = rtnl_link_alloc_cache(nl, AF_UNSPEC, &cache);
+ if (r < 0) {
+ ERROR(jail->pakfire, "Unable to allocate netlink cache: %s\n", nl_geterror(r));
+ goto ERROR;
+ }
+ // Fetch loopback interface
+ link = rtnl_link_get_by_name(cache, "lo");
+ if (!link) {
+ ERROR(jail->pakfire, "Could not find lo interface. Ignoring.\n");
+ r = 0;
+ goto ERROR;
+ }
+
+ // Allocate a new link
+ change = rtnl_link_alloc();
+ if (!change) {
+ ERROR(jail->pakfire, "Could not allocate change link\n");
+ r = 1;
+ goto ERROR;
+ }
+
+ // Set the link to UP
+ rtnl_link_set_flags(change, IFF_UP);
+
+ // Apply any changes
+ r = rtnl_link_change(nl, link, change, 0);
+ if (r) {
+ ERROR(jail->pakfire, "Unable to activate loopback: %s\n", nl_geterror(r));
goto ERROR;
}
r = 0;
ERROR:
- if (f)
- fclose(f);
+ if (nl)
+ nl_socket_free(nl);
return r;
}
+// UID/GID Mapping
+
static int pakfire_jail_setup_uid_mapping(struct pakfire_jail* jail, pid_t pid) {
char path[PATH_MAX];
int r;
if (pakfire_on_root(jail->pakfire))
return 0;
+ // Make path
+ r = pakfire_string_format(path, "/proc/%d/uid_map", pid);
+ if (r)
+ return r;
+
+ // Fetch UID
+ const uid_t uid = pakfire_uid(jail->pakfire);
+
// Fetch SUBUID
const struct pakfire_subid* subuid = pakfire_subuid(jail->pakfire);
if (!subuid)
return 1;
- // Make path
- r = pakfire_string_format(path, "/proc/%d/uid_map", pid);
- if (r < 0)
- return 1;
+ /* When running as root, we will map the entire range.
- DEBUG(jail->pakfire, "Mapping UID range (%u - %lu)\n",
- subuid->id, subuid->id + subuid->length);
+ When running as a non-privileged user, we will map the root user inside the jail
+ to the user's UID outside of the jail, and we will map the rest starting from one.
+ */
- return pakfire_jail_write_uidgid_mapping(jail, path, subuid);
+ // Running as root
+ if (uid == 0) {
+ r = pakfire_file_write(jail->pakfire, path, 0, 0, 0,
+ "0 %lu %lu\n", subuid->id, subuid->length);
+ } else {
+ r = pakfire_file_write(jail->pakfire, path, 0, 0, 0,
+ "0 %lu 1\n1 %lu %lu\n", uid, subuid->id, subuid->length);
+ }
+
+ if (r) {
+ ERROR(jail->pakfire, "Could not map UIDs: %m\n");
+ return r;
+ }
+
+ return r;
}
static int pakfire_jail_setup_gid_mapping(struct pakfire_jail* jail, pid_t pid) {
if (pakfire_on_root(jail->pakfire))
return 0;
+ // Fetch GID
+ const gid_t gid = pakfire_gid(jail->pakfire);
+
// Fetch SUBGID
const struct pakfire_subid* subgid = pakfire_subgid(jail->pakfire);
if (!subgid)
// Make path
r = pakfire_string_format(path, "/proc/%d/gid_map", pid);
- if (r < 0)
- return 1;
+ if (r)
+ return r;
+
+ // Running as root
+ if (gid == 0) {
+ r = pakfire_file_write(jail->pakfire, path, 0, 0, 0,
+ "0 %lu %lu\n", subgid->id, subgid->length);
+ } else {
+ r = pakfire_file_write(jail->pakfire, path, 0, 0, 0,
+ "0 %lu 1\n1 %lu %lu\n", gid, subgid->id, subgid->length);
+ }
- DEBUG(jail->pakfire, "Mapping GID range (%u - %lu)\n",
- subgid->id, subgid->id + subgid->length);
+ if (r) {
+ ERROR(jail->pakfire, "Could not map GIDs: %m\n");
+ return r;
+ }
- return pakfire_jail_write_uidgid_mapping(jail, path, subgid);
+ return r;
}
static int pakfire_jail_setgroups(struct pakfire_jail* jail, pid_t pid) {
char path[PATH_MAX];
- int r = 1;
+ int r;
// Make path
r = pakfire_string_format(path, "/proc/%d/setgroups", pid);
- if (r < 0)
- return 1;
-
- // Open file for writing
- FILE* f = fopen(path, "w");
- if (!f) {
- ERROR(jail->pakfire, "Could not open %s for writing: %m\n", path);
- goto ERROR;
- }
-
- // Write content
- int bytes_written = fprintf(f, "deny\n");
- if (bytes_written <= 0) {
- ERROR(jail->pakfire, "Could not write to %s: %m\n", path);
- goto ERROR;
- }
+ if (r)
+ return r;
- r = fclose(f);
- f = NULL;
+ r = pakfire_file_write(jail->pakfire, path, 0, 0, 0, "deny\n");
if (r) {
- ERROR(jail->pakfire, "Could not close %s: %m\n", path);
- goto ERROR;
+ CTX_ERROR(jail->ctx, "Could not set setgroups to deny: %s\n", strerror(errno));
+ r = -errno;
}
-ERROR:
- if (f)
- fclose(f);
-
return r;
}
DEBUG(jail->pakfire, "Sending signal...\n");
// Write to the file descriptor
- ssize_t bytes_written = write(fd, &val, sizeof(val));
- if (bytes_written < 0 || (size_t)bytes_written < sizeof(val)) {
- ERROR(jail->pakfire, "Could not send signal: %m\n");
- r = 1;
+ r = eventfd_write(fd, val);
+ if (r < 0) {
+ ERROR(jail->pakfire, "Could not send signal: %s\n", strerror(errno));
+ r = -errno;
}
// Close the file descriptor
DEBUG(jail->pakfire, "Waiting for signal...\n");
- ssize_t bytes_read = read(fd, &val, sizeof(val));
- if (bytes_read < 0 || (size_t)bytes_read < sizeof(val)) {
- ERROR(jail->pakfire, "Error waiting for signal: %m\n");
- r = 1;
+ r = eventfd_read(fd, &val);
+ if (r < 0) {
+ ERROR(jail->pakfire, "Error waiting for signal: %s\n", strerror(errno));
+ r = -errno;
}
// Close the file descriptor
static int pakfire_jail_parent(struct pakfire_jail* jail, struct pakfire_jail_exec* ctx) {
int r;
- // Write "deny" to /proc/PID/setgroups
- r = pakfire_jail_setgroups(jail, ctx->pid);
+ // Setup UID mapping
+ r = pakfire_jail_setup_uid_mapping(jail, ctx->pid);
if (r)
return r;
- // Setup UID mapping
- r = pakfire_jail_setup_uid_mapping(jail, ctx->pid);
+ // Write "deny" to /proc/PID/setgroups
+ r = pakfire_jail_setgroups(jail, ctx->pid);
if (r)
return r;
return 0;
}
-static int pakfire_jail_child(struct pakfire_jail* jail, struct pakfire_jail_exec* ctx,
- const char* argv[]) {
+static int pakfire_jail_switch_root(struct pakfire_jail* jail, const char* root) {
int r;
- // Redirect any logging to our log pipe
- pakfire_set_log_callback(jail->pakfire, pakfire_jail_log, &ctx->pipes);
+ // Change to the new root
+ r = chdir(root);
+ if (r) {
+ ERROR(jail->pakfire, "chdir(%s) failed: %m\n", root);
+ return r;
+ }
- // Die with parent
- r = prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0);
+ // Switch Root!
+ r = pivot_root(".", ".");
if (r) {
- ERROR(jail->pakfire, "Could not configure to die with parent: %m\n");
- return 126;
+ ERROR(jail->pakfire, "Failed changing into the new root directory %s: %m\n", root);
+ return r;
}
+ // Umount the old root
+ r = umount2(".", MNT_DETACH);
+ if (r) {
+ ERROR(jail->pakfire, "Could not umount the old root filesystem: %m\n");
+ return r;
+ }
+
+ return 0;
+}
+
+#if 0
+static int pakfire_jail_open_pty(struct pakfire_jail* jail, struct pakfire_jail_exec* ctx) {
+ int r;
+
+ // Allocate a new PTY
+ ctx->consolefd = posix_openpt(O_RDWR|O_NONBLOCK|O_NOCTTY|O_CLOEXEC);
+ if (ctx->consolefd < 0)
+ return -errno;
+
+ // Fetch the path
+ r = ptsname_r(ctx->consolefd, ctx->console, sizeof(ctx->console));
+ if (r)
+ return -r;
+
+ CTX_DEBUG(jail->ctx, "Allocated console at %s (%d)\n", ctx->console, ctx->consolefd);
+
+ // Create a symlink
+ r = pakfire_symlink(jail->ctx, "/dev/console", ctx->console);
+ if (r)
+ return r;
+
+ return r;
+}
+#endif
+
+static int pakfire_jail_child(struct pakfire_jail* jail, struct pakfire_jail_exec* ctx,
+ const char* argv[]) {
+ int r;
+
+ // Redirect any logging to our log pipe
+ pakfire_ctx_set_log_callback(jail->ctx, pakfire_jail_log_redirect, &ctx->pipes);
+
// Fetch my own PID
pid_t pid = getpid();
DEBUG(jail->pakfire, "Launched child process in jail with PID %d\n", pid);
- // Log argv
- for (unsigned int i = 0; argv[i]; i++)
- DEBUG(jail->pakfire, " argv[%d] = %s\n", i, argv[i]);
-
// Wait for the parent to finish initialization
r = pakfire_jail_wait_for_signal(jail, ctx->completed_fd);
if (r)
return r;
- // Perform further initialization
+ // Die with parent
+ r = prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0);
+ if (r) {
+ ERROR(jail->pakfire, "Could not configure to die with parent: %m\n");
+ return 126;
+ }
+
+ // Make this process dumpable
+ r = prctl (PR_SET_DUMPABLE, 1, 0, 0, 0);
+ if (r) {
+ ERROR(jail->pakfire, "Could not make the process dumpable: %m\n");
+ return 126;
+ }
+
+ // Don't drop any capabilities on setuid()
+ r = prctl(PR_SET_KEEPCAPS, 1);
+ if (r) {
+ ERROR(jail->pakfire, "Could not set PR_SET_KEEPCAPS: %m\n");
+ return 126;
+ }
// Fetch UID/GID
uid_t uid = getuid();
uid_t euid = geteuid();
gid_t egid = getegid();
- DEBUG(jail->pakfire, " UID: %d (effective %d)\n", uid, euid);
- DEBUG(jail->pakfire, " GID: %d (effective %d)\n", gid, egid);
+ DEBUG(jail->pakfire, " UID: %u (effective %u)\n", uid, euid);
+ DEBUG(jail->pakfire, " GID: %u (effective %u)\n", gid, egid);
+
+ // Log all mountpoints
+ pakfire_mount_list(jail->ctx);
- // Check if we are (effectively running as root)
+ // Fail if we are not PID 1
+ if (pid != 1) {
+ CTX_ERROR(jail->ctx, "Child process is not PID 1\n");
+ return 126;
+ }
+
+ // Fail if we are not running as root
if (uid || gid || euid || egid) {
ERROR(jail->pakfire, "Child process is not running as root\n");
return 126;
}
+ // Mount all default stuff
+ r = pakfire_mount_all(jail->pakfire, PAKFIRE_MNTNS_INNER, 0);
+ if (r)
+ return 126;
+
+#if 0
+ // Create a new session
+ r = setsid();
+ if (r < 0) {
+ CTX_ERROR(jail->ctx, "Could not create a new session: %s\n", strerror(errno));
+ return 126;
+ }
+
+ // Allocate a new PTY
+ r = pakfire_jail_open_pty(jail, ctx);
+ if (r) {
+ CTX_ERROR(jail->ctx, "Could not allocate a new PTY: %s\n", strerror(-r));
+ return 126;
+ }
+#endif
+
const char* root = pakfire_get_path(jail->pakfire);
- const char* arch = pakfire_get_arch(jail->pakfire);
+ const char* arch = pakfire_get_effective_arch(jail->pakfire);
+
+ // Change mount propagation to slave to receive anything from the parent namespace
+ r = pakfire_mount_change_propagation(jail->ctx, "/", MS_SLAVE);
+ if (r)
+ return r;
+
+ // Make root a mountpoint in the new mount namespace
+ r = pakfire_mount_make_mounpoint(jail->pakfire, root);
+ if (r)
+ return r;
+
+ // Change mount propagation to private
+ r = pakfire_mount_change_propagation(jail->ctx, root, MS_PRIVATE);
+ if (r)
+ return r;
// Change root (unless root is /)
if (!pakfire_on_root(jail->pakfire)) {
// Mount everything
- r = pakfire_jail_mount(jail);
+ r = pakfire_jail_mount(jail, ctx);
if (r)
return r;
- // Call chroot()
- r = chroot(root);
- if (r) {
- ERROR(jail->pakfire, "chroot() to %s failed: %m\n", root);
- return 1;
- }
-
- // Change directory to /
- r = chdir("/");
- if (r) {
- ERROR(jail->pakfire, "chdir() after chroot() failed: %m\n");
- return 1;
- }
+ // chroot()
+ r = pakfire_jail_switch_root(jail, root);
+ if (r)
+ return r;
}
// Set personality
}
}
+ // Setup networking
+ if (!pakfire_jail_exec_has_flag(ctx, PAKFIRE_JAIL_HAS_NETWORKING)) {
+ r = pakfire_jail_setup_loopback(jail);
+ if (r)
+ return 1;
+ }
+
// Set nice level
if (jail->nice) {
DEBUG(jail->pakfire, "Setting nice level to %d\n", jail->nice);
close(ctx->pipes.log_DEBUG[0]);
#endif /* ENABLE_DEBUG */
+ // Connect standard input
+ if (ctx->pipes.stdin[0] >= 0) {
+ r = dup2(ctx->pipes.stdin[0], STDIN_FILENO);
+ if (r < 0) {
+ ERROR(jail->pakfire, "Could not connect fd %d to stdin: %m\n",
+ ctx->pipes.stdin[0]);
+
+ return 1;
+ }
+ }
+
// Connect standard output and error
- if (ctx->pipes.stdout[1] && ctx->pipes.stderr[1]) {
+ if (ctx->pipes.stdout[1] >= 0 && ctx->pipes.stderr[1] >= 0) {
r = dup2(ctx->pipes.stdout[1], STDOUT_FILENO);
if (r < 0) {
ERROR(jail->pakfire, "Could not connect fd %d to stdout: %m\n",
}
// Close the pipe (as we have moved the original file descriptors)
+ pakfire_jail_close_pipe(jail, ctx->pipes.stdin);
pakfire_jail_close_pipe(jail, ctx->pipes.stdout);
pakfire_jail_close_pipe(jail, ctx->pipes.stderr);
}
if (r)
return r;
- // Drop capabilities
- r = pakfire_jail_drop_capabilities(jail);
+ // Set capabilities
+ r = pakfire_jail_set_capabilities(jail);
+ if (r)
+ return r;
+
+ // Show capabilities
+ r = pakfire_jail_show_capabilities(jail);
if (r)
return r;
if (r)
return r;
+ DEBUG(jail->pakfire, "Child process initialization done\n");
+ DEBUG(jail->pakfire, "Launching command:\n");
+
+ // Log argv
+ for (unsigned int i = 0; argv[i]; i++)
+ DEBUG(jail->pakfire, " argv[%u] = %s\n", i, argv[i]);
+
// exec() command
r = execvpe(argv[0], (char**)argv, jail->env);
- if (r < 0)
- ERROR(jail->pakfire, "Could not execve(): %m\n");
+ if (r < 0) {
+ // Translate errno into regular exit code
+ switch (errno) {
+ case ENOENT:
+ // Ignore if the command doesn't exist
+ if (ctx->flags & PAKFIRE_JAIL_NOENT_OK)
+ r = 0;
+ else
+ r = 127;
- // Translate errno into regular exit code
- switch (errno) {
- case ENOENT:
- r = 127;
- break;
+ break;
- default:
- r = 1;
+ default:
+ r = 1;
+ }
+
+ ERROR(jail->pakfire, "Could not execve(%s): %m\n", argv[0]);
}
// We should not get here
}
// Run a command in the jail
-static int __pakfire_jail_exec(struct pakfire_jail* jail, const char* argv[]) {
+static int __pakfire_jail_exec(struct pakfire_jail* jail, const char* argv[],
+ const int interactive,
+ pakfire_jail_communicate_in communicate_in,
+ pakfire_jail_communicate_out communicate_out,
+ void* data, int flags) {
int exit = -1;
int r;
// Initialize context for this call
struct pakfire_jail_exec ctx = {
+ .flags = flags,
+
.pipes = {
- .stdout = { 0, 0 },
- .stderr = { 0, 0 },
+ .stdin = { -1, -1 },
+ .stdout = { -1, -1 },
+ .stderr = { -1, -1 },
+ .log_INFO = { -1, -1 },
+ .log_ERROR = { -1, -1 },
+#ifdef ENABLE_DEBUG
+ .log_DEBUG = { -1, -1 },
+#endif /* ENABLE_DEBUG */
},
+
+ .communicate = {
+ .in = communicate_in,
+ .out = communicate_out,
+ .data = data,
+ },
+
+ .pidfd = -1,
};
DEBUG(jail->pakfire, "Executing jail...\n");
+ // Enable networking in interactive mode
+ if (interactive)
+ ctx.flags |= PAKFIRE_JAIL_HAS_NETWORKING;
+
/*
Setup a file descriptor which can be used to notify the client that the parent
has completed configuration.
}
// Create pipes to communicate with child process if we are not running interactively
- if (!pakfire_jail_has_flag(jail, PAKFIRE_JAIL_INTERACTIVE)) {
+ if (!interactive) {
+ // stdin (only if callback is set)
+ if (ctx.communicate.in) {
+ r = pakfire_jail_setup_pipe(jail, &ctx.pipes.stdin, 0);
+ if (r)
+ goto ERROR;
+ }
+
// stdout
r = pakfire_jail_setup_pipe(jail, &ctx.pipes.stdout, 0);
if (r)
CLONE_NEWIPC |
CLONE_NEWNS |
CLONE_NEWPID |
+ CLONE_NEWTIME |
CLONE_NEWUSER |
CLONE_NEWUTS |
CLONE_PIDFD,
args.cgroup = pakfire_cgroup_fd(ctx.cgroup);
}
+ // Setup networking
+ if (!pakfire_jail_exec_has_flag(&ctx, PAKFIRE_JAIL_HAS_NETWORKING)) {
+ args.flags |= CLONE_NEWNET;
+ }
+
// Fork this process
ctx.pid = clone3(&args, sizeof(args));
if (ctx.pid < 0) {
break;
case CLD_KILLED:
- case CLD_DUMPED:
ERROR(jail->pakfire, "The child process was killed\n");
+ exit = 139;
+ break;
+
+ case CLD_DUMPED:
+ ERROR(jail->pakfire, "The child process terminated abnormally\n");
break;
// Log anything else
// Destroy the temporary cgroup (if any)
if (ctx.cgroup) {
// Read cgroup stats
- r = pakfire_cgroup_stat(ctx.cgroup, &ctx.cgroup_stats);
- if (r) {
- ERROR(jail->pakfire, "Could not read cgroup stats: %m\n");
- } else {
- pakfire_cgroup_stat_dump(ctx.cgroup, &ctx.cgroup_stats);
- }
-
+ pakfire_cgroup_stat(ctx.cgroup, &ctx.cgroup_stats);
+ pakfire_cgroup_stat_dump(ctx.cgroup, &ctx.cgroup_stats);
pakfire_cgroup_destroy(ctx.cgroup);
pakfire_cgroup_unref(ctx.cgroup);
}
// Close any file descriptors
+ pakfire_jail_close_pipe(jail, ctx.pipes.stdin);
pakfire_jail_close_pipe(jail, ctx.pipes.stdout);
pakfire_jail_close_pipe(jail, ctx.pipes.stderr);
- if (ctx.pidfd)
+ if (ctx.pidfd >= 0)
close(ctx.pidfd);
pakfire_jail_close_pipe(jail, ctx.pipes.log_INFO);
pakfire_jail_close_pipe(jail, ctx.pipes.log_ERROR);
+#ifdef ENABLE_DEBUG
pakfire_jail_close_pipe(jail, ctx.pipes.log_DEBUG);
+#endif /* ENABLE_DEBUG */
return exit;
}
-PAKFIRE_EXPORT int pakfire_jail_exec(struct pakfire_jail* jail,
- const char* argv[], char** output) {
- int r;
-
- // Store logging callback
- pakfire_jail_log_callback log_callback = jail->log_callback;
- void* log_data = jail->log_data;
-
- // Capture output if requested by user
- if (output)
- pakfire_jail_set_log_callback(jail, pakfire_jail_capture_stdout, output);
+PAKFIRE_EXPORT int pakfire_jail_exec(
+ struct pakfire_jail* jail,
+ const char* argv[],
+ pakfire_jail_communicate_in callback_in,
+ pakfire_jail_communicate_out callback_out,
+ void* data, int flags) {
+ return __pakfire_jail_exec(jail, argv, 0, callback_in, callback_out, data, flags);
+}
- // Run exec()
- r = __pakfire_jail_exec(jail, argv);
+static int pakfire_jail_exec_interactive(
+ struct pakfire_jail* jail, const char* argv[], int flags) {
+ int r;
- // Restore log callback
- pakfire_jail_set_log_callback(jail, log_callback, log_data);
+ // Setup interactive stuff
+ r = pakfire_jail_setup_interactive_env(jail);
+ if (r)
+ return r;
- return r;
+ return __pakfire_jail_exec(jail, argv, 1, NULL, NULL, NULL, flags);
}
-PAKFIRE_EXPORT int pakfire_jail_exec_script(struct pakfire_jail* jail,
- const char* script, const size_t size, const char* args[], char** output) {
+int pakfire_jail_exec_script(struct pakfire_jail* jail,
+ const char* script,
+ const size_t size,
+ const char* args[],
+ pakfire_jail_communicate_in callback_in,
+ pakfire_jail_communicate_out callback_out,
+ void* data) {
char path[PATH_MAX];
const char** argv = NULL;
+ FILE* f = NULL;
int r;
const char* root = pakfire_get_path(jail->pakfire);
// Write the scriptlet to disk
- r = pakfire_path_join(path, root, "pakfire-script.XXXXXX");
- if (r < 0)
+ r = pakfire_path_append(path, root, PAKFIRE_TMP_DIR "/pakfire-script.XXXXXX");
+ if (r)
goto ERROR;
- // Open a temporary file
- int fd = mkstemp(path);
- if (fd < 0) {
- ERROR(jail->pakfire, "Could not open a temporary file: %m\n");
- r = 1;
+ // Create a temporary file
+ f = pakfire_mktemp(path, 0700);
+ if (!f) {
+ ERROR(jail->pakfire, "Could not create temporary file: %m\n");
goto ERROR;
}
DEBUG(jail->pakfire, "Writing script to %s:\n%.*s\n", path, (int)size, script);
// Write data
- ssize_t bytes_written = write(fd, script, size);
- if (bytes_written < (ssize_t)size) {
+ r = fprintf(f, "%s", script);
+ if (r < 0) {
ERROR(jail->pakfire, "Could not write script to file %s: %m\n", path);
- r = 1;
- goto ERROR;
- }
-
- // Make the script executable
- r = fchmod(fd, S_IRUSR|S_IWUSR|S_IXUSR);
- if (r) {
- ERROR(jail->pakfire, "Could not set executable permissions on %s: %m\n", path);
goto ERROR;
}
// Close file
- r = close(fd);
+ r = fclose(f);
if (r) {
ERROR(jail->pakfire, "Could not close script file %s: %m\n", path);
- r = 1;
goto ERROR;
}
+ f = NULL;
+
// Count how many arguments were passed
unsigned int argc = 1;
if (args) {
argv[i] = args[i-1];
// Run the script
- r = pakfire_jail_exec(jail, argv, output);
+ r = pakfire_jail_exec(jail, argv, callback_in, callback_out, data, 0);
ERROR:
if (argv)
free(argv);
+ if (f)
+ fclose(f);
// Remove script from disk
if (*path)
int r;
// Create a new jail
- r = pakfire_jail_create(&jail, pakfire, flags);
+ r = pakfire_jail_create(&jail, pakfire);
if (r)
goto ERROR;
// Execute the command
- r = pakfire_jail_exec(jail, argv, output);
+ r = pakfire_jail_exec(jail, argv, NULL, pakfire_jail_capture_stdout, output, 0);
ERROR:
if (jail)
}
int pakfire_jail_run_script(struct pakfire* pakfire,
- const char* script, const size_t length, const char* argv[], int flags, char** output) {
+ const char* script, const size_t length, const char* argv[], int flags) {
struct pakfire_jail* jail = NULL;
int r;
// Create a new jail
- r = pakfire_jail_create(&jail, pakfire, flags);
+ r = pakfire_jail_create(&jail, pakfire);
if (r)
goto ERROR;
// Execute the command
- r = pakfire_jail_exec_script(jail, script, length, argv, output);
+ r = pakfire_jail_exec_script(jail, script, length, argv, NULL, NULL, NULL);
ERROR:
if (jail)
}
int pakfire_jail_shell(struct pakfire_jail* jail) {
+ int r;
+
const char* argv[] = {
"/bin/bash", "--login", NULL,
};
- // Check if this operation is possible
- if (!pakfire_jail_has_flag(jail, PAKFIRE_JAIL_INTERACTIVE)) {
- ERROR(jail->pakfire, "You cannot run a shell in a non-interactive jail\n");
- errno = ENOTSUP;
- return 1;
- }
-
// Execute /bin/bash
- return pakfire_jail_exec(jail, argv, NULL);
+ r = pakfire_jail_exec_interactive(jail, argv, 0);
+
+ // Raise any errors
+ if (r < 0)
+ return r;
+
+ // Ignore any return codes from the shell
+ return 0;
}
-int pakfire_jail_ldconfig(struct pakfire* pakfire) {
+static int pakfire_jail_run_if_possible(struct pakfire* pakfire, const char** argv) {
char path[PATH_MAX];
+ int r;
- const char* ldconfig = "/sbin/ldconfig";
-
- // Check if ldconfig exists before calling it to avoid overhead
- int r = pakfire_make_path(pakfire, path, ldconfig);
- if (r < 0)
- return 1;
+ r = pakfire_path(pakfire, path, "%s", *argv);
+ if (r)
+ return r;
- // Check if ldconfig is executable
+ // Check if the file is executable
r = access(path, X_OK);
if (r) {
- DEBUG(pakfire, "%s is not executable. Skipping...\n", ldconfig);
+ DEBUG(pakfire, "%s is not executable. Skipping...\n", *argv);
return 0;
}
+ return pakfire_jail_run(pakfire, argv, 0, NULL);
+}
+
+int pakfire_jail_ldconfig(struct pakfire* pakfire) {
const char* argv[] = {
- ldconfig, NULL,
+ "/sbin/ldconfig",
+ NULL,
};
- // Run ldconfig
- return pakfire_jail_run(pakfire, argv, 0, NULL);
+ return pakfire_jail_run_if_possible(pakfire, argv);
+}
+
+int pakfire_jail_run_systemd_tmpfiles(struct pakfire* pakfire) {
+ const char* argv[] = {
+ "/usr/bin/systemd-tmpfiles",
+ "--create",
+ NULL,
+ };
+
+ return pakfire_jail_run_if_possible(pakfire, argv);
}