检测在linux上的另一个线程中的程序启动
Detect program launch in another thread on linux
我试图在ELF二进制文件启动后通过(除其他外)chroot来沙箱它们。为此,使用CLONE_FS标记克隆的子进程执行chroot,而父进程通过调用exec函数运行二进制文件。
如果chroot发生在程序完成加载所需的共享库之后,这个技巧实际上是有效的。问题是,我找不到一种方法来检测何时从其他进程中实际发生这种情况。有办法吗?
您可以使用一个预加载库,其中包含一个在main()
之前执行的函数,一个具有CAP_SYS_CHROOT
允许的文件系统功能的辅助二进制文件,以及两者之间的Unix域套接字对。
辅助二进制文件创建套接字对,然后使用clone(CLONE_FS)
fork一个共享文件系统信息的辅助进程,设置LD_PRELOAD
加载预加载库,并执行沙盒二进制文件。(exec
根据沙盒二进制文件系统的功能重置功能,因此沙盒二进制文件将没有任何额外的特权。)
辅助进程将CAP_SYS_CHROOT
添加到有效集中,等待沙盒二进制文件(预加载库)通过套接字通知它,调用chroot()
,并通知沙盒二进制文件(预加载库)成功。
注意:绝对没有必要将辅助二进制标记为setuid root,或者给沙盒二进制任何功能或特权。我们可以用最少的权限来做这件事:CAP_SYS_CHROOT
能力就足够了。
我倾向于只将能力添加到二进制文件的允许集中,这样二进制文件本身必须在chroot()
工作之前将能力添加到有效集中。我觉得这种方法减少了可能的安装/管理员错误的影响。如果您不同意,可以随意从exec.c
中省略相应的代码,并在Makefile中的setcap
命令中使用=pe
而不是=p
。
这里的巧妙之处在于预加载库也可以插入所需的C函数,并使用unix域套接字从辅助进程获取必要的信息;您甚至可以使用SCM_RIGHTS
辅助消息将文件描述符从chroot外部传输到沙盒二进制文件。(本质上,这就是fakeroot
所做的,但反过来:您可以选择沙箱二进制文件可以从chroot环境外部访问哪些文件,而不是创建chroot环境。)只要在套接字的另一端仍然打开时让helper进程保持活动状态,这样它就会在沙盒二进制文件退出后退出。
下面是我的示例实现,它将helper进程作为沙盒二进制文件的子进程启动,在启动沙盒main()
之前,helper进程退出(并预加载库获取它)。
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中的缩进是使用制表符s,而不是空格。运行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
chroot到/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),因为在chroot中无法访问passwd和group数据库。但是,正如我已经提到的,可以通过修改和扩展上述代码来解决这个问题。
虽然我确实尝试编写带有错误处理的代码,但我并没有真正彻底地考虑到错误条件或安全问题的整体操作;这就是为什么只有当前用户才能访问这些文件。
问题吗?
- QSerialPort 在应用程序启动之前正在使用中
- C++ - 从 MFC 应用程序启动记事本,并将其默认标题从 "Untitled - Notepad" 更改为其他内容
- 在应用程序启动时停靠 QDockWidget?
- 在应用程序启动时读取文件
- 如何从 c++ 程序启动 zsh/sh/bash 检查信号 SIGTTIN for 命令"cat &"
- osX - 如何从我的 QT 应用程序启动另一个程序
- 如何跟踪 C++14 中的控制台应用程序启动的次数?
- 我是一个大的C 菜鸟,我需要帮助我的程序启动代码块
- 调试从 Linux 中的另一个C++应用程序启动的 python 脚本
- QT 创建器:程序启动时音量滑块仍播放音频
- 从守护程序启动的控制台应用捕获输出
- 添加新对话框后,MFC 应用程序启动失败并"DLL Initialization Failed"
- 在程序启动时选择不同的QMainWindow,但面临奇怪的QMessageBox exec()行为
- 在应用程序启动之前做某事
- 基于CDialog的应用程序启动时,如何将我的辅助对话框窗口带到顶部
- 在Qt和C++中从另一个应用程序启动一个应用软件会产生问题
- 在 C++/STL/MFC 应用程序启动早期发生的致命异常中,是否可以信任堆栈回溯符号名称?
- 从控制台应用程序启动窗口应用程序
- C++使用Qt库的应用程序在屏幕保护程序启动时停止工作
- 程序启动时出错