Определить запуск программы в другом потоке на Linux

Я пытаюсь изолировать исполняемые файлы ELF путем (помимо прочего) их изменения в корневой папке после их запуска. Для этого дочерний процесс, клонированный с тегом CLONE_FS, выполняет chroot, в то время как родительский процесс запускает двоичный файл, вызывая функцию exec.

Трюк действительно работает, если chroot происходит после того, как программа закончила загрузку разделяемых библиотек, в которых она нуждается. Проблема в том, что я не могу найти способ обнаружить, когда это на самом деле происходит из другого процесса. Там в любом случае?

4

Решение

Вы можете использовать библиотеку предварительной загрузки с функцией, выполняемой непосредственно перед main()вспомогательный двоичный файл с CAP_SYS_CHROOT разрешенные возможности файловой системы и пара сокетов домена Unix между ними.

Вспомогательный двоичный файл создает пару сокетов, а затем использует clone(CLONE_FS) для разветвления вспомогательного процесса, который разделяет информацию о файловой системе, устанавливает LD_PRELOAD загрузить библиотеку предварительной загрузки и выполняет исполняемый двоичный файл. (exec сбрасывает возможности в соответствии с возможностями изолированной двоичной файловой системы, так что изолированная двоичная версия не будет иметь никаких дополнительных привилегий вообще.)

Вспомогательный процесс добавляет CAP_SYS_CHROOT к эффективному набору, ждет, когда двоичный файл с песочницей (библиотека предварительной загрузки) уведомит его через сокет, вызывает chroot()и уведомляет об изолированном двоичном файле (библиотека предварительной загрузки) об успехе.

Примечание. Абсолютно нет необходимости отмечать вспомогательный двоичный файл setuid root или предоставлять изолированному двоичному файлу какие-либо возможности или привилегии. Мы можем сделать это с минимальными привилегиями: CAP_SYS_CHROOT возможность достаточно.

Я предпочитаю добавлять возможность в двоичный файл только в разрешенный набор, так что сам двоичный файл должен добавить возможность в эффективный набор до chroot() работает. Я чувствую, что этот подход уменьшает последствия возможных ошибок установки / администратора. Если вы не согласны, не стесняйтесь опускать соответствующий код из exec.cи использовать =pe вместо =p в setcap Команда в Makefile.

Здесь важно то, что библиотека предварительной загрузки может также вставлять нужные функции C и использовать сокет домена unix для получения необходимой информации от вспомогательного процесса; Вы даже можете использовать SCM_RIGHTS вспомогательные сообщения для передачи файловых дескрипторов извне chroot в изолированную программу. (По сути, это то, что fakeroot делает, но наоборот: вместо фальсификации среды chroot вы можете выбрать, к каким файлам двоичный файл с песочницей может получить доступ из-за пределов среды chroot.) Просто оставьте вспомогательный процесс в активном состоянии до тех пор, пока другой конец сокета все еще работает. открыть, поэтому он выйдет после того, как двоичные файлы в песочнице выйдут

Вот мой пример реализации, который запускает вспомогательный процесс как дочерний процесс для двоичного файла с песочницей, причем вспомогательный процесс завершает работу (и предварительно загружает библиотеку, пожиная его) до того, как помещается в песочницу 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]);

/* Read the target PID. */
{   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;
}

/* Enable CAP_SYS_CHROOT. */
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;
}

/* Target is ready to be chrooted, so do it now. */
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;
}

/* Send my own pid, so this process will be reaped. */
{   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;
}
}
}

/* We won't be sending anything else. */
shutdown(socket_fd[1], SHUT_WR);

/* Ignore further input; wait for other end to close descriptor. */
{   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;
}
}
}

/* Close the socket, and exit. */
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);

/* Capabilities are reset over an execve(). */
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;

/* Note: We could probably only remove libpremain.so
*       from the value, instead of clearing it altogether. */
unsetenv("LD_PRELOAD");

/* Verify SOCKFD is an Unix domain socket. */
{   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:
/* SOCKET_FD is not open. Continue as if libpremain.so was never loaded. */
errno = 0;
return;

case ENOTSOCK:
/* SOCKET_FD is not a socket. Continue as if libpremain.so was never loaded. */
errno = 0;
return;

default:
/* All other errors are fatal. */
exit(127);
}

if (addr.sun_family != AF_UNIX) {
/* SOCKET_FD is not an Unix domain socket. Continue as if libpremain.so was never loaded. */
errno = 0;
return;
}
}

/* Make SOCKET_FD blocking and close-on-exec. */
if (fcntl(SOCKET_FD, F_SETFD, (long)FD_CLOEXEC) ||
fcntl(SOCKET_FD, F_SETFL, (long)0L))
exit(127);

/* Send our PID. */
{   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);
}
}

/* Receive the PID from the other end. */
{   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 the PID is > 1, we wait for it to exit.
* If an error occurs, it's not a problem. */
if (pid > (pid_t)1) {
pid_t p;
do {
p = waitpid(pid, NULL, 0);
} while (p == (pid_t)-1 && errno == EINTR);
}

/* All done. */
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 табуляцияс, а не пробелы. Бежать

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 привязан к /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--r--r--  1    0    0    11 May 29 17:15 .X0-lock
drwxrwxrwt  2    0    0  4096 May 29 17:15 .X11-unix/
drwx------  2 1000 1000  4096 May 29 17:15 .esd-1000/
drwx------  2    0    0 16384 Dec  2  2011 lost+found/
drwx------  2 1000 1000  4096 May 29 17:15 pulse-xxxxxxxxx/
drwx------  2    0    0  4096 May 29 17:15 pulse-yyyyyyyyy/

где владелец и группа — 0 (root) и 1000 (user) соответственно, потому что базы данных passwd и group недоступны из chroot. Однако, как я уже упоминал, это можно обойти, изменив и расширив вышеприведенный код, как указано выше.

Несмотря на то, что я пытался написать код с тщательной обработкой ошибок, я не очень тщательно рассмотрел всю операцию в отношении условий ошибок или проблем безопасности; поэтому файлы устанавливаются только для текущего пользователя.

Вопросы?

2

Другие решения


По вопросам рекламы [email protected]