preload , main(), CAP_SYS_CHROOT Unix .
, clone(CLONE_FS) , , LD_PRELOAD . (exec , .)
CAP_SYS_CHROOT , , (preload library) , chroot() (preload library).
. setuid root . : CAP_SYS_CHROOT .
, chroot(). , /. , exec.c =pe =p setcap Makefile.
, C unix ; SCM_RIGHTS chroot . ( , , fakeroot , : , chrooted , , chroot.) , , .
, , ( preload) main().
exec.c
#define _GNU_SOURCE
#define _POSIX_C_SOURCE 200809L
#include <unistd.h>
#include <sys/capability.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/mman.h>
#include <sched.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#ifndef SOCKET_FD
#error SOCKET_FD not defined!
#endif
#ifndef LIBRARY_PATH
#error LIBRARY_PATH not defined!
#endif
static size_t helper_stack_size = 32768;
static void *helper_stack = NULL;
static const char *helper_chroot = NULL;
static const cap_value_t helper_cap[] = { CAP_SYS_CHROOT };
static const int helper_caps = sizeof helper_cap / sizeof helper_cap[0];
static int socket_fd[2] = { -1, -1 };
#ifdef __hppa
#define helper_endstack (helper_stack)
#else
#define helper_endstack ((void *)((char *)helper_stack + helper_stack_size - 1))
#endif
static int helper_main(void *arg)
{
const char *const argv0 = arg;
pid_t pid;
cap_t caps;
close(socket_fd[0]);
{ char *p = (char *)(&pid);
char *const q = (char *)(&pid) + sizeof pid;
ssize_t n;
while (p < q) {
n = recv(socket_fd[1], p, (size_t)(q - p), MSG_WAITALL);
if (n > (ssize_t)0)
p += n;
else
if (n != (ssize_t)-1) {
fprintf(stderr, "%s: %s.\n", argv0, strerror(EIO));
return 127;
} else
if (errno != EINTR) {
fprintf(stderr, "%s: %s.\n", argv0, strerror(errno));
return 127;
}
}
}
if (pid < (pid_t)2) {
shutdown(socket_fd[1], SHUT_RDWR);
close(socket_fd[1]);
return 127;
}
caps = cap_get_proc();
if (cap_set_flag(caps, CAP_EFFECTIVE, helper_caps, helper_cap, CAP_SET)) {
shutdown(socket_fd[1], SHUT_RDWR);
close(socket_fd[1]);
fprintf(stderr, "%s: %s.\n", argv0, strerror(errno));
return 127;
}
if (cap_set_proc(caps)) {
shutdown(socket_fd[1], SHUT_RDWR);
close(socket_fd[1]);
fprintf(stderr, "%s: %s.\n", argv0, strerror(errno));
return 127;
}
if (chroot(helper_chroot)) {
shutdown(socket_fd[1], SHUT_RDWR);
close(socket_fd[1]);
fprintf(stderr, "%s: Cannot chroot: %s.\n", argv0, strerror(errno));
return 127;
}
{ const char *p = (char *)(&pid);
const char *const q = (char *)(&pid) + sizeof pid;
ssize_t n;
pid = getpid();
while (p < q) {
n = send(socket_fd[1], p, (size_t)(q - p), MSG_NOSIGNAL);
if (n > (ssize_t)0)
p += n;
else
if (n != (ssize_t)-1) {
fprintf(stderr, "%s: %s.\n", argv0, strerror(EIO));
return 127;
} else
if (errno != EINTR) {
fprintf(stderr, "%s: %s.\n", argv0, strerror(errno));
return 127;
}
}
}
shutdown(socket_fd[1], SHUT_WR);
{ char buf[16];
ssize_t n;
while (1) {
n = recv(socket_fd[1], buf, sizeof buf, 0);
if (n > (ssize_t)0)
continue;
else
if (n == (ssize_t)0)
break;
else
if (n != (ssize_t)-1) {
fprintf(stderr, "%s: %s.\n", argv0, strerror(EIO));
return 127;
} else
if (errno == EPIPE)
break;
else
if (errno != EINTR) {
fprintf(stderr, "%s: %s.\n", argv0, strerror(errno));
return 127;
}
}
}
shutdown(socket_fd[1], SHUT_RDWR);
close(socket_fd[1]);
return 0;
}
int main(int argc, char *argv[])
{
if (argc < 4 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
fprintf(stderr, " %s CHROOT WORKDIR COMMAND [ ARGS ... ]\n", argv[0]);
fprintf(stderr, "\n");
fprintf(stderr, "Note: . is a valid WORKDIR.\n");
fprintf(stderr, "\n");
return 1;
}
if (chdir(argv[2])) {
fprintf(stderr, "%s: %s.\n", argv[2], strerror(errno));
return 1;
}
helper_stack = mmap(NULL, helper_stack_size, PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_PRIVATE | MAP_STACK | MAP_GROWSDOWN, -1, (off_t)0);
if ((void *)helper_stack == MAP_FAILED) {
fprintf(stderr, "Cannot create helper process stack: %s.\n", strerror(errno));
return 1;
}
helper_chroot = argv[1];
if (socketpair(AF_UNIX, SOCK_STREAM, 0, socket_fd)) {
fprintf(stderr, "Cannot create an Unix domain stream socket pair: %s.\n", strerror(errno));
return 1;
}
if (clone(helper_main, helper_endstack, CLONE_FS, argv[0]) == -1) {
fprintf(stderr, "Cannot clone a helper process: %s.\n", strerror(errno));
close(socket_fd[0]);
close(socket_fd[1]);
return 1;
}
close(socket_fd[1]);
if (socket_fd[0] != SOCKET_FD) {
if (dup2(socket_fd[0], SOCKET_FD) == -1) {
fprintf(stderr, "Cannot move stream socket: %s.\n", strerror(errno));
close(socket_fd[0]);
close(SOCKET_FD);
return 1;
}
close(socket_fd[0]);
}
setenv("LD_PRELOAD", LIBRARY_PATH, 1);
execvp(argv[3], argv + 3);
close(SOCKET_FD);
fprintf(stderr, "%s: %s.\n", argv[3], strerror(errno));
return 1;
}
premain.c
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#ifndef SOCKET_FD
#error SOCKET_FD is not defined!
#endif
static void init(void) __attribute__ ((constructor (65535)));
static void init(void)
{
pid_t pid;
unsetenv("LD_PRELOAD");
{ struct sockaddr_un addr;
socklen_t addrlen = sizeof addr;
memset(&addr, 0, sizeof addr);
errno = EIO;
if (getsockname(SOCKET_FD, (struct sockaddr *)&addr, &addrlen))
switch (errno) {
case EBADF:
errno = 0;
return;
case ENOTSOCK:
errno = 0;
return;
default:
exit(127);
}
if (addr.sun_family != AF_UNIX) {
errno = 0;
return;
}
}
if (fcntl(SOCKET_FD, F_SETFD, (long)FD_CLOEXEC) ||
fcntl(SOCKET_FD, F_SETFL, (long)0L))
exit(127);
{ const char *p = (const char *)(&pid);
const char *const q = (const char *)(&pid) + sizeof pid;
pid = getpid();
while (p < q) {
ssize_t n = send(SOCKET_FD, p, (size_t)(q - p), MSG_NOSIGNAL);
if (n > (ssize_t)0)
p += n;
else
if (n != (ssize_t)-1)
exit(127);
else
if (errno != EINTR)
exit(127);
}
}
{ char *p = (char *)(&pid);
char *const q = (char *)(&pid) + sizeof pid;
pid = (pid_t)-1;
while (p < q) {
ssize_t n = recv(SOCKET_FD, p, (size_t)(q - p), MSG_WAITALL);
if (n > (ssize_t)0)
p += n;
else
if (n != (ssize_t)-1)
exit(127);
else
if (errno != EINTR)
exit(127);
}
}
shutdown(SOCKET_FD, SHUT_RDWR);
close(SOCKET_FD);
if (pid > (pid_t)1) {
pid_t p;
do {
p = waitpid(pid, NULL, 0);
} while (p == (pid_t)-1 && errno == EINTR);
}
return;
}
Makefile
CC := gcc
CFLAGS := -Wall -O3
LD := $(CC)
LDFLAGS := -lcap
PREFIX := /usr
BINDIR := $(PREFIX)/bin
LIBDIR := $(PREFIX)/lib
SOCKFD := 15
.PHONY: all clean
all: clean libpremain.so exec-chroot
clean:
rm -f libpremain.so exec-chroot
libpremain.so: premain.c
$(CC) $(CFLAGS) -DSOCKET_FD=$(SOCKFD) -fPIC -shared $^ -ldl -Wl,-soname,$@ $(LDFLAGS) -o $@
exec-chroot: exec.c
$(CC) $(CFLAGS) -DSOCKET_FD=$(SOCKFD) -DLIBRARY_PATH='"'$(LIBDIR)/libpremain.so'"' $^ $(LDFLAGS) -o $@
install: libpremain.so exec-chroot
sudo rm -f $(LIBDIR)/libpremain.so $(BINDIR)/exec-chroot
sudo install -o `id -un` -g `id -gn` -m 00770 libpremain.so $(LIBDIR)/libpremain.so
sudo install -o `id -un` -g `id -gn` -m 00770 exec-chroot $(BINDIR)/exec-chroot
sudo setcap 'cap_sys_chroot=p' $(BINDIR)/exec-chroot
uninstall:
sudo rm -f $(LIBDIR)/libpremain.so $(BINDIR)/exec-chroot
, Makefile tab s, . Run
make PREFIX=/usr/local clean install
/usr/local, . clean all uninstall .
libcap. , libcap-dev libcap-devel , .
, ,
exec-chroot /tmp /tmp ls -alF /
ls -alF / /tmp chrooted /tmp. Ubuntu, ,
drwxrwxrwt 11 0 0 4096 May 29 23:55 ./
drwxrwxrwt 11 0 0 4096 May 29 23:55 ../
drwxrwxrwt 2 0 0 4096 May 29 17:15 .ICE-unix/
-r
drwxrwxrwt 2 0 0 4096 May 29 17:15 .X11-unix/
drwx
drwx
drwx
drwx
0 (root) 1000 (), , passwd group chroot. , , , , .
, , ; .
?