care-2.2.1/0000755000175000017500000000000012430615044012001 5ustar ivoireivoirecare-2.2.1/.travis.yml0000644000175000017500000000147512430615044014121 0ustar ivoireivoirelanguage: c compiler: gcc before_install: - sudo apt-get update -qq - sudo apt-get install -qq libtalloc-dev script: if [ ${COVERITY_SCAN_BRANCH} != 1 ]; then make -C src && env PATH=/bin:/usr/bin:/sbin:/usr/sbin make -C tests; fi env: global: # The next declration is the encrypted COVERITY_SCAN_TOKEN, created # via the "travis encrypt" command using the project repo's public key - secure: "G2fG/oWcEOiG+RYLCK3XOO/7ZXn4LoiIyOT4fxxJzb43gu/90lEkbwsALqVAAjh/Fn5T4rYqiECeJ0jEhxvWak1pK/jkFshfXzL34yaY8ZLNtwvnFf7+DDtsj1Hx136TEjYuyc/jasoTf/3fNlIX6Oh1BB6c0UtJfHwTfbreRbI=" addons: coverity_scan: project: name: "cedric-vincent/PRoot" description: "PRoot" notification_email: cedric.vincent@gmail.com build_command_prepend: build_command: make -C src branch_pattern: coverity_scan care-2.2.1/tests/0000755000175000017500000000000012430615044013143 5ustar ivoireivoirecare-2.2.1/tests/test-88888888.c0000644000175000017500000000245212430615044015246 0ustar ivoireivoire#include #include #include #include #include #include #include int main() { int fd; fd = open("/bin/true", O_RDONLY); if (fd < 0) { perror(NULL); exit(EXIT_FAILURE); } close(fd); fd = open("/bin/true/", O_RDONLY); if (fd >= 0 || errno != ENOTDIR) { perror(NULL); exit(EXIT_FAILURE); } close(fd); fd = open("/bin/true/.", O_RDONLY); if (fd >= 0 || errno != ENOTDIR) { perror(NULL); exit(EXIT_FAILURE); } close(fd); fd = open("/bin/true/..", O_RDONLY); if (fd >= 0 || errno != ENOTDIR) { perror(NULL); exit(EXIT_FAILURE); } close(fd); fd = open("/6a05942f08d5a72de56483487963deec", O_RDONLY); if (fd >= 0 || errno != ENOENT) { perror(NULL); exit(EXIT_FAILURE); } close(fd); fd = open("/6a05942f08d5a72de56483487963deec/", O_RDONLY); if (fd >= 0 || errno != ENOENT) { perror(NULL); exit(EXIT_FAILURE); } close(fd); fd = open("/6a05942f08d5a72de56483487963deec/.", O_RDONLY); if (fd >= 0 || errno != ENOENT) { perror(NULL); exit(EXIT_FAILURE); } close(fd); #if 0 /* This test fails in OBS, why? */ fd = open("/6a05942f08d5a72de56483487963deec/..", O_RDONLY); if (fd >= 0 || errno != ENOENT) { perror(NULL); exit(EXIT_FAILURE); } close(fd); #endif exit(EXIT_SUCCESS); } care-2.2.1/tests/test-092c5e26.sh0000644000175000017500000000264012430615044015535 0ustar ivoireivoireif [ ! -x ${ROOTFS}/bin/echo ] || [ ! -x ${ROOTFS}/bin/argv0 ] || [ -z `which mcookie` ] || [ -z `which grep` ] || [ -z `which cat` ] || [ -z `which rm` ]; then exit 125; fi TMP=$(mcookie) TMP_ABS=/tmp/${TMP} ${PROOT} -r ${ROOTFS} argv0 | grep '^argv0$' ${PROOT} -r ${ROOTFS} /bin/argv0 | grep '^/bin/argv0$' cat > ${ROOTFS}/${TMP_ABS} < ${ROOTFS}/${TMP_ABS} < #include #include #include #include #include #include bool sigtrap_received = false; void handler(int signo) { if (signo == SIGTRAP) sigtrap_received = true; } int main() { struct sigaction sa; int status; sa.sa_flags = 0; sa.sa_handler = handler; status = sigemptyset(&sa.sa_mask); if (status < 0) { perror("sigemptyset()"); exit(EXIT_FAILURE); } status = sigaction(SIGTRAP, &sa, 0); if (status < 0) { perror("sigaction(SIGTRAP)"); exit(EXIT_FAILURE); } status = raise(SIGTRAP); if (status != 0) { perror("raise(SIGTRAP)"); exit(EXIT_FAILURE); } if (sigtrap_received) exit(EXIT_SUCCESS); else exit(EXIT_FAILURE); } care-2.2.1/tests/test-1fedd9a3.sh0000644000175000017500000000217212430615044015756 0ustar ivoireivoireif [ -z `which mcookie` ] || [ -z `which id` ] || [ -z `which mkdir` ] || [ -z `which touch` ] || [ -z `which chmod` ] || [ -z `which stat` ] || [ -z `which grep` ]; then exit 125; fi if [ `id -u` -eq 0 ]; then exit 125; fi TMP=/tmp/$(mcookie) mkdir -p ${TMP}/foo chmod a-rwx ${TMP}/foo chmod a-rwx ${TMP} ! ${PROOT} touch ${TMP}/foo/bar [ $? -eq 0 ] ! ${PROOT} -i 123:456 touch ${TMP}/foo/bar [ $? -eq 0 ] ${PROOT} -0 touch ${TMP}/foo/bar stat -c %a ${TMP} | grep '^0$' ! stat -c %a ${TMP}/foo [ $? -eq 0 ] ! ${PROOT} -i 123:456 stat -c %a ${TMP}/foo | grep '^0$' [ $? -eq 0 ] ${PROOT} -0 stat -c %a ${TMP}/foo | grep '^0$' chmod -R a+rwx ${TMP} chmod a-rwx ${TMP}/foo/bar chmod a-rwx ${TMP}/foo chmod a-rwx ${TMP} ! ${PROOT} chmod g+w ${TMP}/foo/bar [ $? -eq 0 ] ! ${PROOT} -i 123:456 chmod g+w ${TMP}/foo/bar [ $? -eq 0 ] ${PROOT} -0 chmod g+w ${TMP}/foo/bar chmod u+wx ${TMP} chmod u+x ${TMP}/foo stat -c %a ${TMP}/foo/bar | grep '^20$' chmod -R +rwx ${TMP} rm -fr ${TMP} mkdir -p ${TMP}/foo chmod -rwx ${TMP} ! rm -fr ${TMP} [ $? -eq 0 ] ! ${PROOT} -i 123:456 rm -fr ${TMP} [ $? -eq 0 ] ${PROOT} -0 rm -fr ${TMP} care-2.2.1/tests/test-de756935.sh0000644000175000017500000000041212430615044015544 0ustar ivoireivoireif [ -z `which mcookie` ] || [ -z `which mkdir` ] || [ -z `which bash` ] || [ -z `which grep` ] || [ -z `which rm` ]; then exit 125; fi TMP=/tmp/$(mcookie) mkdir -p ${TMP} cd ${TMP} ${PROOT} -b ${PWD}:/foo -w /foo bash -c 'pwd' | grep '^/foo$' rm -fr ${TMP} care-2.2.1/tests/test-2401b850.sh0000644000175000017500000000533612430615044015450 0ustar ivoireivoireif [ -z `which mcookie` ] || [ -z `which echo` ] || [ -z `which rm` ] || [ -z `which touch` ] || [ -z `which chmod` ] || [ -z `which grep` ]; then exit 125; fi TMP=/tmp/$(mcookie) TMP2=/tmp/$(mcookie) rm -f ${TMP} touch ${TMP} chmod +x ${TMP} # Valgrind prepends "/bin/sh" in front of foreign binaries and uses # LD_PRELOAD. if $(echo ${PROOT} | grep -q valgrind); then ENV=$(which env) PROOT="env PROOT_FORCE_FOREIGN_BINARY=1 ${PROOT}" COMMAND1="-E LD_PRELOAD=.* -0 /bin/sh /bin/sh ${TMP}" TEST1="-- -U LD_LIBRARY_PATH -E LD_PRELOAD=.* -0 env ${ENV} LD_LIBRARY_PATH=test1 ${TMP}" TEST2="-- -E LD_PRELOAD=.* -E LD_LIBRARY_PATH=test2 -0 /bin/sh /bin/sh ${TMP}" TEST3="-- -E LD_PRELOAD=.* -E LD_LIBRARY_PATH=test2 -0 env ${ENV} LD_LIBRARY_PATH=test1 ${TMP}" TEST4="-- -U LD_LIBRARY_PATH -E LD_PRELOAD=.* -0 env ${ENV} LD_TRACE_LOADED_OBJECTS=1 ${TMP}" TEST5="-- -E LD_PRELOAD= -E LD_LIBRARY_PATH=test5 -0 sh /bin/sh -c ${TMP}" TEST52="-- -E LD_PRELOAD= -E LD_LIBRARY_PATH=test5 -0 sh /bin/sh -c sh -c ${TMP}" TEST6="-- -E LD_PRELOAD= -E LD_LIBRARY_PATH=test5 -0 env ${ENV} LD_LIBRARY_PATH=test6 ${TMP}" COMMAND2="-E LD_PRELOAD=.* -0 ${TMP} ${TMP} ${TMP2}" else COMMAND1="-0 ${TMP} ${TMP}" TEST1="-- -E LD_LIBRARY_PATH=test1 ${COMMAND1}" TEST2="-- -E LD_LIBRARY_PATH=test2 ${COMMAND1}" TEST3="${TEST1}" TEST4="-- -E LD_TRACE_LOADED_OBJECTS=1 -E LD_LIBRARY_PATH=.+ ${COMMAND1}" TEST5="-- -E LD_LIBRARY_PATH=test5 ${COMMAND1}" TEST52=${TEST5} TEST6="-- -E LD_LIBRARY_PATH=test6 ${COMMAND1}" COMMAND2="-0 ${TMP} ${TMP} ${TMP2}" fi ${PROOT} -q true ${TMP} ! ${PROOT} -q false ${TMP} [ $? -eq 0 ] (cd /; ${PROOT} -q ./$(which true) ${TMP}) ! (cd /; ${PROOT} -q ./$(which false) ${TMP}) [ $? -eq 0 ] HOST_LD_LIBRARY_PATH=$(${PROOT} -q 'echo --' env | grep LD_LIBRARY_PATH) test ! -z "${HOST_LD_LIBRARY_PATH}" unset LD_LIBRARY_PATH ${PROOT} -q 'echo --' ${TMP} | grep -- "^-- -U LD_LIBRARY_PATH ${COMMAND1}$" ${PROOT} -q 'echo --' env LD_LIBRARY_PATH=test1 ${TMP} | grep -- "^${TEST1}$" env LD_LIBRARY_PATH=test2 ${PROOT} -q 'echo --' ${TMP} | grep -- "^${TEST2}$" env LD_LIBRARY_PATH=test2 ${PROOT} -q 'echo --' env LD_LIBRARY_PATH=test1 ${TMP} | grep -- "^${TEST3}$" ${PROOT} -q 'echo --' env LD_TRACE_LOADED_OBJECTS=1 ${TMP} | grep -E -- "^${TEST4}$" env LD_LIBRARY_PATH=test5 ${PROOT} -q 'echo --' sh -c ${TMP} | grep -- "^${TEST5}$" env LD_LIBRARY_PATH=test5 ${PROOT} -q 'echo --' sh -c "sh -c ${TMP}" | grep -- "^${TEST52}$" env LD_LIBRARY_PATH=test5 ${PROOT} -q 'echo --' env LD_LIBRARY_PATH=test6 ${TMP} | grep -- "^${TEST6}$" rm -f ${TMP2} echo "#!${TMP}" > ${TMP2} chmod +x ${TMP2} ${PROOT} -q 'echo --' ${TMP2} | grep -- "^-- -U LD_LIBRARY_PATH ${COMMAND2}$" rm -fr ${TMP} ${TMP2} care-2.2.1/tests/test-b94dd86a.sh0000644000175000017500000000013612430615044015701 0ustar ivoireivoireif [ ! -x ${ROOTFS}/bin/true ]; then exit 125; fi ${PROOT} -w /bin -r ${ROOTFS} ./true care-2.2.1/tests/test-aaaaaaaa.sh0000644000175000017500000000346612430615044016154 0ustar ivoireivoireif [ ! -x ${ROOTFS}/bin/true ] || [ -z `which id` ] || [ -z `which mcookie` ] || [ -z `which ln` ] || [ -z `which rm` ]; then exit 125; fi if [ `id -u` -eq 0 ]; then exit 125; fi DONT_EXIST=/$(mcookie) ${PROOT} -r ${ROOTFS} true ! ${PROOT} ${DONT_EXIST} true [ $? -eq 0 ] ${PROOT} -r ${ROOTFS} true ${PROOT} -r /etc -r ${ROOTFS} true ! ${PROOT} -r ${ROOTFS} -r ${DONT_EXIST} true [ $? -eq 0 ] ! ${PROOT} -r ${DONT_EXIST} ${ROOTFS} true [ $? -eq 0 ] ! ${PROOT} ${ROOTFS} -r ${ROOTFS} true [ $? -eq 0 ] ! ${PROOT} -v [ $? -eq 0 ] ${PROOT} -b /bin/true:${DONT_EXIST} ${DONT_EXIST} ! ${PROOT} -r / -b /etc:/ true [ $? -eq 0 ] ! ${PROOT} -b /etc:/ true [ $? -eq 0 ] ${PROOT} -b /etc:/ -r / true TMP1=/tmp/$(mcookie) TMP2=/tmp/$(mcookie) echo "${TMP1}" > ${TMP1} echo "${TMP2}" > ${TMP2} REGULAR=/tmp/$(mcookie) SYMLINK_TO_REGULAR=/tmp/$(mcookie) ln -s ${REGULAR} ${SYMLINK_TO_REGULAR} ${PROOT} -v -1 -b ${TMP1}:${REGULAR} -b ${TMP2}:${SYMLINK_TO_REGULAR} cat ${REGULAR} | grep "^${TMP2}$" ${PROOT} -v -1 -b ${TMP2}:${SYMLINK_TO_REGULAR} -b ${TMP1}:${REGULAR} cat ${REGULAR} | grep "^${TMP1}$" ${PROOT} -v -1 -b ${TMP1}:${REGULAR} -b ${TMP2}:${SYMLINK_TO_REGULAR}! cat ${REGULAR} | grep "^${TMP1}$" ${PROOT} -v -1 -b ${TMP1}:${REGULAR} -b ${TMP2}:${SYMLINK_TO_REGULAR}! cat ${SYMLINK_TO_REGULAR} | grep "^${TMP2}$" ${PROOT} -v -1 -b ${TMP1}:${REGULAR}! -b ${TMP2}:${SYMLINK_TO_REGULAR}! cat ${REGULAR} | grep "^${TMP1}$" ${PROOT} -v -1 -b ${TMP1}:${REGULAR}! -b ${TMP2}:${SYMLINK_TO_REGULAR}! cat ${SYMLINK_TO_REGULAR} | grep "^${TMP2}$" ${PROOT} -v -1 -b ${TMP1}:${REGULAR} -b ${TMP2}:${SYMLINK_TO_REGULAR} cat ${SYMLINK_TO_REGULAR} | grep "^${TMP2}$" ${PROOT} -v -1 -b ${TMP2}:${SYMLINK_TO_REGULAR} -b ${TMP1}:${REGULAR} cat ${SYMLINK_TO_REGULAR} | grep "^${TMP1}$" rm -fr ${TMP1} ${TMP2} ${REGULAR} $SYMLINK_TO_REGULAR} care-2.2.1/tests/test-d2175fc3.sh0000644000175000017500000000061712430615044015616 0ustar ivoireivoireif [ ! -x ${ROOTFS}/bin/readlink ] || [ -z `which grep` ]; then exit 125; fi ${PROOT} -r ${ROOTFS} /bin/readlink /bin/abs-true | grep '^/bin/true$' ${PROOT} -r ${ROOTFS} /bin/readlink /bin/rel-true | grep '^\./true$' ${PROOT} -b /:/host-rootfs -r ${ROOTFS} /bin/readlink /bin/abs-true | grep '^/bin/true$' ${PROOT} -b /:/host-rootfs -r ${ROOTFS} /bin/readlink /bin/rel-true | grep '^./true$' care-2.2.1/tests/test-713b6910.sh0000644000175000017500000000226312430615044015453 0ustar ivoireivoireif [ -z `which mcookie` ] || [ -z `which rm` ] || [ -z `which cat` ] || [ -z `which chmod` ] || [ -z `which ln` ] || [ -z `which grep` ] || [ -z `which mkdir` ] || [ ! -x ${ROOTFS}/bin/readlink ]; then exit 125; fi ###################################################################### TMP1=/tmp/$(mcookie) TMP2=/tmp/$(mcookie) TMP3=/tmp/$(mcookie) TMP4=/tmp/$(mcookie) rm -fr ${TMP1} ${TMP2} ${TMP3} ${TMP4} ###################################################################### cat > ${TMP1} <<'EOF' #!/bin/sh echo $0 EOF chmod +x ${TMP1} ln -s ${TMP1} ${TMP2} ${PROOT} ${TMP2} | grep -v ${TMP1} ${PROOT} ${TMP2} | grep ${TMP2} ###################################################################### mkdir -p ${TMP3} cd ${TMP3} ln -s $(which true) false ! ${PROOT} false echo "#!$(which false)" > true chmod a-x true ${PROOT} true ###################################################################### ln -s ${ROOTFS}/bin/readlink ${TMP4} TEST1=$(${PROOT} ${ROOTFS}/bin/readlink /proc/self/exe) TEST2=$(${PROOT} ${TMP4} /proc/self/exe) test "${TEST1}" = "${TEST2}" ###################################################################### cd / rm -fr ${TMP1} ${TMP2} ${TMP3} ${TMP4} care-2.2.1/tests/test-ptrace00.c0000644000175000017500000000350412430615044015704 0ustar ivoireivoire/* Extracted from strace-4.8/strace.c:test_ptrace_setoptions_for_all. */ #include #include #include #include #include #include #include #include #include int main(void) { bool does_work = false; int status; pid_t pid; switch(pid = fork()) { case -1: perror("fork"); exit(EXIT_FAILURE); case 0: status = ptrace(PTRACE_TRACEME, 0, 0, 0); if (status < 0) { perror("ptrace(PTRACE_TRACEME)"); exit(EXIT_FAILURE); } kill(getpid(), SIGSTOP); exit(EXIT_SUCCESS); default: break; } while (1) { fprintf(stderr, ">>> pid = wait(&status)\n"); pid = wait(&status); if (pid < 0) { perror("wait"); exit(EXIT_FAILURE); } else if (WIFEXITED(status)) { if (WEXITSTATUS(status) == 0) { fprintf(stderr, ">>> EXITSTATUS(status) == 0\n"); break; } fprintf(stderr, "WEXITSTATUS != 0\n"); exit(EXIT_FAILURE); } else if (WIFSIGNALED(status)) { fprintf(stderr, "WIFSIGNALED\n"); exit(EXIT_FAILURE); } else if (!WIFSTOPPED(status)) { fprintf(stderr, "!WIFSTOPPED\n"); exit(EXIT_FAILURE); } else if (WSTOPSIG(status) == SIGSTOP) { fprintf(stderr, ">>> ptrace(PTRACE_SETOPTIONS, ...)\n"); status = ptrace(PTRACE_SETOPTIONS, pid, 0, PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEEXEC); if (status < 0) { perror("ptrace(PTRACE_SETOPTIONS)"); exit(EXIT_FAILURE); } } else if (WSTOPSIG(status) == (SIGTRAP | 0x80)) { fprintf(stderr, ">>> does_work = true\n"); does_work = true; } fprintf(stderr, ">>> ptrace(PTRACE_SYSCALL, ...)\n"); status = ptrace(PTRACE_SYSCALL, pid, 0, 0); if (status < 0) { perror("ptrace(PTRACE_SYSCALL)"); exit(EXIT_FAILURE); } } fprintf(stderr, ">>> exit(...)\n"); exit(does_work ? EXIT_SUCCESS : EXIT_FAILURE); } care-2.2.1/tests/test-5bed7141.c0000644000175000017500000000336012430615044015422 0ustar ivoireivoire#include #include #include #include #include static void *routine(void *path) { int status; status = chdir(path); if (status < 0) { perror("chdir"); pthread_exit((void *)-1); } pthread_exit(NULL); } static void pterror(const char *message, int error) { fprintf(stderr, "%s: %s\n", message, strerror(error)); } void check_cwd(const char *expected) { char path[PATH_MAX]; int status; if (getcwd(path, PATH_MAX) == NULL) { perror("getcwd"); exit(EXIT_FAILURE); } if (strcmp(path, expected) != 0) { fprintf(stderr, "getcwd: %s != %s\n", path, expected); exit(EXIT_FAILURE); } status = readlink("/proc/self/cwd", path, PATH_MAX - 1); if (status < 0) { perror("readlink"); exit(EXIT_FAILURE); } path[status] = '\0'; if (strcmp(path, expected) != 0) { fprintf(stderr, "readlink /proc/self/cwd: %s != %s\n", path, expected); exit(EXIT_FAILURE); } } int main(int argc, char *argv[]) { pthread_t thread; int child_status; void *result; int status; status = pthread_create(&thread, NULL, routine, "/etc"); if (status != 0) { pterror("pthread_create", status); exit(EXIT_FAILURE); } status = pthread_join(thread, &result); if (status != 0) { pterror("pthread_create", status); exit(EXIT_FAILURE); } if (result != NULL) exit(EXIT_FAILURE); check_cwd("/etc"); switch (fork()) { case -1: perror("readlink"); exit(EXIT_FAILURE); case 0: /* child */ status = chdir("/usr"); if (status < 0) { perror("chdir"); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); default: status = wait(&child_status); if (status < 0 || child_status != 0) { perror("wait()"); exit(EXIT_FAILURE); } check_cwd("/etc"); break; } exit(EXIT_SUCCESS); } care-2.2.1/tests/false.c0000644000175000017500000000003512430615044014377 0ustar ivoireivoireint main(void) { return 1; } care-2.2.1/tests/test-33333334.c0000644000175000017500000000073612430615044015202 0ustar ivoireivoire#include /* exit(3), */ #include /* fork(2), */ int main(void) { int child_status; int status; switch (fork()) { case -1: exit(EXIT_FAILURE); case 0: /* child */ return 13; default: /* parent */ status = wait(&child_status); if (status < 0) { perror("wait()"); exit(EXIT_FAILURE); } if (!WIFEXITED(child_status)) exit(EXIT_FAILURE); if (WEXITSTATUS(child_status) != 13) exit(EXIT_FAILURE); exit(EXIT_SUCCESS); } } care-2.2.1/tests/test-af062114.c0000644000175000017500000000105512430615044015331 0ustar ivoireivoire#include #include #include #include #include int main(void) { int fd; int status; bool stop = false; fd = open("/proc/self/cmdline", O_RDONLY); if (fd < 0) { perror("open()"); exit(EXIT_FAILURE); } do { char buffer; status = read(fd, &buffer, 1); if (status < 0) { perror("read()"); exit(EXIT_FAILURE); } stop = (status == 0); status = write(1, &buffer, 1); if (status < 0) { perror("write()"); exit(EXIT_FAILURE); } } while (!stop); exit(EXIT_SUCCESS); } care-2.2.1/tests/test-c5a7a0f0.c0000644000175000017500000000303612430615044015474 0ustar ivoireivoire#include #include #include #include #include static void *setuid_124(void *unused) { int status; status = setuid(124); if (status < 0) { perror("setuid"); pthread_exit((void *)-1); } if (getuid() != 124) { perror("getuid"); pthread_exit((void *)-1); } pthread_exit(NULL); } static void pterror(const char *message, int error) { fprintf(stderr, "%s: %s\n", message, strerror(error)); } int main(void) { pthread_t thread; int child_status; void *result; int status; switch(fork()) { case -1: perror("fork"); exit(EXIT_FAILURE); case 0: /* child */ status = setuid(123); if (status < 0) { perror("setuid"); exit(EXIT_FAILURE); } if (getuid() != 123) { perror("getuid"); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); default: /* parent */ break; } status = wait(&child_status); if (status < 0 || child_status != 0) { perror("wait()"); exit(EXIT_FAILURE); } if (getuid() != 0) { fprintf(stderr, "getuid() == %d != 0\n", getuid()); exit(EXIT_FAILURE); } status = pthread_create(&thread, NULL, setuid_124, NULL); if (status != 0) { pterror("pthread_create", status); exit(EXIT_FAILURE); } status = pthread_join(thread, &result); if (status != 0) { pterror("pthread_create", status); exit(EXIT_FAILURE); } if (result != NULL) { fprintf(stderr, "result != NULL\n"); exit(EXIT_FAILURE); } if (getuid() != 124) { fprintf(stderr, "getuid() == %d != 124\n", getuid()); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } care-2.2.1/tests/test-e940896f.sh0000644000175000017500000000110212430615044015544 0ustar ivoireivoireif [ ! -x ${ROOTFS}/bin/readdir ] || [ -z `which mcookie` ] || [ -z `which rm` ] || [ -z `which mkdir` ] || [ -z `which chmod` ] || [ -z `which rm` ]; then exit 125; fi TMP1=/tmp/$(mcookie) TMP2=${TMP1}/$(mcookie)/$(mcookie) rm -fr ${TMP1} rm -fr ${ROOTFS}/${TMP1} mkdir -p ${TMP2} mkdir -p ${ROOTFS}/${TMP1} chmod -w ${ROOTFS}/${TMP1} cd ${TMP2} ${PROOT} -r ${ROOTFS} -b . readdir ${TMP1} ${PROOT} -r ${ROOTFS} -b . readdir ${TMP2} ${PROOT} -r ${ROOTFS} -b . readdir ${TMP2}/.. ${PROOT} -r ${ROOTFS} -b . readdir ${TMP2}/../.. rm -fr ${TMP1} rm -fr ${ROOTFS}/${TMP1} care-2.2.1/tests/test-e99993c8.sh0000644000175000017500000000047312430615044015566 0ustar ivoireivoireif [ -z `which uname` ] || [ -z `which grep` ]; then exit 125; fi LONG_RELEASE=0123456789012345678901234567890123456789012345678901234567890123456789 ${PROOT} -k 3.33.333 uname -r | grep ^3.33.333$ ${PROOT} -k ${LONG_RELEASE} uname -r | grep ^0123456789012345678901234567890123456789012345678901234567890123$ care-2.2.1/tests/test-pppppppp.sh0000644000175000017500000000047112430615044016335 0ustar ivoireivoireif [ -z `which mcookie` ] || [ -z `which true` ] || [ -z `which mkdir` ]|| [ -z `which env` ]; then exit 125; fi TMP=/tmp/$(mcookie) mkdir -p ${TMP}/true ! ${PROOT} true if [ $? -eq 0 ]; then exit 125; fi env PATH=${TMP}:${PATH} ${PROOT} true env PATH=${TMP}:${PATH} ${PROOT} env true rm -fr ${TMP} care-2.2.1/tests/test-kkkkkkkk.c0000644000175000017500000000630012430615044016072 0ustar ivoireivoire#include /* syscall(2), sysconf(3), */ #include /* SYS_brk, */ #include /* puts(3), */ #include /* exit(3), EXIT_*, */ #include /* uint*_t, */ #include /* mmap(2), MAP_*, */ #include /* memset(3), */ int main() { int exit_status = EXIT_SUCCESS; uint8_t *current_brk = 0; uint8_t *initial_brk; uint8_t *new_brk; uint8_t *old_brk; int failure = 0; long page_size; int i; page_size = sysconf(_SC_PAGE_SIZE); if (page_size <= 0) return 125; void test_brk(int increment, int expected_result) { new_brk = (uint8_t *) syscall(SYS_brk, current_brk + increment); if ((new_brk == current_brk) == expected_result) failure = 1; current_brk = (uint8_t *) syscall(SYS_brk, 0); } void test_result() { if (!failure) puts("OK"); else { puts("failure"); exit_status = EXIT_FAILURE; } } void test_title(const char *title) { failure = 0; printf("%-45s : ", title); fflush(stdout); } test_title("Initialization"); test_brk(0, 1); initial_brk = current_brk; test_result(); test_title("Don't set the \"brk\" below its initial value"); test_brk(page_size, 1); test_brk(-2 * page_size, 0); test_brk(-page_size, 1); test_result(); test_title("Don't overlap \"brk\" pages"); test_brk(page_size, 1); test_brk(page_size, 1); test_result(); /* Preparation for the test "Re-allocated heap is initialized". */ old_brk = current_brk - page_size; memset(old_brk, 0xFF, page_size); test_title("Don't allocate the same \"brk\" page twice"); test_brk(-page_size, 1); test_brk(page_size, 1); test_result(); test_title("Re-allocated \"brk\" pages are initialized"); for (i = 0; i < page_size; i++) { if (old_brk[i] != 0) { printf("(index = %d, value = 0x%x) ", i, old_brk[i]); failure = 1; break; } } test_result(); #if 0 test_title("Don't allocate \"brk\" pages over \"mmap\" pages"); new_brk = mmap(current_brk, page_size / 2, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0); if (new_brk == (void *) -1) puts("unknown"); else { test_brk(page_size, 0); test_result(); } #endif test_title("All \"brk\" pages are writable (please wait)"); #if 0 if (munmap(current_brk, page_size / 2) != 0) puts("unknown"); else { #endif while (current_brk - initial_brk < 512*1024*1024UL) { old_brk = current_brk; test_brk(page_size, -1); if (old_brk == current_brk) break; for (i = 0; i < page_size; i++) old_brk[i] = 0xAA; } test_result(); #if 0 } #endif test_title("Maximum size of the heap >= 1MB"); failure = (current_brk - initial_brk) < 1024 * 1024; test_result(); test_title("All \"brk\" pages are cleared (please wait)"); test_brk(initial_brk - current_brk, 1); if (current_brk != initial_brk) puts("unknown"); else { while (current_brk - initial_brk < 1024*1024*1024UL) { old_brk = current_brk; test_brk(3 * page_size, -1); if (old_brk == current_brk) break; for (i = 0; i < 3 * page_size; i++) { if (old_brk[i] != 0) { printf("(index = %d, value = 0x%x) ", i, old_brk[i]); failure = 1; goto end; } } } end: test_result(); } exit(exit_status); } care-2.2.1/tests/test-3dec4597.sh0000644000175000017500000000017112430615044015622 0ustar ivoireivoireif [ ! -x ${ROOTFS}/bin/pwd ]; then exit 125; fi ${PROOT} -m /tmp:/longer-tmp -w /longer-tmp -r ${ROOTFS} /bin/pwd care-2.2.1/tests/test-79cf6614.c0000644000175000017500000000120712430615044015355 0ustar ivoireivoire/* * Submitted-by: Thomas P. HIGDON * Ref.: https://groups.google.com/d/msg/proot_me/4WbUndy-aXI/lmKiDfoIK_IJ */ #include #include #include #include int main() { int status; struct timeval times[2] = { {.tv_sec = 52353, .tv_usec = 0}, { .tv_sec = 52353, .tv_usec = 0 } }; char tmp[] = "proot-XXXXXX"; mktemp(tmp); if (tmp[0] == '\0') exit(EXIT_FAILURE); (void) unlink(tmp); status = symlink("/etc/fstab", tmp); if (status < 0) exit(EXIT_FAILURE); status = lutimes(tmp, times); exit(status < 0 && errno != ENOSYS ? EXIT_FAILURE : EXIT_SUCCESS); } care-2.2.1/tests/test-0228fbe7.sh0000644000175000017500000000151012430615044015610 0ustar ivoireivoireif [ -z `which mcookie` ] || [ -z `which env` ] || [ -z `which head` ] || [ -z `which cmp` ] || [ -z `which rm` ] || [ ! -x ${ROOTFS}/bin/ptrace-2 ] || [ ! -x ${ROOTFS}/bin/true ]; then exit 125; fi TMP1=/tmp/$(mcookie) TMP2=/tmp/$(mcookie) # TODO: remove the "grep -v" tweak. ${ROOTFS}/bin/ptrace-2 ${ROOTFS}/bin/true 2>&1 >${TMP1} ${PROOT} ${ROOTFS}/bin/ptrace-2 ${ROOTFS}/bin/true 2>&1 >${TMP2} cmp ${TMP1} ${TMP2} env PTRACER_BEHAVIOR_1=1 ${ROOTFS}/bin/ptrace-2 ${ROOTFS}/bin/true 2>&1 >${TMP1} env PTRACER_BEHAVIOR_1=1 ${PROOT} ${ROOTFS}/bin/ptrace-2 ${ROOTFS}/bin/true 2>&1 >${TMP2} cmp ${TMP1} ${TMP2} env PTRACER_BEHAVIOR_2=1 ${ROOTFS}/bin/ptrace-2 ${ROOTFS}/bin/true 2>&1 >${TMP1} env PTRACER_BEHAVIOR_2=1 ${PROOT} ${ROOTFS}/bin/ptrace-2 ${ROOTFS}/bin/true 2>&1 >${TMP2} cmp ${TMP1} ${TMP2} rm -f ${TMP1} ${TMP2} care-2.2.1/tests/ptrace-2.c0000644000175000017500000002560512430615044014734 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2013 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* pid_t, waitpid(2), */ #include /* ptrace(3), PTRACE_*, */ #include /* struct user*, */ #include /* waitpid(2), */ #include /* offsetof(), */ #include /* fork(2), getpid(2), */ #include /* errno(3), */ #include /* bool, true, false, */ #include /* exit, EXIT_*, */ #include /* fprintf(3), stderr, */ #include /* *int*_t, */ #if !defined(ARCH_X86_64) && !defined(ARCH_ARM_EABI) && !defined(ARCH_X86) && !defined(ARCH_SH4) # if defined(__x86_64__) # define ARCH_X86_64 1 # elif defined(__ARM_EABI__) # define ARCH_ARM_EABI 1 # elif defined(__aarch64__) # define ARCH_ARM64 1 # elif defined(__arm__) # error "Only EABI is currently supported for ARM" # elif defined(__i386__) # define ARCH_X86 1 # elif defined(__SH4__) # define ARCH_SH4 1 # else # error "Unsupported architecture" # endif #endif /** * Compute the offset of the register @reg_name in the USER area. */ #define USER_REGS_OFFSET(reg_name) \ (offsetof(struct user, regs) \ + offsetof(struct user_regs_struct, reg_name)) #define REG(regs, index) \ (*(unsigned long*) (((uint8_t *) ®s) + reg_offset[index])) typedef enum { SYSARG_NUM = 0, SYSARG_1, SYSARG_2, SYSARG_3, SYSARG_4, SYSARG_5, SYSARG_6, SYSARG_RESULT, STACK_POINTER, INSTR_POINTER, } Reg; /* Specify the ABI registers (syscall argument passing, stack pointer). * See sysdeps/unix/sysv/linux/${ARCH}/syscall.S from the GNU C Library. */ #if defined(ARCH_X86_64) static off_t reg_offset[] = { [SYSARG_NUM] = USER_REGS_OFFSET(orig_rax), [SYSARG_1] = USER_REGS_OFFSET(rdi), [SYSARG_2] = USER_REGS_OFFSET(rsi), [SYSARG_3] = USER_REGS_OFFSET(rdx), [SYSARG_4] = USER_REGS_OFFSET(r10), [SYSARG_5] = USER_REGS_OFFSET(r8), [SYSARG_6] = USER_REGS_OFFSET(r9), [SYSARG_RESULT] = USER_REGS_OFFSET(rax), [STACK_POINTER] = USER_REGS_OFFSET(rsp), [INSTR_POINTER] = USER_REGS_OFFSET(rip), }; static off_t reg_offset_x86[] = { [SYSARG_NUM] = USER_REGS_OFFSET(orig_rax), [SYSARG_1] = USER_REGS_OFFSET(rbx), [SYSARG_2] = USER_REGS_OFFSET(rcx), [SYSARG_3] = USER_REGS_OFFSET(rdx), [SYSARG_4] = USER_REGS_OFFSET(rsi), [SYSARG_5] = USER_REGS_OFFSET(rdi), [SYSARG_6] = USER_REGS_OFFSET(rbp), [SYSARG_RESULT] = USER_REGS_OFFSET(rax), [STACK_POINTER] = USER_REGS_OFFSET(rsp), [INSTR_POINTER] = USER_REGS_OFFSET(rip), }; #undef REG #define REG(regs, index) \ (*(unsigned long*) (regs.cs == 0x23 \ ? (((uint8_t *) ®s) + reg_offset_x86[index]) \ : (((uint8_t *) ®s) + reg_offset[index]))) #elif defined(ARCH_ARM_EABI) static off_t reg_offset[] = { [SYSARG_NUM] = USER_REGS_OFFSET(uregs[7]), [SYSARG_1] = USER_REGS_OFFSET(uregs[0]), [SYSARG_2] = USER_REGS_OFFSET(uregs[1]), [SYSARG_3] = USER_REGS_OFFSET(uregs[2]), [SYSARG_4] = USER_REGS_OFFSET(uregs[3]), [SYSARG_5] = USER_REGS_OFFSET(uregs[4]), [SYSARG_6] = USER_REGS_OFFSET(uregs[5]), [SYSARG_RESULT] = USER_REGS_OFFSET(uregs[0]), [STACK_POINTER] = USER_REGS_OFFSET(uregs[13]), [INSTR_POINTER] = USER_REGS_OFFSET(uregs[15]), }; #elif defined(ARCH_ARM64) #undef USER_REGS_OFFSET #define USER_REGS_OFFSET(reg_name) offsetof(struct user_regs_struct, reg_name) static off_t reg_offset[] = { [SYSARG_NUM] = USER_REGS_OFFSET(regs[8]), [SYSARG_1] = USER_REGS_OFFSET(regs[0]), [SYSARG_2] = USER_REGS_OFFSET(regs[1]), [SYSARG_3] = USER_REGS_OFFSET(regs[2]), [SYSARG_4] = USER_REGS_OFFSET(regs[3]), [SYSARG_5] = USER_REGS_OFFSET(regs[4]), [SYSARG_6] = USER_REGS_OFFSET(regs[5]), [SYSARG_RESULT] = USER_REGS_OFFSET(regs[0]), [STACK_POINTER] = USER_REGS_OFFSET(sp), [INSTR_POINTER] = USER_REGS_OFFSET(pc), }; #elif defined(ARCH_X86) static off_t reg_offset[] = { [SYSARG_NUM] = USER_REGS_OFFSET(orig_eax), [SYSARG_1] = USER_REGS_OFFSET(ebx), [SYSARG_2] = USER_REGS_OFFSET(ecx), [SYSARG_3] = USER_REGS_OFFSET(edx), [SYSARG_4] = USER_REGS_OFFSET(esi), [SYSARG_5] = USER_REGS_OFFSET(edi), [SYSARG_6] = USER_REGS_OFFSET(ebp), [SYSARG_RESULT] = USER_REGS_OFFSET(eax), [STACK_POINTER] = USER_REGS_OFFSET(esp), [INSTR_POINTER] = USER_REGS_OFFSET(eip), }; #elif defined(ARCH_SH4) static off_t reg_offset[] = { [SYSARG_NUM] = USER_REGS_OFFSET(regs[3]), [SYSARG_1] = USER_REGS_OFFSET(regs[4]), [SYSARG_2] = USER_REGS_OFFSET(regs[5]), [SYSARG_3] = USER_REGS_OFFSET(regs[6]), [SYSARG_4] = USER_REGS_OFFSET(regs[7]), [SYSARG_5] = USER_REGS_OFFSET(regs[0]), [SYSARG_6] = USER_REGS_OFFSET(regs[1]), [SYSARG_RESULT] = USER_REGS_OFFSET(regs[0]), [STACK_POINTER] = USER_REGS_OFFSET(regs[15]), [INSTR_POINTER] = USER_REGS_OFFSET(pc), }; #else #error "Unsupported architecture" #endif int main(int argc, char *argv[]) { enum __ptrace_request restart_how; int last_exit_status = -1; pid_t *pids = NULL; long status; int signal; pid_t pid; if (argc <= 1) { fprintf(stderr, "Usage: %s /path/to/exe [args]\n", argv[0]); exit(EXIT_FAILURE); } pid = fork(); switch(pid) { case -1: perror("fork()"); exit(EXIT_FAILURE); case 0: /* child */ status = ptrace(PTRACE_TRACEME, 0, NULL, NULL); if (status < 0) { perror("ptrace(TRACEME)"); exit(EXIT_FAILURE); } /* Synchronize with the tracer's event loop. */ kill(getpid(), SIGSTOP); execvp(argv[1], &argv[1]); exit(EXIT_FAILURE); default: /* parent */ break; } restart_how = (getenv("PTRACER_BEHAVIOR_1") == NULL ? PTRACE_SYSCALL : PTRACE_CONT); pids = calloc(1, sizeof(pid_t)); if (pids == NULL) { perror("calloc()"); exit(EXIT_FAILURE); } signal = 0; while (1) { int tracee_status; pid_t pid; pid_t sid; int i; /* Wait for the next tracee's stop. */ pid = waitpid(-1, &tracee_status, __WALL); if (pid < 0) { if (errno != ECHILD) { perror("waitpid()"); exit(EXIT_FAILURE); } break; } sid = 0; for (i = 0; pids[i] != 0; i++) { if (pid == pids[i]) { sid = i + 1; break; } } if (sid == 0) { pids = realloc(pids, (i + 1 + 1) * sizeof(pid_t)); if (pids == NULL) { perror("realloc()"); exit(EXIT_FAILURE); } pids[i + 1] = 0; pids[i] = pid; sid = i + 1; } if (WIFEXITED(tracee_status)) { fprintf(stderr, "sid %d: exited with status %d\n", sid, WEXITSTATUS(tracee_status)); last_exit_status = WEXITSTATUS(tracee_status); continue; /* Skip the call to ptrace(SYSCALL). */ } else if (WIFSIGNALED(tracee_status)) { fprintf(stderr, "sid %d: terminated with signal %d\n", sid, WTERMSIG(tracee_status)); continue; /* Skip the call to ptrace(SYSCALL). */ } else if (WIFCONTINUED(tracee_status)) { fprintf(stderr, "sid %d: continued\n", sid); signal = SIGCONT; } else if (WIFSTOPPED(tracee_status)) { struct user_regs_struct regs; /* Don't use WSTOPSIG() to extract the signal * since it clears the PTRACE_EVENT_* bits. */ signal = (tracee_status & 0xfff00) >> 8; switch (signal) { static bool skip_bare_sigtrap = false; long ptrace_options; case SIGTRAP: fprintf(stderr, "sid %d: SIGTRAP\n", sid); status = ptrace(PTRACE_GETREGS, pid, NULL, ®s); if (status < 0) { fprintf(stderr, "sigtrap: ?, ?\n"); } else { fprintf(stderr, "sigtrap: %ld == 0 ? %d\n", REG(regs, SYSARG_NUM), REG(regs, SYSARG_RESULT) == 0); } /* PTRACER_BEHAVIOR_1 */ if (restart_how != PTRACE_SYSCALL) { restart_how = PTRACE_SYSCALL; signal = 0; break; } /* Distinguish some events from others and * automatically trace each new process with * the same options. * * Note that only the first bare SIGTRAP is * related to the tracing loop, others SIGTRAP * carry tracing information because of * TRACE*FORK/CLONE/EXEC. */ if (skip_bare_sigtrap) { signal = 0; break; } skip_bare_sigtrap = true; ptrace_options = PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK | PTRACE_O_TRACEVFORKDONE | PTRACE_O_TRACECLONE | PTRACE_O_TRACEEXIT; if (getenv("PTRACER_BEHAVIOR_2") == NULL) ptrace_options |= PTRACE_O_TRACEEXEC; status = ptrace(PTRACE_SETOPTIONS, pid, NULL, ptrace_options); if (status < 0) { perror("ptrace(PTRACE_SETOPTIONS)"); exit(EXIT_FAILURE); } /* Fall through. */ case SIGTRAP | 0x80: fprintf(stderr, "sid %d: PTRACE_EVENT_SYSGOOD\n", sid); signal = 0; status = ptrace(PTRACE_GETREGS, pid, NULL, ®s); if (status < 0) { fprintf(stderr, "syscall(?) = ?\n"); } else { fprintf(stderr, "syscall(%ld) == 0 ? %d\n", REG(regs, SYSARG_NUM), REG(regs, SYSARG_RESULT) == 0); } break; case SIGTRAP | PTRACE_EVENT_VFORK << 8: fprintf(stderr, "sid %d: PTRACE_EVENT_VFORK\n", sid); signal = 0; break; case SIGTRAP | PTRACE_EVENT_VFORK_DONE << 8: fprintf(stderr, "sid %d: PTRACE_EVENT_VFORK\n", sid); signal = 0; break; case SIGTRAP | PTRACE_EVENT_FORK << 8: fprintf(stderr, "sid %d: PTRACE_EVENT_FORK\n", sid); signal = 0; break; case SIGTRAP | PTRACE_EVENT_CLONE << 8: fprintf(stderr, "sid %d: PTRACE_EVENT_CLONE\n", sid); signal = 0; break; case SIGTRAP | PTRACE_EVENT_EXEC << 8: fprintf(stderr, "sid %d: PTRACE_EVENT_EXEC\n", sid); signal = 0; break; case SIGTRAP | PTRACE_EVENT_EXIT << 8: fprintf(stderr, "sid %d: PTRACE_EVENT_EXIT\n", sid); signal = 0; break; case SIGSTOP: fprintf(stderr, "sid %d: SIGSTOP\n", sid); signal = 0; break; default: break; } } else { fprintf(stderr, "sid %d: unknown trace event\n", sid); signal = 0; } /* Restart the tracee and stop it at the next entry or * exit of a system call. */ status = ptrace(restart_how, pid, NULL, signal); if (status < 0) fprintf(stderr, "ptrace(, %d, %d)\n", sid, signal); } return last_exit_status; } care-2.2.1/tests/symlink.c0000644000175000017500000000071012430615044014773 0ustar ivoireivoire#include /* syscall(2), */ #include /* perror(3), fprintf(3), */ #include /* exit(3), */ #include /* SYS_symlink, */ int main(int argc, char *argv[]) { int status; if (argc != 3) { fprintf(stderr, "usage: symlink REFEREE REFERER\n"); exit(EXIT_FAILURE); } status = syscall(SYS_symlink, argv[1], argv[2]); if (status < 0) { perror("symlink()"); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } care-2.2.1/tests/test-ffffffff.sh0000644000175000017500000000043712430615044016217 0ustar ivoireivoireif [ -z `which mcookie` ] || [ -z `which touch` ] || [ -z `which stat` ] || [ -z `which grep` ] || [ -z `which rm` ]; then exit 125; fi TMP=/tmp/$(mcookie) touch ${TMP} ${PROOT} -0 stat -c %u:%g ${TMP} | grep 0:0 ${PROOT} -i 123:456 stat -c %u:%g ${TMP} | grep 123:456 rm ${TMP} care-2.2.1/tests/test-99999999.sh0000644000175000017500000000327012430615044015445 0ustar ivoireivoireif [ ! -x ${ROOTFS}/bin/readlink ] || [ -z `which readlink` ] || [ -z `which cut` ] || [ -z `which grep` ] || [ -z `which md5sum` ]; then exit 125; fi WHICH_READLINK=$(readlink -f $(which readlink)) ${PROOT} readlink /proc/self/exe | grep ^${WHICH_READLINK}$ ${PROOT} sh -c 'readlink /proc/self/exe' | grep ^${WHICH_READLINK}$ ${PROOT} bash -c 'readlink /proc/$$/exe' | grep ^${WHICH_READLINK}$ ${PROOT} -b /proc -r ${ROOTFS} readlink /proc/self/exe | grep ^/bin/readlink$ ${PROOT} readlink /proc/1/../self/exe | grep ^${WHICH_READLINK}$ ${PROOT} sh -c 'readlink /proc/1/../self/exe' | grep ^${WHICH_READLINK}$ ${PROOT} bash -c 'readlink /proc/1/../$$/exe' | grep ^${WHICH_READLINK}$ ${PROOT} -b /proc -r ${ROOTFS} readlink /proc/1/../self/exe | grep ^/bin/readlink$ ! ${PROOT} readlink /proc/self/exe/ [ $? -eq 0 ] ! ${PROOT} readlink /proc/self/exe/.. [ $? -eq 0 ] ! ${PROOT} readlink /proc/self/exe/../exe [ $? -eq 0 ] ! ${PROOT} -b /proc readlink /proc/self/exe/ [ $? -eq 0 ] ! ${PROOT} -b /proc readlink /proc/self/exe/.. [ $? -eq 0 ] ! ${PROOT} -b /proc readlink /proc/self/exe/../exe [ $? -eq 0 ] TEST=$(${PROOT} readlink /proc/self/fd/0 | grep -E "^/proc/[[:digit:]]+/fd/0$" | true) test -z $TEST TEST=$(${PROOT} -b /proc -r ${ROOTFS} readlink /proc/self/fd/0 | grep -E "^/proc/[[:digit:]]+/fd/0$" | true) test -z $TEST if [ ! -z $$ ]; then TEST=$(readlink -f /proc/$$/exe) ${PROOT} sh -c 'true; readlink /proc/$$/exe' | grep ${TEST} fi MD5=$(md5sum $(which md5sum) | cut -f 1 -d ' ') MD5_PROOT=$(${PROOT} md5sum /proc/self/exe | cut -f 1 -d ' ') test ${MD5_PROOT} = ${MD5} MD5_PROOT=$(${PROOT} -b /proc md5sum /proc/self/exe | cut -f 1 -d ' ') test ${MD5_PROOT} = ${MD5} care-2.2.1/tests/test-0238c7f1.sh0000644000175000017500000000043112430615044015527 0ustar ivoireivoireif [ ! -x ${ROOTFS}/bin/pwd ] || [ -z `which grep` ]; then exit 125; fi ${PROOT} -m /:/hostfs -w /hostfs/etc -r ${ROOTFS} pwd | grep '^/hostfs/etc$' ${PROOT} -m /:/hostfs -w /hostfs -r ${ROOTFS} pwd | grep '^/hostfs$' ${PROOT} -m /:/hostfs -w / -r ${ROOTFS} pwd | grep '^/$' care-2.2.1/tests/test-d1da0d8d.sh0000644000175000017500000000046512430615044015752 0ustar ivoireivoireif [ ! -x ${ROOTFS}/bin/readlink ] || [ ! -e /proc/self/cwd ] || [ -z `which grep` ]; then exit 125; fi ${PROOT} -m /proc -m /tmp:/asym -w /asym -r ${ROOTFS} /bin/readlink /proc/self/cwd | grep '^/asym$' ${PROOT} -m /proc -m /tmp:/asym -w /tmp -r ${ROOTFS} /bin/readlink /proc/self/cwd | grep '^/tmp$' care-2.2.1/tests/test-fdf487a0.c0000644000175000017500000000107612430615044015513 0ustar ivoireivoire#include #include #include #include #include #include int main() { int fd; fd = openat(0, "foo", O_RDONLY); if (fd >= 0 || errno != ENOTDIR) { printf("1. %d %d\n", fd, (int) errno); exit(EXIT_FAILURE); } fd = openat(0, "", O_RDONLY); if (fd >= 0 || errno != ENOENT) { printf("2. %d %d\n", fd, (int) errno); exit(EXIT_FAILURE); } fd = openat(0, NULL, O_RDONLY); if (fd >= 0 || errno != EFAULT) { printf("3. %d %d\n", fd, (int) errno); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } care-2.2.1/tests/test-c6b77b77.sh0000644000175000017500000000013012430615044015616 0ustar ivoireivoireif [ -z `which make` ]; then exit 125; fi ${PROOT} make -f ${PWD}/test-c6b77b77.mk care-2.2.1/tests/test-xxxxxxxx.c0000644000175000017500000000171412430615044016246 0ustar ivoireivoire#include #include #include #include #include extern char *environ[]; #define CONTENT "this isn't an executable" int main(void) { char * const argv[] = { "argv0", "argv1", "argv2", NULL }; char tmp_name[] = "/tmp/proot-XXXXXX"; int status; int fd; status = execve("/tmp", argv, environ); if (errno != EACCES) { perror("execve (1)"); exit(EXIT_FAILURE); } fd = mkstemp(tmp_name); if (fd < 0) { perror("mkstemp"); exit(EXIT_FAILURE); } status = write(fd, CONTENT, sizeof(CONTENT)); if (status != sizeof(CONTENT)) { perror("write"); exit(EXIT_FAILURE); } close(fd); status = chmod(tmp_name, 0700); if (status < 0) { perror("chmod"); exit(EXIT_FAILURE); } status = execve(tmp_name, argv, environ); if (errno != ENOEXEC) { perror("execve (2)"); exit(EXIT_FAILURE); } printf("Check the stack integrity: %F + %F\n", (double) status, (double) errno); exit(EXIT_SUCCESS); } care-2.2.1/tests/test-d2175fc4.c0000644000175000017500000000117612430615044015430 0ustar ivoireivoire#include /* syscall(2), */ #include /* perror(3), fprintf(3), */ #include /* PATH_MAX, */ #include /* exit(3), */ #include /* bzero(3), */ #include /* SYS_readlink, */ int main(int argc, char *argv[]) { char path[PATH_MAX]; int status; bzero(path, sizeof(path)); status = syscall(SYS_readlink, "/proc/self/exe", path, PATH_MAX); if (status < 0) { perror("readlink()"); exit(EXIT_FAILURE); } if (status >= PATH_MAX) return 125; if (path[status] != '\0') { path[PATH_MAX - 1] = '\0'; puts(path); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } care-2.2.1/tests/test-9f5eeb72.sh0000644000175000017500000000745412430615044015716 0ustar ivoireivoireif [ -z `which mkdir` ] || [ -z `which chmod` ] || [ -z `which touch` ] || [ -z `which ln` ] || [ -z `which cpio` ] || [ -z `which stat` ] || [ -z `which cat` ] || [ -z `which readlink` ] || [ -z `which mcookie` ] || [ -z `which mknod` ]; then exit 125; fi if [ ! -e $CARE ]; then exit 125; fi unset PROOT TMP=/tmp/$(mcookie) mkdir ${TMP} cd ${TMP} export LANG=en_US.UTF-8 mkdir a echo "I'm a bee" > a/b echo "I'm a sea" > a/c chmod -w a touch Å‚ ln Å‚ d ln -s dangling_symlink e mknod f p mkdir -p x/y chmod -rwx x for BUNCH in \ "FORMAT=/ EXTRACT=''" \ "FORMAT=.raw EXTRACT='${CARE} -x'" \ "FORMAT=.cpio EXTRACT='${CARE} -x'" \ "FORMAT=.cpio.gz EXTRACT='${CARE} -x'" \ "FORMAT=.cpio.lzo EXTRACT='${CARE} -x'" \ "FORMAT=.tar EXTRACT='${CARE} -x'" \ "FORMAT=.tgz EXTRACT='${CARE} -x'" \ "FORMAT=.tar.gz EXTRACT='${CARE} -x'" \ "FORMAT=.tzo EXTRACT='${CARE} -x'" \ "FORMAT=.tar.lzo EXTRACT='${CARE} -x'" \ "FORMAT=.bin EXTRACT='${CARE} -x'" \ "FORMAT=.bin EXTRACT='sh -c'" \ "FORMAT=.gz.bin EXTRACT='sh -c'" \ "FORMAT=.lzo.bin EXTRACT='sh -c'" \ "FORMAT=.cpio.bin EXTRACT='sh -c'" \ "FORMAT=.cpio.gz.bin EXTRACT='sh -c'" \ "FORMAT=.cpio.lzo.bin EXTRACT='sh -c'" \ "FORMAT=.tar.bin EXTRACT='sh -c'" \ "FORMAT=.tgz.bin EXTRACT='sh -c'" \ "FORMAT=.tzo.bin EXTRACT='sh -c'" \ "FORMAT=.tar.gz.bin EXTRACT='sh -c'" \ "FORMAT=.tar.lzo.bin EXTRACT='sh -c'" do eval $BUNCH CWD=${PWD} if echo ${FORMAT} | grep '.bin' && ${CARE} -V | grep '(.bin): no'; then continue fi # Check: permissions, unordered archive, UTF-8, hard-links ${CARE} -o test${FORMAT} cat a/b Å‚ d a/c if [ -n "${EXTRACT}" ]; then ! chmod +rwx -R test-${FORMAT}-1 rm -fr test-${FORMAT}-1 mkdir test-${FORMAT}-1 cd test-${FORMAT}-1 ${EXTRACT} ../test${FORMAT} fi test -d test/rootfs/${CWD}/a test -f test/rootfs/${CWD}/a/b test -f test/rootfs/${CWD}/a/c test -f test/rootfs/${CWD}/Å‚ test -f test/rootfs/${CWD}/d INODE1=$(stat -c %i test/rootfs/${CWD}/d) INODE2=$(stat -c %i test/rootfs/${CWD}/Å‚) [ $INODE1 -eq $INODE2 ] PERM1=$(stat -c %a ${CWD}/a) PERM2=$(stat -c %a test/rootfs/${CWD}/a) [ $PERM1 -eq $PERM2 ] if [ -n "${EXTRACT}" ]; then cd .. else ! chmod +rwx -R test rm -fr test fi # Check: last archived version wins, symlinks ${CARE} -o test${FORMAT} sh -c 'ls a; ls a/b; ls -l e' if [ -n "${EXTRACT}" ]; then ! chmod +rwx -R test-${FORMAT}-2 rm -fr test-${FORMAT}-2 mkdir test-${FORMAT}-2 cd test-${FORMAT}-2 ${EXTRACT} ../test${FORMAT} fi B=$(cat test/rootfs/${CWD}/a/b) [ x"$B" != x ] [ "$B" = "I'm a bee" ] test -L test/rootfs/${CWD}/e F=$(readlink test/rootfs/${CWD}/e) [ x"$F" != x ] [ "$F" = "dangling_symlink" ] if [ -n "${EXTRACT}" ]; then cd .. else ! chmod +rwx -R test rm -fr test fi # Check: non-regular files are archived/extractable ${CARE} -d -p /dev -p /proc -o test${FORMAT} sh -c 'ls -l f' if [ -n "${EXTRACT}" ]; then ! chmod +rwx -R test-${FORMAT}-1 rm -fr test-${FORMAT}-1 mkdir test-${FORMAT}-1 cd test-${FORMAT}-1 ${EXTRACT} ../test${FORMAT} fi [ "fifo" = "$(stat -c %F test/rootfs/${CWD}/f)" ] if [ -n "${EXTRACT}" ]; then cd .. else ! chmod +rwx -R test rm -fr test fi # Check: extractable archive ${CARE} -o test${FORMAT} chmod -R +rwx x if [ -n "${EXTRACT}" ]; then ! chmod +rwx -R test-${FORMAT}-3 rm -fr test-${FORMAT}-3 mkdir test-${FORMAT}-3 cd test-${FORMAT}-3 ${EXTRACT} ../test${FORMAT} cd .. else ! chmod +rwx -R test rm -fr test fi done cd .. chmod +rwx -R ${TMP} rm -fr ${TMP} care-2.2.1/tests/test-c6b77b77.mk0000644000175000017500000000013512430615044015620 0ustar ivoireivoireSHELL=/bin/bash FOO:=$(shell test -e /dev/null && echo OK) all: @/usr/bin/test -n "$(FOO)" care-2.2.1/tests/test-2db65cd2.sh0000644000175000017500000000101712430615044015666 0ustar ivoireivoireif [ ! -x ${ROOTFS}/bin/true ] || [ -z `which gdb` ]; then exit 125; fi TMP1=/tmp/$(mcookie) TMP2=/tmp/$(mcookie) TMP3=/tmp/$(mcookie) TMP4=/tmp/$(mcookie) TMP5=/tmp/$(mcookie) cat > ${TMP5} < ${TMP1} ! grep -v 'process' ${TMP1} > ${TMP2} ${PROOT} ${COMMAND} > ${TMP4} ! grep -v 'process' ${TMP4} > ${TMP3} ! grep -v '^proot warning: ' ${TMP3} > ${TMP4} cmp ${TMP2} ${TMP4} rm -f ${TMP1} ${TMP2} ${TMP3} ${TMP4} ${TMP5} care-2.2.1/tests/test-25069c13.c.todo0000644000175000017500000000036612430615044016231 0ustar ivoireivoire#include /* execve(2), */ #include /* exit(3), */ #include /* strcmp(3), */ int main(int argc, char *argv[]) { if (argc == 0) exit(EXIT_SUCCESS); execve("/proc/self/exe", NULL, NULL); exit(EXIT_FAILURE); } care-2.2.1/tests/test-5bed7142.sh0000644000175000017500000000051712430615044015614 0ustar ivoireivoireif [ ! -x ${ROOTFS}/bin/pwd ] || [ -z `which mkdir` ] || [ -z `which grep` ] || [ -z `which mcookie` ] || [ -z `which pwd` ]; then exit 125; fi mkdir -p ${ROOTFS}/${PWD} ${PROOT} -v 1 -w . -r ${ROOTFS} pwd | grep ^${PWD}$ TMP=/tmp/$(mcookie) mkdir ${TMP} ! ${PROOT} sh -c "cd ${TMP}; rmdir ${TMP}; $(which pwd) -P" [ $? -eq 0 ] care-2.2.1/tests/test-hhhhhhhh.sh0000644000175000017500000000112712430615044016234 0ustar ivoireivoireif [ ! -x ${ROOTFS}/bin/true ] || [ -h /bin/true ] || [ -h /bin ] || [ -z `which mcookie` ] || [ -z `which true` ] || [ -z `which mkdir` ] || [ -z `which ln` ] || [ -z `which rm` ]; then exit 125; fi TMP=/tmp/$(mcookie) mkdir -p ${ROOTFS}/${TMP} A=$(mcookie) B=$(mcookie) ! ln -s /bin/true -r ${ROOTFS}/${TMP}/${A} ! ln -s ${TMP}/${A} -r ${ROOTFS}/${TMP}/${B} if [ ! -e ${ROOTFS}/${TMP}/${A} ]; then exit 125; fi env PATH=${TMP} ${PROOT} -r ${ROOTFS} ${B} rm -f ${TMP}/${B} # just in case it also exists in the host env. ${PROOT} -r ${ROOTFS} /${TMP}/${B} rm -fr ${ROOTFS}/${TMP} care-2.2.1/tests/test-careauth.sh0000644000175000017500000000132612430615044016252 0ustar ivoireivoireif [ -z `which env` ] || [ -z `which rm` ] || [ -z `which mcookie` ] || [ -z `which true` ]; then exit 125; fi if [ ! -e $CARE ]; then exit 125; fi unset PROOT TMP=$(mcookie) cd /tmp env ICEAUTHORITY= ${CARE} -o ${TMP} true ${CARE} -x ./${TMP} ./${TMP}/re-execute.sh rm -fr ${TMP} env XAUTHORITY= ${CARE} -o ${TMP} true ${CARE} -x ./${TMP} ./${TMP}/re-execute.sh rm -fr ${TMP} ${CARE} -o ${TMP} -p ../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../ true ${CARE} -x ./${TMP} ./${TMP}/re-execute.sh rm -fr ${TMP} care-2.2.1/tests/test-305ae31d.c0000644000175000017500000000251112430615044015410 0ustar ivoireivoire#include #include #include #include #include #include #include int main() { int fd; int fd_dir; int fd_file; char path[64]; /* 64 > sizeof("/proc//fd/") + 2 * sizeof(#ULONG_MAX) */ int status; fd_dir = open("/bin", O_RDONLY); if (fd_dir < 0) exit(EXIT_FAILURE); fd_file = open("/bin/true", O_RDONLY); if (fd_file < 0) exit(EXIT_FAILURE); status = snprintf(path, sizeof(path), "/proc/%d/fd/%d/", getpid(), fd_dir); if (status < 0 || status >= sizeof(path)) exit(EXIT_FAILURE); fd = open(path, O_RDONLY); if (fd < 0) exit(EXIT_FAILURE); close(fd); status = snprintf(path, sizeof(path), "/proc/%d/fd/%d/..", getpid(), fd_dir); if (status < 0 || status >= sizeof(path)) exit(EXIT_FAILURE); fd = open(path, O_RDONLY); if (fd < 0) exit(EXIT_FAILURE); close(fd); status = snprintf(path, sizeof(path), "/proc/%d/fd/%d/..", getpid(), fd_file); if (status < 0 || status >= sizeof(path)) exit(EXIT_FAILURE); fd = open(path, O_RDONLY); if (fd >= 0 || errno != ENOTDIR) exit(EXIT_FAILURE); status = snprintf(path, sizeof(path), "/proc/%d/fd/999999/..", getpid()); if (status < 0 || status >= sizeof(path)) exit(EXIT_FAILURE); fd = open(path, O_RDONLY); if (fd >= 0 || errno != ENOENT) exit(EXIT_FAILURE); exit(EXIT_SUCCESS); } care-2.2.1/tests/test-6b5a254a.sh0000644000175000017500000000150112430615044015602 0ustar ivoireivoireif [ -z `which mcookie` ] || [ -z `which echo` ] || [ -z `which touch` ] || [ -z `which rm` ]; then exit 125; fi FOO1=/tmp/$(mcookie) FOO2=/tmp/$(mcookie) FOO3=/tmp/$(mcookie) FOO4=/tmp/$(mcookie) echo "content of FOO1" > ${FOO1} echo "content of FOO2" > ${FOO2} ln -s ${FOO1} ${FOO3} # FOO3 -> FOO1 ln -s ${FOO2} ${FOO4} # FOO4 -> FOO2 ${PROOT} -b ${FOO3}:${FOO4} cat ${FOO2} | grep '^content of FOO1$' ${PROOT} -b ${FOO4}:${FOO3} cat ${FOO1} | grep '^content of FOO2$' ${PROOT} -b ${FOO3}:${FOO4}! cat ${FOO2} | grep '^content of FOO2$' ${PROOT} -b ${FOO4}:${FOO3}! cat ${FOO1} | grep '^content of FOO1$' ${PROOT} -v -1 -b ${FOO1} -b ${FOO3} cat ${FOO1} | grep '^content of FOO1$' ${PROOT} -v -1 -b ${FOO1} -b ${FOO2}:/tmp/../${FOO1} cat ${FOO1} | grep '^content of FOO2$' rm -f ${FOO1} ${FOO2} ${FOO3} care-2.2.1/tests/test-16573e73.c0000644000175000017500000000101112430615044015261 0ustar ivoireivoire#include #include #include #include int main(int argc, char **ignored) { char *const argv[] = { "true", NULL}; char *const envp[] = { NULL }; pid_t pid; int status; pid = (argc <= 1 ? vfork() : fork()); switch (pid) { case -1: exit(EXIT_FAILURE); case 0: /* child */ exit(execve("/bin/true", argv, envp)); default: /* parent */ if (wait(&status) < 0 || !WIFEXITED(status)) exit(EXIT_FAILURE); exit(WEXITSTATUS(status)); } exit(EXIT_FAILURE); } care-2.2.1/tests/test-8e5fa256.sh0000644000175000017500000000443212430615044015624 0ustar ivoireivoireif [ ! -x ${ROOTFS}/bin/readlink ] || [ ! -x ${ROOTFS}/bin/symlink ] || [ -z `which mcookie` ] || [ -z `which rm` ] || [ -z `which ln` ] || [ -z `which mkdir` ]; then exit 125; fi LINK_NAME1=`mcookie` LINK_NAME2=`mcookie` rm -f /tmp/${LINK_NAME1} rm -f /tmp/${LINK_NAME2} mkdir -p ${ROOTFS}/tmp ln -s /tmp/ced-host /tmp/${LINK_NAME1} ln -s /tmp/ced-guest ${ROOTFS}/tmp/${LINK_NAME1} ${PROOT} -r ${ROOTFS} readlink /tmp/${LINK_NAME1} | grep ^/tmp/ced-guest$ ${PROOT} -b /tmp -r ${ROOTFS} readlink /tmp/${LINK_NAME1} | grep ^/tmp/ced-host$ ${PROOT} -b /tmp:/foo -r ${ROOTFS} readlink /foo/${LINK_NAME1} | grep ^/foo/ced-host$ ${PROOT} -b /tmp:/foo -r ${ROOTFS} readlink /tmp/${LINK_NAME1} | grep ^/tmp/ced-guest$ ${PROOT} -b /:/host-rootfs -r ${ROOTFS} readlink /tmp/${LINK_NAME1} | grep ^/tmp/ced-guest$ ${PROOT} -b /:/host-rootfs -b /tmp:/foo -r ${ROOTFS} readlink /tmp/${LINK_NAME1} | grep ^/tmp/ced-guest$ # Always use the deepest binding, deepest from the host point-of-view. ${PROOT} -b /:/host-rootfs -r ${ROOTFS} readlink /tmp/${LINK_NAME1} | grep ^/tmp/ced-guest$ ${PROOT} -b /:/host-rootfs -b /tmp -r ${ROOTFS} readlink /tmp/${LINK_NAME1} | grep ^/tmp/ced-host$ ${PROOT} -b /:/host-rootfs -b /tmp:/foo -r ${ROOTFS} readlink /foo/${LINK_NAME1} | grep ^/foo/ced-host$ ${PROOT} -b /:/host-rootfs -b /tmp -r ${ROOTFS} readlink /host-rootfs/tmp/${LINK_NAME1} | grep ^/tmp/ced-host$ ${PROOT} -b /:/host-rootfs -b /tmp:/foo -r ${ROOTFS} readlink /host-rootfs/tmp/${LINK_NAME1} | grep ^/foo/ced-host$ rm /tmp/${LINK_NAME1} rm ${ROOTFS}/tmp/${LINK_NAME1} ${PROOT} -b /:/host-rootfs -b /tmp -w /bin -r ${ROOTFS} symlink /bin/bar /bin/${LINK_NAME1} ${PROOT} -b /:/host-rootfs -b /tmp -w /bin -r ${ROOTFS} readlink ${LINK_NAME1} | grep ^/bin/bar$ rm ${ROOTFS}/bin/${LINK_NAME1} ${PROOT} -b /:/host-rootfs -b /tmp -w /tmp -r ${ROOTFS} symlink /bin/bar /tmp/${LINK_NAME1} ${PROOT} -b /:/host-rootfs -b /tmp -w /tmp -r ${ROOTFS} readlink ${LINK_NAME1} | grep ^/bin/bar$ ${PROOT} -b /:/host-rootfs -b /tmp:/foo -w /foo -r ${ROOTFS} symlink /foo/bar /foo/${LINK_NAME2} ${PROOT} -b /:/host-rootfs -b /tmp:/foo -w /foo -r ${ROOTFS} readlink ${LINK_NAME2} | grep ^/foo/bar$ ${PROOT} -b /:/host-rootfs -b /tmp -w /host-rootfs/tmp -r ${ROOTFS} readlink ${LINK_NAME2} | grep ^/foo/bar$ rm /tmp/${LINK_NAME2} rm /tmp/${LINK_NAME1} care-2.2.1/tests/test-82ba4ba1.sh0000644000175000017500000000254612430615044015667 0ustar ivoireivoireif [ ! -x /bin/true ] || [ -z `which id` ] || [ -z `which grep` ] || [ -z `which env` ] || [ -z `which chown` ] || [ -z `which chroot` ] || [ ! /bin/true ] ; then exit 125; fi if [ `id -u` -eq 0 ]; then exit 125; fi ${PROOT} -i 123:456 id -u | grep ^123$ ${PROOT} -i 123:456 id -g | grep ^456$ ${PROOT} -i 123:456 env LD_SHOW_AUXV=1 /bin/true | grep '^AT_UID:[[:space:]]*123$' ${PROOT} -i 123:456 env LD_SHOW_AUXV=1 /bin/true | grep '^AT_EUID:[[:space:]]*123$' ${PROOT} -i 123:456 env LD_SHOW_AUXV=1 /bin/true | grep '^AT_GID:[[:space:]]*456$' ${PROOT} -i 123:456 env LD_SHOW_AUXV=1 /bin/true | grep '^AT_EGID:[[:space:]]*456$' ! ${PROOT} -i 123:456 chown root.root /root [ $? -eq 0 ] ! chroot / /bin/true EXPECTED=$? ! ${PROOT} -i 123:456 chroot / /bin/true [ $? -eq ${EXPECTED} ] ! ${PROOT} -i 123:456 chroot /tmp/.. /bin/true [ $? -eq ${EXPECTED} ] ! ${PROOT} -i 123:456 chroot /tmp /bin/true [ $? -eq 0 ] ${PROOT} -0 id -u | grep ^0$ ${PROOT} -0 id -g | grep ^0$ ${PROOT} -0 chown root.root /root ${PROOT} -0 chroot / /bin/true ${PROOT} -0 chroot /tmp/.. /bin/true ${PROOT} -0 env LD_SHOW_AUXV=1 /bin/true | grep '^AT_UID:[[:space:]]*0$' ${PROOT} -0 env LD_SHOW_AUXV=1 /bin/true | grep '^AT_EUID:[[:space:]]*0$' ${PROOT} -0 env LD_SHOW_AUXV=1 /bin/true | grep '^AT_GID:[[:space:]]*0$' ${PROOT} -0 env LD_SHOW_AUXV=1 /bin/true | grep '^AT_EGID:[[:space:]]*0$' care-2.2.1/tests/test-071599da.sh0000644000175000017500000000114312430615044015536 0ustar ivoireivoireif [ -z `which uname` ] || [ -z `which true` ] || [ -z `which env` ] || [ -z `which grep` ] || [ -z `which tail` ] || [ ! -x ${ROOTFS}/bin/true ]; then exit 125; fi ${PROOT} -k $(uname -r) true env PROOT_FORCE_KOMPAT=1 ${PROOT} -k $(uname -r) true ${PROOT} -k $(uname -r) ${ROOTFS}/bin/true env PROOT_FORCE_KOMPAT=1 ${PROOT} -k $(uname -r) ${ROOTFS}/bin/true if env LD_SHOW_AUXV=1 true | grep -q ^AT_RANDOM; then env PROOT_FORCE_KOMPAT=1 ${PROOT} -k $(uname -r) env LD_SHOW_AUXV=1 true | tail -1 | grep ^AT_RANDOM fi ! ${PROOT} -k $(uname -r) env LD_SHOW_AUXV=1 true | grep AT_SYSINFO [ $? -eq 0 ] care-2.2.1/tests/test-22222222.sh0000644000175000017500000000122212430615044015350 0ustar ivoireivoireif [ ! -x ${ROOTFS}/bin/readlink ] || [ -z `which mcookie` ] || [ -z `which touch` ] || [ -z `which mkdir` ] || [ -z `which ln` ] || [ -z `which grep` ] || [ -z `which rm` ]; then exit 125; fi REGULAR=`mcookie` SYMLINK=`mcookie` touch /tmp/${REGULAR} ln -fs /tmp/${REGULAR} /tmp/${SYMLINK} mkdir -p ${ROOTFS}/tmp touch ${ROOTFS}/tmp/${REGULAR} ln -fs /tmp/${REGULAR} ${ROOTFS}/tmp/${SYMLINK} ${PROOT} -b /tmp:/ced -r ${ROOTFS} /bin/readlink /tmp/${SYMLINK} | grep ^/tmp/${REGULAR}$ ${PROOT} -b /tmp:/ced -r ${ROOTFS} /bin/readlink /ced/${SYMLINK} | grep ^/ced/${REGULAR}$ rm -f /tmp/${REGULAR} rm -f /tmp/${SYMLINK} rm -f ${ROOTFS}/tmp/${REGULAR} care-2.2.1/tests/test-b161bc0a.sh0000644000175000017500000000017612430615044015657 0ustar ivoireivoireif [ -z `which pwd` ] || [ -z `which grep` ]; then exit 125; fi ${PROOT} -w /tmp/a -m /etc:/tmp/a pwd | grep '^/tmp/a$' care-2.2.1/tests/test-7601199b.sh0000644000175000017500000000017312430615044015457 0ustar ivoireivoireif [ ! -x /bin/sh ] || [ -z `which grep` ]; then exit 125; fi ${PROOT} -w /tmp /bin/sh -c 'echo $PWD' | grep '^/tmp$' care-2.2.1/tests/test-44444444.c0000644000175000017500000000053112430615044015202 0ustar ivoireivoire#include #include #include #include int main(void) { char buffer[2 * PATH_MAX]; if (!getcwd(buffer, sizeof(buffer))) { perror("getcwd"); exit(EXIT_FAILURE); } if (readlink("/bin/abs-true", buffer, sizeof(buffer)) < 0) { perror("readlink"); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } care-2.2.1/tests/cat.c0000644000175000017500000000112412430615044014054 0ustar ivoireivoire#include #include #include #include #include #include #include int main(int argc, char *argv[]) { int status; int fd; int i; for (i = 1; i < argc; i++) { char buffer[1024]; fd = open(argv[i], O_RDONLY); if (fd < 0) { perror("open(2)"); exit(EXIT_FAILURE); } while ((status = read(fd, buffer, sizeof(buffer))) > 0 && write(1, buffer, status) == status) errno = 0; if (errno != 0) { perror("read(2)/write(2)"); exit(EXIT_FAILURE); } (void) close(fd); } exit(EXIT_SUCCESS); } care-2.2.1/tests/chdir_getcwd.c0000644000175000017500000000107312430615044015736 0ustar ivoireivoire#define _GNU_SOURCE #include #include #include #include #include #include #include int main(int argc, char *argv[]) { char path[PATH_MAX]; int status; int fd; if (argc < 2) { fprintf(stderr, "missing argument\n"); exit(EXIT_FAILURE); } status = chdir(argv[1]); if (status < 0) { perror("chdir()"); exit(EXIT_FAILURE); } if (getcwd(path, PATH_MAX) == NULL) { perror("getcwd()"); exit(EXIT_FAILURE); } printf("%s\n", get_current_dir_name()); exit(EXIT_SUCCESS); } care-2.2.1/tests/test-55fd1da5.sh0000644000175000017500000000011312430615044015665 0ustar ivoireivoireif [ -z `which ls` ]; then exit 125; fi ${PROOT} -b /etc:/x ls -la /x care-2.2.1/tests/test-53355a5b.sh0000644000175000017500000000036212430615044015531 0ustar ivoireivoireif [ -z `which mcookie` ] || [ -z `which mkdir` ] || [ -z `which chmod` ] || [ -z `which rm` ]; then exit 125; fi TMP=/tmp/$(mcookie) mkdir ${TMP} chmod a-x ${TMP} ! ${PROOT} sh -c "cd $TMP" [ $? -eq 0 ] chmod a+x ${TMP} rm -fr ${TMP} care-2.2.1/tests/test-a8e69d6f.c0000644000175000017500000000076712430615044015532 0ustar ivoireivoire#include /* syscall(2), */ #include /* perror(3), fprintf(3), */ #include /* exit(3), */ #include /* SYS_lstat, */ #include /* struct stat, */ int main(void) { struct stat stat; int status; status = syscall(SYS_lstat, "/proc/self/cwd/", &stat); if (status < 0) { perror("lstat()"); exit(EXIT_FAILURE); } if (S_ISLNK(stat.st_mode)) { fprintf(stderr, "trailing '/' ignored\n"); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } care-2.2.1/tests/test-carequot.sh0000644000175000017500000000051712430615044016302 0ustar ivoireivoireif [ -z `which env` ] || [ -z `which rm` ] || [ -z `which mcookie` ] || [ -z `which true` ]; then exit 125; fi if [ ! -e $CARE ]; then exit 125; fi unset PROOT TMP=/tmp/$(mcookie) env 'COMP_WORDBREAKS= "'\''><;|&(:' ${CARE} -o ${TMP}.raw true cd /tmp; ${CARE} -x ${TMP}.raw ${TMP}/re-execute.sh rm -fr ${TMP}.raw ${TMP} care-2.2.1/tests/test-1cd9d8f9.sh0000644000175000017500000000015412430615044015707 0ustar ivoireivoireif ! `which pwd` -P || [ -z `which grep` ] ; then exit 125; fi ${PROOT} -w /tmp pwd -P | grep '^/tmp$' care-2.2.1/tests/test-mmmmmmmm.sh0000644000175000017500000000041412430615044016302 0ustar ivoireivoireif [ -z `which mcookie` ] || [ -z `which rmdir` ] || [ -z `which mkdir` ]; then exit 125; fi TMP=$(mcookie) cd /tmp ${PROOT} mkdir ./${TMP} ${PROOT} rmdir ./${TMP} ${PROOT} mkdir ${TMP}/ ${PROOT} rmdir ${TMP}/ ${PROOT} mkdir ./${TMP}/ ${PROOT} rmdir ./${TMP}/ care-2.2.1/tests/test-03969e70.sh0000644000175000017500000000173012430615044015463 0ustar ivoireivoireif [ ! -x ${ROOTFS}/bin/true ] || [ -z `which env` ]; then exit 125; fi ! ${PROOT} -r ${ROOTFS} /true [ $? -eq 0 ] ! ${PROOT} -r ${ROOTFS} ./true [ $? -eq 0 ] ! env PATH='' ${PROOT} -r ${ROOTFS} true [ $? -eq 0 ] ! env PATH='' ${PROOT} -r ${ROOTFS} -w /bin true [ $? -eq 0 ] env PATH='' ${PROOT} -r ${ROOTFS} -w /bin ./true env PATH='' ${PROOT} -r ${ROOTFS} -w / bin/true env PATH='' ${PROOT} -r ${ROOTFS} -w / bin/./true env PATH='' ${PROOT} -r ${ROOTFS} -w / ../bin/true ! env PATH='' ${PROOT} -r ${ROOTFS} -w /bin/true ../true [ $? -eq 0 ] ! env --unset PATH ${PROOT} -r ${ROOTFS} true [ $? -eq 0 ] ! env --unset PATH ${PROOT} -r ${ROOTFS} -w /bin true [ $? -eq 0 ] env --unset PATH ${PROOT} -r ${ROOTFS} -w /bin ./true env --unset PATH ${PROOT} -r ${ROOTFS} -w / /bin/true env --unset PATH ${PROOT} -r ${ROOTFS} -w / /bin/./true env --unset PATH ${PROOT} -r ${ROOTFS} -w / ../bin/true ! env --unset PATH ${PROOT} -r ${ROOTFS} -w /bin/true ../true [ $? -eq 0 ] care-2.2.1/tests/test-f7089d4f.sh0000644000175000017500000000022712430615044015630 0ustar ivoireivoireif [ -z `which timeout` ] || [ -z `which msgmerge` ] || [ ! -e /dev/null ]; then exit 125; fi timeout 5s ${PROOT} msgmerge -q /dev/null /dev/null care-2.2.1/tests/test-230f47cg.sh0000644000175000017500000000176412430615044015623 0ustar ivoireivoireif [ ! -x ${ROOTFS}/bin/cat ] || [ -z `which mcookie` ] || [ -z `which echo` ] || [ -z `which cp` ] || [ -z `which grep` ]|| [ -z `which rm` ]; then exit 125; fi ! ${PROOT} ${PROOT_RAW} /bin/true if [ $? -eq 0 ]; then exit 125; fi FOO1=/tmp/$(mcookie) FOO2=/tmp/$(mcookie) ROOTFS2=/$(mcookie) FOO3=/tmp/$(mcookie) mkdir -p ${ROOTFS}/tmp mkdir -p ${ROOTFS}/${ROOTFS2}/bin cp ${ROOTFS}/bin/cat ${ROOTFS}/${ROOTFS2}/bin/cat echo "content of foo1" > ${FOO1} echo "content of foo2" > ${FOO2} echo "content of foo3" > ${ROOTFS}/${FOO3} CMD="${PROOT} -r ${ROOTFS} \ -b ${FOO2} \ -b ${FOO1}:${ROOTFS2}/${FOO1} \ -b ${FOO2}:${ROOTFS2}/${FOO2} \ -b ${PROOT_RAW} \ ${PROOT_RAW} -r ${ROOTFS2} \ -b /:/host-rootfs \ -b ${FOO3}:${FOO2} \ -v -1" ${CMD} cat /${FOO1} | grep '^content of foo1$' ${CMD} cat /host-rootfs/${FOO2} | grep '^content of foo2$' ${CMD} cat /${FOO2} | grep '^content of foo3$' rm -fr ${FOO1} rm -fr ${FOO2} rm -fr ${ROOTFS2} rm -fr ${ROOTFS}/${FOO3} care-2.2.1/tests/test-1ffc8309.sh0000644000175000017500000000035612430615044015623 0ustar ivoireivoireif [ -z `which mcookie` ] || [ -z `which mkdir` ] || [ -z `which env` ] || [ -z `which uname` ] || [ -z `which rm` ]; then exit 125; fi TMP=/tmp/$(mcookie) mkdir ${TMP} env PROOT_FORCE_KOMPAT=1 ${PROOT} -k $(uname -r) rm -r ${TMP} care-2.2.1/tests/test-25069c12.c.todo0000644000175000017500000000063612430615044016230 0ustar ivoireivoire/* Note: loop infinitely when -r is specified to PRoot (use of ELF * interpreter), because argc is never equal to 0. */ #include /* execve(2), */ #include /* exit(3), */ #include /* strcmp(3), */ int main(int argc, char *argv[]) { char *void_array[] = { NULL }; if (argc == 0) exit(EXIT_SUCCESS); execve("/proc/self/exe", void_array, void_array); exit(EXIT_FAILURE); } care-2.2.1/tests/pwd.c0000644000175000017500000000061312430615044014101 0ustar ivoireivoire#include /* syscall(2), */ #include /* perror(3), */ #include /* PATH_MAX, */ #include /* exit(3), */ #include /* SYS_getcwd, */ int main(void) { char path[PATH_MAX]; int status; status = syscall(SYS_getcwd, path, PATH_MAX); if (status < 0) { perror("getcwd()"); exit(EXIT_FAILURE); } puts(path); exit(EXIT_SUCCESS); } care-2.2.1/tests/test-5467b986.sh0000644000175000017500000000352612430615044015500 0ustar ivoireivoireif [ -z `which mcookie` ] || [ -z `which grep` ] || [ ! -x ${ROOTFS}/bin/readlink ] || [ ! -x ${ROOTFS}/bin/chdir_getcwd ] || [ ! -x ${ROOTFS}/bin/fchdir_getcwd ]; then exit 125; fi DOES_NOT_EXIST=/$(mcookie) ${PROOT} -v -1 -b /proc -w ${DOES_NOT_EXIST} -r ${ROOTFS} readlink /proc/self/cwd | grep '^/$' ${PROOT} -v -1 -w /a -b /tmp:/a -b /tmp:/b -r ${ROOTFS} pwd | grep '^/a$' ${PROOT} -v -1 -w /a -b /tmp:/b -b /tmp:/a -r ${ROOTFS} pwd | grep '^/a$' ${PROOT} -v -1 -w /b -b /tmp:/a -b /tmp:/b -r ${ROOTFS} pwd | grep '^/b$' ${PROOT} -v -1 -w /b -b /tmp:/b -b /tmp:/a -r ${ROOTFS} pwd | grep '^/b$' ${PROOT} -v -1 -b /tmp:/a -b /tmp:/b -r ${ROOTFS} chdir_getcwd /a | grep '^/[ab]$' ${PROOT} -v -1 -b /tmp:/b -b /tmp:/a -r ${ROOTFS} chdir_getcwd /a | grep '^/[ab]$' ${PROOT} -v -1 -b /tmp:/a -b /tmp:/b -r ${ROOTFS} chdir_getcwd /b | grep '^/[ab]$' ${PROOT} -v -1 -b /tmp:/b -b /tmp:/a -r ${ROOTFS} chdir_getcwd /b | grep '^/[ab]$' ${PROOT} -v -1 -b /tmp:/a -b /tmp:/b -r ${ROOTFS} fchdir_getcwd /a | grep '^/[ab]$' ${PROOT} -v -1 -b /tmp:/b -b /tmp:/a -r ${ROOTFS} fchdir_getcwd /a | grep '^/[ab]$' ${PROOT} -v -1 -b /tmp:/a -b /tmp:/b -r ${ROOTFS} fchdir_getcwd /b | grep '^/[ab]$' ${PROOT} -v -1 -b /tmp:/b -b /tmp:/a -r ${ROOTFS} fchdir_getcwd /b | grep '^/[ab]$' ! ${PROOT} -r ${ROOTFS} chdir_getcwd /bin/true [ $? -eq 0 ] ! ${PROOT} -r ${ROOTFS} fchdir_getcwd /bin/true [ $? -eq 0 ] ! ${PROOT} -w /bin -r ${ROOTFS} chdir_getcwd true [ $? -eq 0 ] ! ${PROOT} -w /bin -r ${ROOTFS} fchdir_getcwd true [ $? -eq 0 ] ${PROOT} -v -1 -w /usr -r / ${ROOTFS}/bin/chdir_getcwd share | grep '^/usr/share$' ${PROOT} -v -1 -w /usr -r / ${ROOTFS}/bin/fchdir_getcwd share | grep '^/usr/share$' (cd /; ${PROOT} -v -1 -w usr -r / ${ROOTFS}/bin/chdir_getcwd share | grep '^/usr/share$') (cd /; ${PROOT} -v -1 -w usr -r / ${ROOTFS}/bin/fchdir_getcwd share | grep '^/usr/share$') care-2.2.1/tests/test-654decce.sh0000644000175000017500000000717112430615044015764 0ustar ivoireivoireif [ ! -x ${ROOTFS}/bin/readdir ] || [ ! -x ${ROOTFS}/bin/cat ] || [ -z `which mcookie` ] || [ -z `which mkdir` ] || [ -z `which chmod` ] || [ -z `which grep` ] || [ -z `which rm` ] || [ -z `which id` ]; then exit 125; fi if [ `id -u` -eq 0 ]; then exit 125; fi TMP1=/tmp/$(mcookie) TMP2=/tmp/$(mcookie) TMP3=$(mcookie) TMP4=$(mcookie) echo "content of ${TMP1}" > ${TMP1} mkdir -p ${ROOTFS}/${TMP2} chmod -rw ${ROOTFS}/${TMP2} export LANG=C ! ${PROOT} -v -1 -r ${ROOTFS} -b ${TMP1}:${TMP2}/${TMP3}/${TMP4} readdir ${TMP2} | grep '^opendir(3): Permission denied$' ${PROOT} -v -1 -r ${ROOTFS} -b ${TMP1}:${TMP2}/${TMP3}/${TMP4} readdir ${TMP2}/${TMP3} | grep "DT_REG ${TMP4}" ${PROOT} -v -1 -r ${ROOTFS} -b ${TMP1}:${TMP2}/${TMP3}/${TMP4} cat ${TMP2}/${TMP3}/${TMP4} | grep "^content of ${TMP1}$" ${PROOT} -v -1 -r ${ROOTFS} -b /tmp:${TMP2}/${TMP3}/${TMP4} readdir ${TMP2}/${TMP3} | grep "DT_DIR ${TMP4}" # TODO ${PROOT} -v -1 -r ${ROOTFS} -b /tmp:${TMP2}/${TMP3}/${TMP4} readdir /tmp | grep "DT_DIR ${TMP2}" # TODO ${PROOT} -v -1 -r ${ROOTFS} -b /tmp:/${TMP4} readdir / | grep "DT_REG ${TMP4}" ${PROOT} -v -1 -r ${ROOTFS} -b ${TMP1}:${TMP2}/${TMP3}/${TMP4} -b /etc/fstab:${TMP2}/${TMP3}/motd readdir ${TMP2}/${TMP3} | grep "DT_REG ${TMP4}" ${PROOT} -v -1 -r ${ROOTFS} -b ${TMP1}:${TMP2}/${TMP3}/${TMP4} -b /etc/fstab:${TMP2}/${TMP3}/motd readdir ${TMP2}/${TMP3} | grep "DT_REG motd" ${PROOT} -v -1 -r ${ROOTFS} -b ${TMP1}:${TMP2}/${TMP3}/${TMP4}/motd -b /etc/fstab:${TMP2}/${TMP3}/motd cat ${TMP2}/${TMP3}/${TMP4}/motd | grep "^content of ${TMP1}$" ${PROOT} -v -1 -r ${ROOTFS} -b /etc/fstab:${TMP2}/${TMP3}/motd -b ${TMP1}:${TMP2}/${TMP3}/${TMP4}/motd cat ${TMP2}/${TMP3}/${TMP4}/motd | grep "^content of ${TMP1}$" ! chmod +rw ${ROOTFS}/${TMP2} rm -fr ${ROOTFS}/${TMP2} mkdir -p ${TMP2} chmod -rw ${TMP2} export LANG=C ! ${PROOT} -v -1 -b ${TMP1}:${TMP2}/${TMP3}/${TMP4} ${ROOTFS}/bin/readdir ${TMP2} | grep '^opendir(3): Permission denied$' ${PROOT} -v -1 -b ${TMP1}:${TMP2}/${TMP3}/${TMP4} ${ROOTFS}/bin/readdir ${TMP2}/${TMP3} | grep "DT_REG ${TMP4}" ${PROOT} -v -1 -b ${TMP1}:${TMP2}/${TMP3}/${TMP4} ${ROOTFS}/bin/cat ${TMP2}/${TMP3}/${TMP4} | grep "^content of ${TMP1}$" ${PROOT} -v -1 -b /tmp:${TMP2}/${TMP3}/${TMP4} ${ROOTFS}/bin/readdir ${TMP2}/${TMP3} | grep "DT_DIR ${TMP4}" # TODO ${PROOT} -v -1 -b /tmp:${TMP2}/${TMP3}/${TMP4} readdir /tmp | grep "DT_DIR ${TMP2}" # TODO ${PROOT} -v -1 -b /tmp:/${TMP4} readdir / | grep "DT_REG ${TMP4}" ${PROOT} -v -1 -b ${TMP1}:${TMP2}/${TMP3}/${TMP4} -b /etc/fstab:${TMP2}/${TMP3}/motd ${ROOTFS}/bin/readdir ${TMP2}/${TMP3} | grep "DT_REG ${TMP4}" ${PROOT} -v -1 -b ${TMP1}:${TMP2}/${TMP3}/${TMP4} -b /etc/fstab:${TMP2}/${TMP3}/motd ${ROOTFS}/bin/readdir ${TMP2}/${TMP3} | grep "DT_REG motd" ${PROOT} -v -1 -b ${TMP1}:${TMP2}/${TMP3}/${TMP4}/motd -b /etc/fstab:${TMP2}/${TMP3}/motd ${ROOTFS}/bin/cat ${TMP2}/${TMP3}/${TMP4}/motd | grep "^content of ${TMP1}$" ${PROOT} -v -1 -b /etc/fstab:${TMP2}/${TMP3}/motd -b ${TMP1}:${TMP2}/${TMP3}/${TMP4}/motd ${ROOTFS}/bin/cat ${TMP2}/${TMP3}/${TMP4}/motd | grep "^content of ${TMP1}$" ${PROOT} -b /bin:/this1/does/not/exist -b /tmp:/this2/does/not/exist ${ROOTFS}/bin/readdir /this1/ ${PROOT} -b /bin:/this1/does/not/exist -b /tmp:/this2/does/not/exist ${ROOTFS}/bin/readdir /this2/ ${PROOT} -b /tmp:/this1/does/not/exist -b /bin:/this2/does/not/exist ${ROOTFS}/bin/readdir /this1/ ${PROOT} -b /tmp:/this1/does/not/exist -b /bin:/this2/does/not/exist ${ROOTFS}/bin/readdir /this2/ ! chmod +rw ${TMP1} ${TMP2} rm -fr ${TMP1} ${TMP2} care-2.2.1/tests/test-1c68c218.c0000644000175000017500000000070412430615044015344 0ustar ivoireivoire#include #include #include #include int main() { int status; char *path; path = tmpnam(NULL); status = symlink(path, path); if (status < 0) exit(EXIT_FAILURE); status = fchownat(AT_FDCWD, path, getuid(), getgid(), 0); if (status >= 0) exit(EXIT_FAILURE); status = fchownat(AT_FDCWD, path, getuid(), getgid(), AT_SYMLINK_NOFOLLOW); if (status < 0) exit(EXIT_FAILURE); exit(EXIT_SUCCESS); } care-2.2.1/tests/test-wwwwwwww.sh0000644000175000017500000000041012430615044016416 0ustar ivoireivoireif [ -z `which mcookie` ] || [ -z `which mkdir` ] || [ -z `which rm` ] || [ ! -x ${ROOTFS}/bin/pwd ]; then exit 125; fi TMP=/tmp/$(mcookie) mkdir ${TMP} cd ${TMP} ${PROOT} sh -c "cd ..; rm -r ${TMP}; mkdir ${TMP}; cd ${TMP}; ${ROOTFS}/bin/pwd" rm -fr ${TMP} care-2.2.1/tests/true.c0000644000175000017500000000003512430615044014264 0ustar ivoireivoireint main(void) { return 0; } care-2.2.1/tests/test-ptrace01.c0000644000175000017500000000162112430615044015703 0ustar ivoireivoire#include /* fork(2), */ #include /* perror(3), fprintf(3), */ #include /* exit(3), */ #include /* ptrace(2), */ #include /* waitpid(2), */ #include /* waitpid(2), */ int main(void) { int child_status, status; pid_t pid; pid = fork(); switch (pid) { case -1: perror("fork()"); exit(EXIT_FAILURE); case 0: /* child */ status = ptrace(PTRACE_TRACEME, 0, NULL, NULL); if (status < 0) { perror("ptrace(TRACEME)"); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); default: /* parent */ status = waitpid(pid, &child_status, 0); if (status < 0) { perror("waitpid()"); exit(EXIT_FAILURE); } if (!WIFEXITED(child_status) || WEXITSTATUS(child_status) != 0) { perror("unexpected child status\n"); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } /* Unreachable. */ exit(EXIT_FAILURE); } care-2.2.1/tests/validation.mk0000644000175000017500000000666212430615044015640 0ustar ivoireivoirelibuv-version = 0.10.27 coreutils-version = 8.21 perl-version = 5.18.1 ltp-version = 20140422 opt-version = 20140422 gdb-version = 7.6.1 proot-version = 3.2.2 libuv = libuv-$(libuv-version) coreutils = coreutils-$(coreutils-version) perl = perl-$(perl-version) ltp = ltp-$(ltp-version) opt = opt-$(opt-version) gdb = gdb-$(gdb-version) proot = PRoot-$(proot-version) testsuites = $(libuv) $(perl) $(ltp) $(opt) $(gdb) $(proot) # $(coreutils) needs PRoot's loader logs = $(testsuites:=.log) logs: $(logs) .PHONY: clean clean: rm -f $(logs) rm -fr $(testsuites) .PHONY: distclean distclean: clean rm -f $(testsuites:=.tar.*) ###################################################################### $(libuv).tar.gz: wget https://github.com/joyent/libuv/archive/v$(libuv-version).tar.gz -O $@ $(libuv).log: $(libuv).tar.gz rm -fr $(libuv) tar -xf $< $(MAKE) -C $(libuv) ($(MAKE) -C $(libuv) test 2>&1 || true) | tee $@ ###################################################################### $(coreutils).tar.xz: wget http://ftp.gnu.org/gnu/coreutils/$(coreutils).tar.xz $(coreutils).log: $(coreutils).tar.xz rm -fr $(coreutils) tar -xf $< cd $(coreutils) && ./configure $(MAKE) -C $(coreutils) ($(MAKE) -C $(coreutils) check || true) | tee $@ ###################################################################### $(perl).tar.gz: wget http://www.cpan.org/src/5.0/$(perl).tar.gz $(perl).log: $(perl).tar.gz rm -fr $(perl) tar -xf $< cd $(perl) && ./configure.gnu $(MAKE) -C $(perl) ($(MAKE) -C $(perl) check || true) | tee $@ ###################################################################### $(ltp).tar.gz: wget https://github.com/linux-test-project/ltp/archive/$(ltp-version).tar.gz -O $@ $(ltp).log: $(ltp).tar.gz rm -fr $(ltp) tar -xf $< $(MAKE) -C $(ltp) autotools cd $(ltp) && ./configure --prefix=$(PWD)/$(ltp)/install $(MAKE) -C $(ltp) $(MAKE) -C $(ltp) install sed -i s/^msgctl10/#/ $(ltp)/install/runtest/syscalls # is too CPU intensive sed -i s/^msgctl11/#/ $(ltp)/install/runtest/syscalls # is too CPU intensive ($(ltp)/install/runltp -f syscalls || true) | tee $@ ###################################################################### $(opt).log: $(ltp).tar.gz rm -fr $(opt) mkdir $(opt) tar -C $(opt) -xf $< $(ltp)/testcases/open_posix_testsuite $(MAKE) -C $(opt)/$(ltp)/testcases/open_posix_testsuite -j 1 # has broken // build ($(MAKE) -C $(opt)/$(ltp)/testcases/open_posix_testsuite -j 1 test || true) | tee $@ ###################################################################### $(gdb).tar.gz: wget http://ftp.gnu.org/gnu/gdb/$(gdb).tar.gz $(gdb).log: $(gdb).tar.gz rm -fr $(gdb) tar -xf $< cd $(gdb) && ./configure $(MAKE) -C $(gdb) rm -f $(gdb)/gdb/testsuite/gdb.base/foll-fork.exp # makes PRoot stall rm -f $(gdb)/gdb/testsuite/gdb.base/foll-vfork.exp # makes PRoot stall rm -f $(gdb)/gdb/testsuite/gdb.base/watch_thread_num.exp # makes PRoot stall rm -f $(gdb)/gdb/testsuite/gdb.base/attach-twice.exp # kills PRoot explicitly ($(MAKE) -C $(gdb)/gdb/testsuite check-gdb.base1 check-gdb.base2 check-gdb.server || true) | tee $@ ###################################################################### $(proot).tar.gz: wget https://github.com/cedric-vincent/proot/archive/v$(proot-version).tar.gz -O $@ $(proot).log: $(proot).tar.gz rm -fr $(proot) tar -xf $< $(MAKE) -C $(proot)/src ($(MAKE) -C $(proot)/tests || true) | tee $@ care-2.2.1/tests/readdir.c0000644000175000017500000000171612430615044014726 0ustar ivoireivoire#include #include #include #include #include int main(int argc, char *argv[]) { struct dirent *dirents; DIR *dir; int status; int i; for (i = 1; i < argc; i++) { dir = opendir(argv[i]); if (dir == NULL) { perror("opendir(3)"); exit(EXIT_FAILURE); } errno = 0; while ((dirents = readdir(dir)) != NULL) { printf("%s %s\n", dirents->d_type == DT_BLK ? "DT_BLK " : dirents->d_type == DT_CHR ? "DT_CHR " : dirents->d_type == DT_DIR ? "DT_DIR " : dirents->d_type == DT_FIFO ? "DT_FIFO" : dirents->d_type == DT_LNK ? "DT_LNK " : dirents->d_type == DT_REG ? "DT_REG " : dirents->d_type == DT_SOCK ? "DT_SOCK" : "DT_UNKNOWN", dirents->d_name); errno = 0; } if (errno != 0) { perror("readdir(3)"); exit(EXIT_FAILURE); } status = closedir(dir); if (status < 0) { perror("closedir(3)"); exit(EXIT_FAILURE); } } exit(EXIT_SUCCESS); } care-2.2.1/tests/test-230f47ch.sh0000644000175000017500000000162612430615044015621 0ustar ivoireivoireif [ -z `which id` ] || [ -z `which uname` ] || [ -z `which grep` ]; then exit 125; fi ! ${PROOT} ${PROOT_RAW} /bin/true if [ $? -eq 0 ]; then exit 125; fi ${PROOT} ${PROOT_RAW} -0 id -u | grep ^0$ ${PROOT} ${PROOT_RAW} -i 123:456 id -u | grep ^123$ ${PROOT} ${PROOT_RAW} -k 3.33.333 uname -r | grep ^3\.33\.333$ ${PROOT} -0 ${PROOT_RAW} id -u | grep ^0$ ${PROOT} -i 123:456 ${PROOT_RAW} id -u | grep ^123$ ${PROOT} -k 3.33.333 ${PROOT_RAW} uname -r | grep ^3\.33\.333$ ${PROOT} -0 ${PROOT_RAW} -k 3.33.333 id -u | grep ^0$ ${PROOT} -0 ${PROOT_RAW} -k 3.33.333 uname -r | grep ^3\.33\.333$ ${PROOT} -k 3.33.333 ${PROOT_RAW} -0 id -u | grep ^0$ ${PROOT} -k 3.33.333 ${PROOT_RAW} -0 uname -r | grep ^3\.33\.333$ ${PROOT} -i 123:456 ${PROOT_RAW} -k 3.33.333 id -u | grep ^123$ ${PROOT} -k 3.33.333 ${PROOT_RAW} -i 123:456 id -u | grep ^123$ care-2.2.1/tests/test-82ba4ba1.c0000644000175000017500000000313612430615044015473 0ustar ivoireivoire#define _GNU_SOURCE #include #include #include int main(void) { uid_t ruid = 13, euid = 13, suid = 13; gid_t rgid = 13, egid = 13, sgid = 13; int status; status = getresuid(&ruid, &euid, &suid); if (status != 0 || ruid != 0 || euid != 0 || suid != 0) { perror("getresuid"); fprintf(stderr, "%ld %ld %ld\n", (unsigned long) ruid, (unsigned long) euid, (unsigned long) suid); exit(EXIT_FAILURE); } status = getresgid(&rgid, &egid, &sgid); if (status != 0 || rgid != 0 || egid != 0 || sgid != 0) { perror("getresgid"); fprintf(stderr, "%ld %ld %ld\n", (unsigned long) ruid, (unsigned long) euid, (unsigned long) suid); exit(EXIT_FAILURE); } status = setresgid(1, 1, 1); if (status != 0) { perror("setresgid"); exit(EXIT_FAILURE); } status = getresgid(&rgid, &egid, &sgid); if (status != 0 || rgid != 1 || egid != 1 || sgid != 1) { perror("getresgid"); fprintf(stderr, "%ld %ld %ld\n", (unsigned long) rgid, (unsigned long) egid, (unsigned long) sgid); exit(EXIT_FAILURE); } if (status != 0 || rgid != 1 || egid != 1 || sgid != 1) { perror("getresgid"); fprintf(stderr, "%ld %ld %ld\n", (unsigned long) ruid, (unsigned long) euid, (unsigned long) suid); exit(EXIT_FAILURE); } status = setresuid(1, 1, 1); if (status != 0) { perror("setresuid"); exit(EXIT_FAILURE); } status = getresuid(&ruid, &euid, &suid); if (status != 0 || ruid != 1 || euid != 1 || suid != 1) { perror("getresuid"); fprintf(stderr, "%ld %ld %ld\n", (unsigned long) ruid, (unsigned long) euid, (unsigned long) suid); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } care-2.2.1/tests/test-rrrrrrrr.sh0000644000175000017500000000032712430615044016355 0ustar ivoireivoireif [ ! -x ${ROOTFS}/bin/readlink ] || [ -z `which realpath` ] || [ -z `which grep` ]; then exit 125; fi RESULT=$(realpath ${ROOTFS}) ${PROOT} -b /proc -r ${ROOTFS} readlink /proc/self/root | grep ^${RESULT}$ care-2.2.1/tests/test-11111111.sh0000644000175000017500000000267112430615044015351 0ustar ivoireivoire#!/bin/bash if [ -z `which cat` ] || [ -z `which readlink` ] || [ -z `which mcookie` ] || [ -z `which touch` ] || [ -z `which mkdir` ] || [ -z `which ln` ] || [ -z `which grep` ] || [ -z `which rm` ]; then exit 125; fi set +e x1="r1 d1 rl1 dl1" # root of the test tree. x2="r2 d2 rl2 dl2" # subtree of d1/dl1, every components exist. x3="r3 d3 rl3 dl3" # subtree of d1/dl1, no component exists. x4="/ /. /.." # terminators. generate () { output=${1} make_tests () { for c in ${x4} ""; do x="${1}${c}" $(cd ${x} 2>/dev/null); cd_result=$? cat ${x} 2>/dev/null; cat_result=$? readlink ${x} 2>/dev/null 1>&2; readlink_result=$? echo "${x}, $cd_result, $cat_result, $readlink_result" >> $output done } echo "path, chdir, cat, readlink" > $output for a in ${x1}; do for b in ${x2}; do make_tests "${a}/${b}" done for b in ${x3}; do make_tests "${a}/${b}" done done } if [ -z ${PROOT_STAGE2} ]; then create_components () { touch r${1} 2>/dev/null mkdir -p d${1} 2>/dev/null ln -fs r${1} rl${1} 2>/dev/null ln -fs d${1} dl${1} 2>/dev/null } create_components 1 $(cd d1; create_components 2) REF=/tmp/`mcookie` mkdir -p /tmp generate $REF env PROOT_STAGE2=$REF ${PROOT} -w ${PWD} sh ./$0 exit $? fi TMP=/tmp/`mcookie` mkdir -p /tmp generate $TMP set -e cmp $TMP $PROOT_STAGE2 rm $TMP $PROOT_STAGE2 care-2.2.1/tests/test-230f47cf.sh0000644000175000017500000000016612430615044015615 0ustar ivoireivoire! ${PROOT} ${PROOT_RAW} /bin/true if [ $? -eq 0 ]; then exit 125; fi echo exit | ${PROOT} -v 0 ${PROOT_RAW} -v 0 care-2.2.1/tests/test-cccccccc.sh0000644000175000017500000000035312430615044016164 0ustar ivoireivoireif [ -z `which mcookie` ] || [ -z `which rmdir` ] || [ -z `which mkdir` ]; then exit 125; fi TMP=/tmp/$(mcookie) mkdir ${TMP} ! ${PROOT} rmdir ${TMP}/. [ $? -eq 0 ] ! ${PROOT} rmdir ${TMP}/./ [ $? -eq 0 ] ${PROOT} rmdir ${TMP} care-2.2.1/tests/fork-wait.c0000644000175000017500000000102612430615044015211 0ustar ivoireivoire#include /* exit(3), */ #include /* fork(2), sleep(3), */ #include /* wait(2), */ #include /* wait(2), */ int main(int argc, char *argv[]) { int child_status; int status; switch (fork()) { case -1: exit(EXIT_FAILURE); case 0: /* Child */ argc > 1 && sleep(2); exit(EXIT_SUCCESS); default: /* Parent */ argc <= 1 && sleep(2); status = wait(&child_status); if (status < 0 || !WIFEXITED(child_status)) exit(EXIT_FAILURE); exit(WEXITSTATUS(child_status)); } } care-2.2.1/tests/test-a4d7ed70.sh0000644000175000017500000000063612430615044015700 0ustar ivoireivoireif [ -z `which mcookie` ] || [ -z `which mkdir` ] || [ -z `which ln` ] || [ -z `which ls` ] || [ -z `which rm` ] || [ -z `which cat` ]; then exit 125; fi TMP=/tmp/$(mcookie) TMP2=/tmp/$(mcookie) mkdir ${TMP} ln -s /proc/self/fd ${TMP}/fd ln -s ${TMP}/fd/0 ${TMP}/stdin ${PROOT} \ls ${TMP}/stdin | grep ^${TMP}/stdin$ echo OK > ${TMP2} ${PROOT} cat ${TMP}/stdin < ${TMP2} | grep ^OK$ rm -fr ${TMP} ${TMP2} care-2.2.1/tests/test-cea75343.sh0000644000175000017500000000173112430615044015614 0ustar ivoireivoireif [ -z `which mcookie` ] || [ -z `which mkdir` ] || [ -z `which cat` ] || [ -z `which grep` ] || [ -z `which rm` ]; then exit 125; fi TMP1=/tmp/`mcookie` TMP2=/tmp/`mcookie` TMP3=/tmp/`mcookie` # /a/b/c # /a/b # /a/d # /a echo 'binding 1' > ${TMP1} echo 'binding 2' > ${TMP2} mkdir -p ${TMP3}/a/b BINDINGS="-b ${TMP1}:${TMP3}/a/b/c -b ${TMP3}/a/b -b ${TMP2}:${TMP3}/a/d -b ${TMP3}/a" ${PROOT} ${BINDINGS} cat ${TMP3}/a/b/c | grep '^binding 1$' BINDINGS="-b ${TMP3}/a -b ${TMP2}:${TMP3}/a/d -b ${TMP3}/a/b -b ${TMP1}:${TMP3}/a/b/c" ${PROOT} ${BINDINGS} cat ${TMP3}/a/d | grep '^binding 2$' mkdir -p ${TMP3}/c/b # /c/b/a # /c/b # /c/d # /c BINDINGS="-b ${TMP1}:${TMP3}/c/b/a -b ${TMP3}/c/b -b ${TMP2}:${TMP3}/c/d -b ${TMP3}/c" ${PROOT} ${BINDINGS} cat ${TMP3}/c/b/a | grep '^binding 1$' BINDINGS="-b ${TMP3}/c -b ${TMP2}:${TMP3}/c/d -b ${TMP3}/c/b -b ${TMP1}:${TMP3}/c/b/a" ${PROOT} ${BINDINGS} cat ${TMP3}/c/d | grep '^binding 2$' rm ${TMP1} rm ${TMP2} rm -fr ${TMP3} care-2.2.1/tests/test-67972fbe.sh0000644000175000017500000000065312430615044015633 0ustar ivoireivoireif [ ! -x ${ROOTFS}/bin/readdir ] || [ ! -e /bin/true ] || [ -z `which mkdir` ] || [ -z `which ln` ] || [ -z `which rm` ] || [ -z `which grep` ] || [ -z `which mcookie` ]; then exit 125; fi TMP=/tmp/$(mcookie) mkdir -p ${ROOTFS}/${TMP}/run/dbus mkdir -p ${ROOTFS}/${TMP}/var ln -s ../run ${ROOTFS}/${TMP}/var/run ${PROOT} -b /bin:${TMP}/var/run/dbus -r ${ROOTFS} readdir ${TMP}/var/run/dbus/ | grep true rm -fr ${TMP} care-2.2.1/tests/test-d1be631a.sh0000644000175000017500000000033312430615044015661 0ustar ivoireivoireif [ -z `which mknod`] || [ `id -u` -eq 0 ]; then exit 125; fi TMP=/tmp/$(mcookie) ! ${PROOT} mknod ${TMP} b 1 1 [ $? -eq 0 ] ! ${PROOT} -i 123:456 mknod ${TMP} b 1 1 [ $? -eq 0 ] ${PROOT} -0 mknod ${TMP} b 1 1 care-2.2.1/tests/test-33333333.c0000644000175000017500000000065412430615044015200 0ustar ivoireivoire/* Check a child is traced even if its parent doesn't call wait(2). * * Reported-by: Clément BAZIN * on Ubuntu 11.10 x86_64 */ #include /* exit(3), */ #include /* fork(2), */ int main(void) { switch (fork()) { case -1: exit(EXIT_FAILURE); case 0: /* Child: XXX */ sleep(2); return 0; default: /* Parent: "look child, no wait(2)!" */ return 1; } } care-2.2.1/tests/test-cb1143ab.sh0000644000175000017500000000253612430615044015662 0ustar ivoireivoireif [ -z `which mcookie` ] || [ -z `which mkdir` ] || [ -z `which ln` ] || [ -z `which ls` ]; then exit 125; fi D1=`mcookie` D2=`mcookie` LINK=`mcookie` F=`mcookie` TMP=/tmp/${D1}/${D2} mkdir -p ${TMP} ln -s ${TMP}/./. ${TMP}/${LINK} ${PROOT} \ls ${TMP}/${LINK} | grep ^${LINK}$ ${PROOT} \ls ${TMP}/${LINK}/ | grep ^${LINK}$ ${PROOT} \ls ${TMP}/${LINK}/. | grep ^${LINK}$ ${PROOT} \ls ${TMP}/${LINK}/.. | grep ^${D2}$ ${PROOT} \ls ${TMP}/${LINK}/./.. | grep ^${D2}$ rm ${TMP}/${LINK} touch ${TMP}/${F} ln -s ${TMP}/${F} ${TMP}/${LINK} ${PROOT} \ls ${TMP}/${LINK} ! ${PROOT} \ls ${TMP}/${LINK}/ [ $? -eq 0 ] ! ${PROOT} \ls ${TMP}/${LINK}/. [ $? -eq 0 ] ! ${PROOT} \ls ${TMP}/${LINK}/.. [ $? -eq 0 ] ! ${PROOT} \ls ${TMP}/${LINK}/./.. [ $? -eq 0 ] ! ${PROOT} \ls ${TMP}/${LINK}/../.. [ $? -eq 0 ] ${PROOT} -b /tmp/${D1}:${TMP}/${F} \ls ${TMP}/${LINK} ${PROOT} -b /tmp/${D1}:${TMP}/${F} \ls ${TMP}/${LINK}/ ${PROOT} -b /tmp/${D1}:${TMP}/${F} \ls ${TMP}/${LINK}/. ${PROOT} -b /tmp/${D1}:${TMP}/${F} \ls ${TMP}/${LINK}/.. rm ${TMP}/${LINK} ln -s ${TMP}/${D1} ${TMP}/${LINK} ${PROOT} -b /tmp/${F}:${TMP}/${D1} \ls ${TMP}/${LINK} ! ${PROOT} -b /tmp/${F}:${TMP}/${D1} \ls ${TMP}/${LINK}/ [ $? -eq 0 ] ! ${PROOT} -b /tmp/${F}:${TMP}/${D1} \ls ${TMP}/${LINK}/. [ $? -eq 0 ] ! ${PROOT} -b /tmp/${F}:${TMP}/${D1} \ls ${TMP}/${LINK}/.. [ $? -eq 0 ] rm -fr /tmp/${D1} care-2.2.1/tests/argv0.c0000644000175000017500000000012312430615044014322 0ustar ivoireivoire#include int main(int argc, char **argv) { puts(argv[0]); return 0; } care-2.2.1/tests/readlink.c0000644000175000017500000000104612430615044015101 0ustar ivoireivoire#include /* syscall(2), */ #include /* perror(3), fprintf(3), */ #include /* PATH_MAX, */ #include /* exit(3), */ #include /* SYS_readlink, */ int main(int argc, char *argv[]) { char path[PATH_MAX]; int status; if (argc != 2) { fprintf(stderr, "usage: readlink FILE\n"); exit(EXIT_FAILURE); } status = syscall(SYS_readlink, argv[1], path, PATH_MAX); if (status < 0) { perror("readlink()"); exit(EXIT_FAILURE); } path[status] = '\0'; puts(path); exit(EXIT_SUCCESS); } care-2.2.1/tests/test-a3e68988.c0000644000175000017500000000427212430615044015370 0ustar ivoireivoire#include /* AT_*, */ #include /* printf(3), */ #include /* open(2), */ #include /* open(2), */ #include /* open(2), */ #include /* read(2), close(2), */ #include /* exit(3), EXIT_*, realloc(3), free(3), */ struct auxv { long type; long value; } __attribute__((packed)); void print_auxv(struct auxv *auxv) { #define CASE(a) \ case (a): \ printf("%s = 0x%lx\n", #a, auxv->value); \ break; switch (auxv->type) { CASE(AT_NULL) CASE(AT_IGNORE) CASE(AT_EXECFD) CASE(AT_PHDR) CASE(AT_PHENT) CASE(AT_PHNUM) CASE(AT_PAGESZ) CASE(AT_BASE) CASE(AT_FLAGS) CASE(AT_ENTRY) CASE(AT_NOTELF) CASE(AT_UID) CASE(AT_EUID) CASE(AT_GID) CASE(AT_EGID) CASE(AT_PLATFORM) CASE(AT_HWCAP) CASE(AT_CLKTCK) CASE(AT_SECURE) CASE(AT_BASE_PLATFORM) CASE(AT_RANDOM) #if defined(AT_HWCAP2) CASE(AT_HWCAP2) #endif CASE(AT_EXECFN) #if defined(AT_SYSINFO) CASE(AT_SYSINFO) #endif #if defined(AT_SYSINFO_EHDR) CASE(AT_SYSINFO_EHDR) #endif default: printf("unknown (%ld) = 0x%lx\n", auxv->type, auxv->value); break; } #undef CASE } extern char **environ; int main() { long at_base_proc = 0; long at_base_mem = 0; struct auxv *auxv; void *data = NULL; size_t size = 0; void **pointer; int status; int fd; for (pointer = (void **) environ; *pointer != NULL; pointer++) /* Nothing */; for (auxv = (void *) ++pointer; auxv->type != AT_NULL; auxv++) { if (auxv->type == AT_BASE) at_base_mem = auxv->value; print_auxv(auxv); } printf("----------------------------------------------------------------------\n"); fd = open("/proc/self/auxv", O_RDONLY); if (fd < 0) exit(EXIT_FAILURE); #define CHUNK_SIZE 1024 do { data = realloc(data, size + CHUNK_SIZE); if (data == NULL) exit(EXIT_FAILURE); status = read(fd, data + size, CHUNK_SIZE); size += CHUNK_SIZE; } while (status > 0); for (auxv = data; auxv->type != AT_NULL; auxv++) { if (auxv->type == AT_BASE) at_base_proc = auxv->value; print_auxv(auxv); } (void) close(fd); (void) free(data); exit((at_base_proc != 0 && at_base_mem == at_base_proc) ? EXIT_SUCCESS : EXIT_FAILURE); } care-2.2.1/tests/GNUmakefile0000644000175000017500000001137312430615044015222 0ustar ivoireivoireDIR = $(dir $(abspath $(firstword $(MAKEFILE_LIST)))) ROOTFS = $(DIR)/rootfs PROOT = $(DIR)/../src/proot CARE = $(DIR)/../src/care CC = gcc PROOT_RAW = $(PROOT) CHECK_TESTS = $(patsubst %,check-%, $(wildcard test-*.sh) $(wildcard test-*.c)) .PHONY: check clean_failure check_failure setup check-% check: | clean_failure check_failure memcheck: PROOT_RAW := $(PROOT) memcheck: PROOT := $(shell which valgrind) -q --error-exitcode=1 $(PROOT) memcheck: check clean_failure: @rm -f failure check_failure: $(CHECK_TESTS) @bash -c '! test -e failure' check-%.sh: %.sh setup $(Q)env CARE="$(CARE)" PROOT_RAW="$(PROOT_RAW)" PROOT="$(PROOT)" ROOTFS=$(ROOTFS) sh -ex $< $(silently); $(call check,$*) check-%.c: $(ROOTFS)/bin/% setup $(call check_c,$*,$(PROOT) -b /proc -r $(ROOTFS) /bin/$*) # Special cases. check-test-bdc90417.c: test-bdc90417 $(call check_c,$<,$(PROOT) -w . ./$<) check-test-af062114.c: test-af062114 $(call check_c,$<,$(PROOT) -v -1 -q /bin/true -b . -b /lib -b /lib64 -b /proc -r $(ROOTFS) ./$< | grep -- --inhibit-rpath) # $(call check_c,$<,$(PROOT) -v -1 -q /bin/true / ./$< | grep '^./$<') deprecated check-test-5bed7141.c: test-5bed7141 $(call check_c,$<,$(PROOT) ./$<) check-test-5bed7143.c: test-5bed7143 $(call check_c,$<,$(PROOT) -r $(ROOTFS) -b . ./$<) $(call check_c,$<,$(PROOT) ./$<) check-test-16573e73.c: test-16573e73 $(call check_c,$<,$(PROOT) ./$<) $(call check_c,$<,$(PROOT) ./$< 1) check-test-82ba4ba1.c: test-82ba4ba1 $(call check_c,$<,$(PROOT) -0 ./$<) $(call check_c,$<,! $(PROOT) -i 123:456 ./$<) check-test-kkkkkkkk.c: test-kkkkkkkk $(call check_c,$<,$(PROOT) ./$<) check-test-25069c12.c: test-25069c12 $(call check_c,$<,$(PROOT) ./$<) check-test-25069c13.c: test-25069c13 $(call check_c,$<,$(PROOT) ./$<) check-test-1ffc8309.c: test-1ffc8309 $(call check_c,$<,env PROOT_FORCE_KOMPAT=1 $(PROOT) -k $(shell uname -r) ./$<) check-test-c5a7a0f0.c: test-c5a7a0f0 $(call check_c,$<,$(PROOT) -0 ./$<) $(call check_c,$<,! $(PROOT) -i 123:456 ./$<) check-test-a3e68988.c: test-a3e68988 @which gdb >/dev/null 2>&1 || rm -f $< $(call check_c,$<,$(PROOT) gdb -return-child-result -ex run -ex quit ./$<) check-test-fdf487a0.c: test-fdf487a0 $(call check_c,$<,echo test | $(PROOT) ./$<) check-test-iiiiiiii.c: test-iiiiiiii $(call check_c,$<,echo test | env PROOT_DONT_POLLUTE_ROOTFS=1 $(PROOT) -b /bin:/this_shall_not_exist_outside_proot ./$<) check_c = $(Q)if [ -e $< ]; then \ $(2) $(silently); $(call check,$(1)) \ else \ echo " CHECK $(1) skipped"; \ fi check = case "$$?" in \ 0) echo " CHECK $(1) ok";; \ 125) echo " CHECK $(1) skipped";; \ *) echo " CHECK $(1) FAILED"; \ touch failure ;; \ esac ###################################################################### # Build a clean rootfs setup: $(ROOTFS)/bin/true $(ROOTFS)/bin/false \ $(ROOTFS)/bin/pwd $(ROOTFS)/bin/readlink $(ROOTFS)/bin/symlink \ $(ROOTFS)/bin/abs-true $(ROOTFS)/bin/rel-true $(ROOTFS)/bin/echo \ $(ROOTFS)/bin/argv0 $(ROOTFS)/bin/readdir $(ROOTFS)/bin/cat $(ROOTFS)/tmp \ $(ROOTFS)/bin/chdir_getcwd $(ROOTFS)/bin/fchdir_getcwd \ $(ROOTFS)/bin/fork-wait $(ROOTFS)/bin/ptrace $(ROOTFS)/bin/ptrace-2 $(ROOTFS)/tmp: @mkdir $@ $(ROOTFS)/bin/abs-true: @ln -fs /bin/true $@ $(ROOTFS)/bin/rel-true: @ln -fs ./true $@ .SECONDARY: $(patsubst %.c,$(ROOTFS)/bin/%, $(wildcard test-*.c)) $(ROOTFS)/bin/%: %.c @test -e $(dir $@) || mkdir -p $(dir $@) $(Q)$(CC) -static $*.c -o $@ $(silently) || true # Special cases. test-bdc90417: test-bdc90417.c $(Q)$(CC) $< -o $@ $(silently) || true test-af062114: test-af062114.c $(Q)$(CC) $< -Wl,-rpath=foo -o $@ $(silently) || true test-5bed7141: test-5bed7141.c $(Q)$(CC) $< -pthread -static -o $@ $(silently) || true test-16573e73: test-16573e73.c $(Q)$(CC) $< -static -o $@ $(silently) || true test-82ba4ba1: test-82ba4ba1.c $(Q)$(CC) $< -o $@ $(silently) || true test-kkkkkkkk: test-kkkkkkkk.c $(Q)$(CC) $< -o $@ $(silently) || true test-25069c12: test-25069c12.c $(Q)$(CC) $< -o $@ $(silently) || true test-25069c13: test-25069c13.c $(Q)$(CC) $< -o $@ $(silently) || true test-5bed7143: test-5bed7143.c $(Q)$(CC) $< -static -o $@ $(silently) || true test-1ffc8309: test-1ffc8309.c $(Q)$(CC) $< -o $@ $(silently) || true test-c5a7a0f0: test-c5a7a0f0.c $(Q)$(CC) $< -pthread -static -o $@ $(silently) || true test-a3e68988: test-a3e68988.c $(Q)$(CC) $< -o $@ $(silently) || true test-fdf487a0: test-fdf487a0.c $(Q)$(CC) $< -o $@ $(silently) || true test-iiiiiiii: test-iiiiiiii.c $(Q)$(CC) $< -o $@ $(silently) || true ###################################################################### # Beautified output V = 0 ifeq ($(V), 0) quiet = quiet_ Q = @ silently = >/dev/null 2>&1 else quiet = Q = silently = endif care-2.2.1/tests/test-careexit.sh0000644000175000017500000000055512430615044016265 0ustar ivoireivoireif [ -z `which cpio` ] || [ -z `which rm` ] || [ -z `which mcookie` ]; then exit 125; fi if [ ! -e $CARE ]; then exit 125; fi unset PROOT TMP=/tmp/$(mcookie) ${CARE} -o ${TMP}.cpio sh -c 'exit 0' cd /tmp cpio -idmuvF ${TMP}.cpio ${TMP}/re-execute.sh set +e ${TMP}/re-execute.sh sh -c 'exit 132' status=$? set -e [ $status -eq 132 ] rm -f ${TMP}.cpio care-2.2.1/tests/test-dfb0c3b6.sh0000644000175000017500000000147112430615044015750 0ustar ivoireivoireif [ -z `which sh` ] || [ -z `which readlink` ] || [ -z `which grep` ] || [ -z `which echo` ] || [ -z `which mcookie` ] || [ ! -e /proc/self/fd/0 ]; then exit 125; fi ${PROOT} readlink /proc/self | grep -E "^[[:digit:]]+$" ! ${PROOT} readlink /proc/self/.. [ $? -eq 0 ] ${PROOT} readlink /proc/self/../self | grep -E "^[[:digit:]]+$" ${PROOT} sh -c 'echo "OK" | readlink /proc/self/fd/0' | grep -E "^pipe:\[[[:digit:]]+\]$" ! ${PROOT} sh -c 'echo "OK" | readlink /proc/self/fd/0/' [ $? -eq 0 ] ! ${PROOT} sh -c 'echo "OK" | readlink /proc/self/fd/0/..' [ $? -eq 0 ] ! ${PROOT} sh -c 'echo "OK" | readlink /proc/self/fd/0/../0' [ $? -eq 0 ] ${PROOT} sh -c 'echo "echo OK" | sh /proc/self/fd/0' | grep ^OK$ TMP=/tmp/$(mcookie) ${PROOT} sh -c "exec 6<>${TMP}; readlink /proc/self/fd/6" | grep ^${TMP} rm -f ${TMP} care-2.2.1/tests/test-6fb08ce1.sh0000644000175000017500000000033712430615044015675 0ustar ivoireivoireif [ -z `which mcookie` ] || [ -z `which grep` ] || [ -z `which rm` ]; then exit 125; fi TMP=/tmp/$(mcookie) echo "OK" > ${TMP} ${PROOT} -b ${TMP}:/etc/fstab -b /dev/null -b /etc cat /etc/fstab | grep ^OK$ rm ${TMP} care-2.2.1/tests/test-gggggggg.sh0000644000175000017500000000102112430615044016215 0ustar ivoireivoireif [ -z `which mcookie` ] || [ -z `which env` ] || [ -z `which mkdir` ] || [ -z `which rm` ] || [ ! -x ${ROOTFS}/bin/readdir ]; then exit 125; fi TMP=/tmp/$(mcookie) mkdir ${TMP} ! env PROOT_DONT_POLLUTE_ROOTFS=1 ${PROOT} -b /bin:${TMP}/dont/create ${ROOTFS}/bin/readdir ${TMP} | grep -w dont [ $? -eq 0 ] env PROOT_DONT_POLLUTE_ROOTFS=1 ${PROOT} -b /bin:${TMP}/dont/create test -e ${TMP}/dont ${PROOT} -b /bin:${TMP}/dont/create test -e ${TMP}/dont ! test -e ${TMP}/dont [ $? -eq 0 ] chmod +rx -R ${TMP} rm -fr ${TMP} care-2.2.1/tests/test-e87b34ae.c0000644000175000017500000000120312430615044015502 0ustar ivoireivoire#include /* syscall(2), fork(2), usleep(3), */ #include /* perror(3), printf(3), */ #include /* PATH_MAX, */ #include /* exit(3), */ #include /* SYS_readlink, SYS_getcwd, */ #include /* errno, */ int main(void) { pid_t pid; int status; int i; for (i = 0; i < 1000; i++) { pid = fork(); switch (pid) { case -1: /* Is the maximum number of processes * reached? */ if (errno == EAGAIN) break; perror("fork()"); exit(EXIT_FAILURE); case 0: /* child */ exit(EXIT_SUCCESS); default: /* parent */ break; } } exit(EXIT_SUCCESS); } care-2.2.1/tests/test-bdc90417.c0000644000175000017500000000167312430615044015430 0ustar ivoireivoire#define _GNU_SOURCE /* See feature_test_macros(7) */ #include /* execv(3), syscall(2), */ #include /* SYS_*, */ #include /* *rlimit(2), */ #include /* *rlimit(2), */ #include /* EXIT_*, exit(3), */ int main(int argc, char *argv[]) { char *const dummy_argv[] = { "test", "stage2", NULL }; long brk1, brk2; int status; struct rlimit rlimit; switch (argc) { case 1: /* 1st step: set the stack limit to the max. */ status = getrlimit(RLIMIT_STACK, &rlimit); if (status < 0) exit(EXIT_FAILURE); rlimit.rlim_cur = rlimit.rlim_max; status = setrlimit(RLIMIT_STACK, &rlimit); if (status < 0) exit(EXIT_FAILURE); return execv(argv[0], dummy_argv); default: /* 2nd step: try to allocate some heap space. */ brk1 = syscall(SYS_brk, 0); brk2 = syscall(SYS_brk, brk1 + 1024 * 1024); exit(brk1 != brk2 ? EXIT_SUCCESS : EXIT_FAILURE); } } care-2.2.1/tests/test-5bed7143.c0000644000175000017500000000255012430615044015424 0ustar ivoireivoire#define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #define TEMPLATE "/tmp/proot-test-5bed7143-XXXXXX" #define COOKIE1 "2fde3df3558fa30bec1b8ebad42df20f" #define COOKIE2 "2ba90289e48d1896e0601239ac25f764" int main() { char *path1; char *path2; char *cwd; int status; path1 = mkdtemp(strdup(TEMPLATE)); if (path1 == NULL) exit(EXIT_FAILURE); status = chdir(path1); if (status < 0) exit(EXIT_FAILURE); status = mkdir(COOKIE1, 0777); if (status < 0) exit(EXIT_FAILURE); status = chdir(COOKIE1); if (status < 0) exit(EXIT_FAILURE); status = creat(COOKIE2, O_RDWR); if (status < 0) exit(EXIT_FAILURE); close(status); path2 = mktemp(strdup(TEMPLATE)); status = rename(path1, path2); if (status < 0) exit(EXIT_FAILURE); status = access(COOKIE2, F_OK); if (status < 0) exit(EXIT_FAILURE); cwd = get_current_dir_name(); if (cwd == NULL || memcmp(cwd, path2, strlen(path2)) != 0) exit(EXIT_FAILURE); status = unlink(COOKIE2); if (status < 0) exit(EXIT_FAILURE); status = rmdir(cwd); if (status < 0) exit(EXIT_FAILURE); if (get_current_dir_name() != NULL || errno != ENOENT) exit(EXIT_FAILURE); status = rmdir(path2); if (status < 0) exit(EXIT_FAILURE); exit(EXIT_SUCCESS); } care-2.2.1/tests/test-oooooooo.c0000755000175000017500000000115512430615044016140 0ustar ivoireivoire#include #include #include #include int main(void) { int i; for (i = 0; i < 999; i++) { int status; int fds[2]; pid_t pid; status = pipe(fds); if (status < 0) { perror("pipe"); break; } pid = fork(); switch (pid) { case -1: perror("fork"); exit(EXIT_FAILURE); case 0: /* child */ do status = write(fds[1], "!", 1); while (status > 0); perror("write"); exit(EXIT_FAILURE); default: /* parent */ status = kill(pid, SIGKILL); if (status < 0) { perror("kill"); exit(EXIT_FAILURE); } } } exit(EXIT_SUCCESS); } care-2.2.1/tests/test-bbbbbbbb.sh0000644000175000017500000000111012430615044016144 0ustar ivoireivoireif [ -z `which mcookie` ] || [ -z `which rm` ] || [ -z `which ln` ]; then exit 125; fi DONT_EXIST=$(mcookie) TMP1=$(mcookie) TMP2=$(mcookie) rm -f /tmp/${DONT_EXIST} ${PROOT} ln -sf /${DONT_EXIST} /tmp/ ${PROOT} ln -sf /${DONT_EXIST} /tmp/ rm -f /tmp/${DONT_EXIST} ${PROOT} ln -sf /etc/fstab/${DONT_EXIST} /tmp/ ! ${PROOT} ln -sf /etc/fstab/${DONT_EXIST} /tmp/ rm -f /tmp/${DONT_EXIST} rm -f /tmp/${TMP1} /tmp/${TMP2} touch /tmp/${TMP2} ln -sf /tmp/${DONT_EXIST} /tmp/${TMP1} ! ${PROOT} ln /tmp/${TMP2} /tmp/${TMP1} rm -f /tmp/${TMP1} /tmp/${TMP2} rm -f /tmp/${DONT_EXIST} care-2.2.1/tests/test-cdd39012.sh0000644000175000017500000000050312430615044015603 0ustar ivoireivoireif [ ! -x ${ROOTFS}/bin/ptrace ] || [ ! -x ${ROOTFS}/bin/ptrace-2 ] || [ ! -x ${ROOTFS}/bin/true ]; then exit 125; fi ${PROOT} -r ${ROOTFS} ptrace ${PROOT} -r ${ROOTFS} ptrace 2 ${PROOT} -r ${ROOTFS} ptrace-2 /bin/true ${PROOT} -r ${ROOTFS} ptrace-2 /bin/fork-wait ${PROOT} -r ${ROOTFS} ptrace-2 /bin/fork-wait 2 care-2.2.1/tests/echo.c0000644000175000017500000000017212430615044014225 0ustar ivoireivoire#include int main(int argc, char **argv) { int i; for (i = 1; i < argc; i++) puts(argv[i]); return 0; } care-2.2.1/tests/test-ssssssss.c0000644000175000017500000000220512430615044016172 0ustar ivoireivoire#include #include #include #include #include #include #include #include #include #include #include #include int main() { struct sockaddr_un sockaddr; socklen_t socklen; char *sockname; mode_t mask; int status; int fd; sockname = strdup("proot-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxXXXXXX"); if (sockname == NULL) return 125; (void) mktemp(sockname); if (sockname[0] == '\0') return 125; fd = socket(AF_UNIX, SOCK_STREAM, 0); if (fd < 0) { perror("socket"); exit(EXIT_FAILURE); } bzero(&sockaddr, sizeof(sockaddr)); sockaddr.sun_family = AF_UNIX; assert(strlen(sockname) == sizeof(sockaddr.sun_path)); memcpy(sockaddr.sun_path, sockname, sizeof(sockaddr.sun_path)); chdir("/tmp"); (void) unlink(sockaddr.sun_path); status = bind(fd, (const struct sockaddr *) &sockaddr, sizeof(sockaddr)); if (status < 0) { perror("bind"); exit(EXIT_FAILURE); } (void) unlink(sockaddr.sun_path); exit(EXIT_SUCCESS); } care-2.2.1/tests/test-305ae31d.sh0000644000175000017500000000030712430615044015601 0ustar ivoireivoireif [ -z `which mcookie` ] || [ -z `which ln` ] || [ -z `which true` ] || [ -z `which rm` ]; then exit 125; fi TMP=$(mcookie) ln -s /proc/self/mounts ${TMP} ${PROOT} -b ${TMP} true rm ${TMP} care-2.2.1/tests/fchdir_getcwd.c0000644000175000017500000000122712430615044016105 0ustar ivoireivoire#define _GNU_SOURCE #include #include #include #include #include #include #include int main(int argc, char *argv[]) { char path[PATH_MAX]; int status; int fd; if (argc < 2) { fprintf(stderr, "missing argument\n"); exit(EXIT_FAILURE); } fd = open(argv[1], O_DIRECTORY); if (fd < 0) { perror("open()"); exit(EXIT_FAILURE); } status = fchdir(fd); if (status < 0) { perror("fchdir()"); exit(EXIT_FAILURE); } if (getcwd(path, PATH_MAX) == NULL) { perror("getcwd()"); exit(EXIT_FAILURE); } printf("%s\n", get_current_dir_name()); exit(EXIT_SUCCESS); } care-2.2.1/tests/test-d92b57ca.sh0000644000175000017500000000017212430615044015674 0ustar ivoireivoireif [ -z `which env` ] || [ -z `which true` ]; then exit 125; fi env PROOT_NO_SUBRECONF=1 ${PROOT} ${PROOT} -v 1 true care-2.2.1/tests/test-55b731d3.sh0000644000175000017500000000007412430615044015532 0ustar ivoireivoireif ! `which pwd` -P; then exit 125; fi ${PROOT} pwd -P care-2.2.1/tests/ptrace.c0000644000175000017500000000160112430615044014563 0ustar ivoireivoire#include #include #include #include #include #include #include int main(int argc, char **argv) { int child_status; long status; pid_t pid; pid = (argc <= 1 ? fork() : vfork()); switch(pid) { case -1: perror("fork()"); exit(EXIT_FAILURE); case 0: /* child */ sleep(2); status = ptrace(PTRACE_TRACEME, 0, NULL, NULL); if (status < 0) { perror("ptrace(TRACEME)"); exit(EXIT_FAILURE); } if (argc <= 1) { kill(getpid(), SIGSTOP); exit(EXIT_SUCCESS); } else { execl("true", "true", NULL); exit(EXIT_FAILURE); } default: /* parent */ pid = waitpid(-1, &child_status, __WALL); if (pid < 0) { perror("waitpid()"); exit(EXIT_FAILURE); } if (argc <= 1) { if (!WIFSTOPPED(child_status)) exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } return 0; } care-2.2.1/tests/test-1ffc8309.c0000644000175000017500000000071512430615044015432 0ustar ivoireivoire#define _GNU_SOURCE #include #include #include #include #include #include int main() { int fds[2]; int status; uint8_t buffer; status = pipe2(fds, O_NONBLOCK); if (status < 0) { perror("pipe2"); exit(EXIT_FAILURE); } (void) alarm(5); (void) read(fds[0], &buffer, 1); if (errno != EAGAIN && errno != EWOULDBLOCK) { perror("read"); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } care-2.2.1/tests/test-nnnnnnnn.c0000644000175000017500000000325112430615044016124 0ustar ivoireivoire#include #include #include #include #include #include #include #include #include int main() { const char *sockname = "/test-nnnnnnnn-socket"; struct sockaddr_un sockaddr; socklen_t socklen; mode_t mask; int status; int fd; /* root can create $hostfs/test-nnnnnnnn-socket. */ if (getuid() == 0) return 125; /* clean-up previous socket. */ (void) unlink(sockname); fd = socket(AF_UNIX, SOCK_STREAM, 0); if (fd < 0) { perror("socket"); exit(EXIT_FAILURE); } bzero(&sockaddr, sizeof(sockaddr)); sockaddr.sun_family = AF_UNIX; strcpy(sockaddr.sun_path, sockname); mask = umask(S_IXUSR|S_IXGRP|S_IRWXO); status = bind(fd, (const struct sockaddr *) &sockaddr, SUN_LEN(&sockaddr)); if (status < 0) { perror("bind"); exit(EXIT_FAILURE); } umask(mask); status = listen(fd, 50); if (status < 0) { perror("listen"); exit(EXIT_FAILURE); } bzero(&sockaddr, sizeof(sockaddr)); socklen = sizeof(sockaddr); status = getsockname(fd, (struct sockaddr *) &sockaddr, &socklen); if (status < 0) { perror("getsockname"); exit(EXIT_FAILURE); } if (socklen != SUN_LEN(&sockaddr) + 1) { fprintf(stderr, "socklen: %d != %d + 1\n", socklen, SUN_LEN(&sockaddr)); exit(EXIT_FAILURE); } if (sockaddr.sun_family != AF_UNIX) { fprintf(stderr, "! AF_UNIX\n"); exit(EXIT_FAILURE); } if (socklen == sizeof(sockaddr) + 1) status = strncmp(sockaddr.sun_path, sockname, sizeof(sockaddr.sun_path)); else status = strcmp(sockaddr.sun_path, sockname); if (status != 0) { fprintf(stderr, "! %s\n", sockname); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } care-2.2.1/tests/test-00000000.sh0000644000175000017500000000012712430615044015333 0ustar ivoireivoireif [ ! -x ${ROOTFS}/bin/true ]; then exit 125; fi ${PROOT} -r ${ROOTFS} /bin/true care-2.2.1/tests/test-0cf405b0.c0000644000175000017500000000043212430615044015406 0ustar ivoireivoire#include /* execlp(2), */ #include /* exit(3), */ #include /* strcmp(3), */ int main(int argc, char *argv[]) { if (argc == 0) //strcmp(argv[0], "/proc/self/exe") == 0) exit(EXIT_SUCCESS); execlp("/proc/self/exe", NULL); exit(EXIT_FAILURE); } care-2.2.1/tests/test-e87ca6ca.sh0000644000175000017500000000053512430615044015760 0ustar ivoireivoireif [ -z `which mcookie` ] || [ -z `which cp` ] || [ -z `which true` ] || [ -z `which setcap` ] || [ -z `which rm` ]; then exit 125; fi if [ `id -u` -eq 0 ]; then exit 125; fi TMP=/tmp/$(mcookie) cp $(which true) ${TMP} ! ${PROOT} -i 123:456 setcap cap_setuid+ep ${TMP} [ $? -eq 0 ] ${PROOT} -0 setcap cap_setuid+ep ${TMP} rm -f ${TMP} care-2.2.1/tests/test-6d1e2650.sh0000644000175000017500000000026012430615044015526 0ustar ivoireivoireif [ ! -x ${ROOTFS}/bin/true ] || [ -z `which env` ]; then exit 125; fi ! env PATH=/nib ${PROOT} -r ${ROOTFS} true [ $? -eq 0 ] env PATH=/bin ${PROOT} -r ${ROOTFS} true care-2.2.1/tests/test-1743dd3d.sh0000644000175000017500000000061212430615044015610 0ustar ivoireivoireif [ ! -x ${ROOTFS}/bin/true ] || [ -z `which mcookie` ] || [ -z `which mkdir` ] || [ -z `which echo` ] || [ -z `which chmod` ]; then exit 125; fi TMP=/tmp/`mcookie` rm -f ${ROOTFS}/${TMP} mkdir -p ${ROOTFS}/tmp echo '#!/bin/true' > ${ROOTFS}/${TMP} chmod -x ${ROOTFS}/${TMP} ! ${PROOT} -r ${ROOTFS} ${TMP} chmod +x ${ROOTFS}/${TMP} ${PROOT} -r ${ROOTFS} ${TMP} rm -f ${ROOTFS}/${TMP} care-2.2.1/tests/test-iiiiiiii.c0000644000175000017500000000220212430615044016047 0ustar ivoireivoire#include #include #include #include #include int main(void) { int result; int status; char *path; int fd; path = strdup("/tmp/proot-test-iiiiiiii-XXXXXX"); if (path == NULL) { result = 125; goto end; } mktemp(path); if (path[0] == '\0') { result = 125; goto end; } status = symlink("/this_shall_not_exist_outside_proot", path); if (status < 0) { result = 125; goto end; } /* For faccessat(2) and fchmodat(2) syscalls, the fourth * parameter is *not* used by the kernel, only the libc uses * it. As a consequence, PRoot shall ignore this flag. * * To be sure this parameter is really ignored by PRoot, we * set it to NOFOLLOW when performing a direct faccessat(2) to * a symlink which is broken from the host point-of-view, but * valid from a guest point-of-view. When PRoot does not * honor this flag, the faccessat(2) is performed against the * referee anyway. */ status = syscall(SYS_faccessat, AT_FDCWD, path, X_OK, AT_SYMLINK_NOFOLLOW); result = (status == 0 ? EXIT_SUCCESS : EXIT_FAILURE); end: (void) unlink(path); exit(result); } care-2.2.1/tests/test-c10e2073.c0000644000175000017500000000160312430615044015330 0ustar ivoireivoire#include /* syscall(2), */ #include /* perror(3), fprintf(3), */ #include /* PATH_MAX, */ #include /* exit(3), */ #include /* strlen(3), */ #include /* SYS_readlink, SYS_getcwd, */ int main(void) { char path[PATH_MAX]; int status; status = syscall(SYS_readlink, "/proc/self/cwd", path, PATH_MAX); if (status < 0) { perror("readlink()"); exit(EXIT_FAILURE); } path[status] = '\0'; if (status != strlen(path)) { fprintf(stderr, "readlink() returned the wrong size %d != %z.\n", status, strlen(path)); exit(EXIT_FAILURE); } status = syscall(SYS_getcwd, path, PATH_MAX); if (status < 0) { perror("getcwd()"); exit(EXIT_FAILURE); } if (status != strlen(path) + 1) { fprintf(stderr, "getcwd() returned the wrong size %d != %z.\n", status, strlen(path)); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } care-2.2.1/tests/test-dddddddd.sh0000644000175000017500000000146012430615044016174 0ustar ivoireivoireif [ -z `which mcookie` ] || [ -z `which rm` ] || [ -z `which ln` ] || [ -z `which realpath` ] || [ -z `which mkdir` ] || [ -z `which rmdir` ]; then exit 125; fi CHECK1=$(realpath -e /proc/self/exe) CHECK2=$(realpath /proc/self/exe) if [ "${CHECK1}" != "${CHECK2}" ]; then exit 125; fi TMP="/tmp/$(mcookie)" TMP2="/tmp/$(mcookie)" RMDIR=$(realpath -e $(which rmdir)) MKDIR=$(realpath -e $(which mkdir)) export LANG=C ln -s /bin ${TMP} ! ${RMDIR} ${TMP} > ${TMP}.ref 2>&1 ! ${PROOT} -v -1 ${RMDIR} ${TMP} > ${TMP}.res 2>&1 cmp ${TMP}.ref ${TMP}.res ln -s /this/does/not/exist ${TMP2} ! ${MKDIR} ${TMP2} > ${TMP2}.ref 2>&1 ! ${PROOT} -v -1 ${MKDIR} ${TMP2} > ${TMP2}.res 2>&1 cmp ${TMP2}.ref ${TMP2}.res rm -f ${TMP} rm -f ${TMP}.ref rm -f ${TMP}.res rm -f ${TMP2} rm -f ${TMP2}.ref rm -f ${TMP2}.res care-2.2.1/tests/test-3624be91.sh0000644000175000017500000000027712430615044015541 0ustar ivoireivoireif [ -z `which sh` ] || [ -z `which kill` ] || [ -z `which grep` ] || [ -z `which cut` ]; then exit 125; fi ${PROOT} sh -c 'kill -15 $(grep TracerPid /proc/self/status | cut -f 2 -d :)' care-2.2.1/tests/test-c15999f9.sh0000644000175000017500000000041512430615044015556 0ustar ivoireivoireif [ -z `which mcookie` ] || [ -z `which mkdir` ] || [ -z `which test` ] || [ -z `which grep` ] || [ -z `which rm` ]; then exit 125; fi TMP=/tmp/$(mcookie) mkdir ${TMP} ${PROOT} -b /bin/true:${TMP}/true /bin/true ! test -e ${TMP}/true [ $? -eq 0 ] rm -fr ${TMP} care-2.2.1/tests/test-77777777.c.unreliable0000644000175000017500000000177212430615044017363 0ustar ivoireivoire#include #include #include #include #include #include int main() { int child_status; int status; pid_t pid; pid = fork(); switch (pid) { case -1: perror("fork()"); exit(EXIT_FAILURE); case 0: /* child */ status = raise(SIGSTOP); if (status != 0) { perror("raise(SIGSTOP)"); exit(EXIT_FAILURE); } sleep(1); exit(EXIT_FAILURE); default: /* parent */ status = waitpid(pid, &child_status, WUNTRACED); if (status < 0) { perror("waitpid()"); exit(EXIT_FAILURE); } if (WIFEXITED(child_status)) printf("exited, status=%d\n", WEXITSTATUS(child_status)); else if (WIFSIGNALED(child_status)) printf("killed by signal %d\n", WTERMSIG(child_status)); else if (WIFSTOPPED(child_status)) printf("stopped by signal %d\n", WSTOPSIG(child_status)); else if (WIFCONTINUED(child_status)) printf("continued\n"); if (WIFSTOPPED(child_status)) exit(EXIT_SUCCESS); else exit(EXIT_FAILURE); } } care-2.2.1/tests/test-51943658.c0000644000175000017500000000240512430615044015215 0ustar ivoireivoire#include /* syscall(2), */ #include /* perror(3), fprintf(3), */ #include /* PATH_MAX, */ #include /* exit(3), */ #include /* openat(2), */ int main(void) { int dir_fd; int dir_fd1; int dir_fd2; ssize_t status; char path1[PATH_MAX]; char path2[PATH_MAX]; char fd_link[64]; /* Format the path to the "virtual" link. */ dir_fd = open("/", O_RDONLY); if (dir_fd < 0) { perror("open(2)"); exit(EXIT_FAILURE); } dir_fd1 = openat(dir_fd, ".", O_RDONLY); if (dir_fd1 < 0) { perror("openat(2)"); exit(EXIT_FAILURE); } dir_fd2 = openat(dir_fd, "..", O_RDONLY); if (dir_fd2 < 0) { perror("openat(2)"); exit(EXIT_FAILURE); } sprintf(fd_link, "/proc/self/fd/%d", dir_fd1); status = readlink(fd_link, path1, PATH_MAX - 1); if (status < 0) { perror("readlink(2)"); exit(EXIT_FAILURE); } path1[status] = '\0'; sprintf(fd_link, "/proc/self/fd/%d", dir_fd2); status = readlink(fd_link, path2, PATH_MAX - 1); if (status < 0) { perror("readlink(2)"); exit(EXIT_FAILURE); } path2[status] = '\0'; if (strcmp(path1, "/") != 0) { fprintf(stderr, "/. != /"); exit(EXIT_FAILURE); } if (strcmp(path2, "/") != 0) { fprintf(stderr, "/.. != /"); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } care-2.2.1/README.txt0000644000175000017500000000012212430615044013472 0ustar ivoireivoirePRoot documentation: doc/proot/manual.txt CARE documentation: doc/care/manual.txt care-2.2.1/doc/0000755000175000017500000000000012430615044012546 5ustar ivoireivoirecare-2.2.1/doc/stylesheets/0000755000175000017500000000000012430615044015122 5ustar ivoireivoirecare-2.2.1/doc/stylesheets/website.css0000644000175000017500000000470112430615044017300 0ustar ivoireivoire* { padding: 0; margin: 0; color: #333333; line-height: 1.5em; font-family: sans; } html { background-color: #dddddd; } body { background-color: white; border: 1px solid #dddddd; border-radius: 1em; -moz-border-radius: 1em; max-width: 50em; min-width: 25em; margin-left: auto; margin-right: auto; margin-top: 1.5em; margin-bottom: 1.5em; -moz-box-shadow: 0 0 1.5em 0.5em #333333; -webkit-box-shadow: 0 0 1.5em 0.5em #333333; box-shadow: 0 0 1.5em 0.5em #333333; } #title { margin-left: auto; margin-right: auto; text-align: center; } h1 { text-align: center; text-shadow: 2px 3px 3px #333333; font-size: 3em; display: inline; } h2 { margin: 1em; margin-bottom: 0.5em; border-bottom: 1px dotted gray; } h3 { margin-left: 1em; margin-top: 1em; } h3 tt { font-style: italic; } #contents { text-align: center; background-color: gray; border-top: 1px solid #dddddd; border-bottom: 1px solid #dddddd; margin-right: -1px; border-right: 1px solid gray; margin-left: -1px; border-left: 1px solid gray; padding-top: 0.5em; padding-bottom: 0.5em; } #contents ul { margin: 0; } #contents li { display: inline; margin-left: 3%; margin-right: 3%; } #contents a { color: white; text-decoration: none; font-weight: bold; border-bottom: none; } #contents a:hover { border-bottom: 2px solid black; } a { text-decoration: none; border-bottom: 1px solid black; } p { text-align: justify; margin-left: 2em; margin-right: 2em; margin-top: 1em; margin-bottom: 0.5em; } ol { margin-left: 5em; margin-right: 2em; margin-top: 0.5em; margin-bottom: 1em; } li { margin-bottom: 0.5em; } table { margin-left: 2em; margin-right: 2em; } pre { margin: 2em ; margin-top: 0.5em ; margin-bottom: 1em ; padding: 0.5em; background-color: #dddddd; color: black; font-family: monospace; white-space: pre-wrap; border-style: solid; border-width: 1px; border-color: gray; border-radius: 0.5em; -moz-border-radius: 0.5em; } pre:first-line { font-style: italic; } tt { font-family: monospace; } ul { margin-left: 3em; } li p, li pre { margin-left: 0; } @media print { * { background-color: transparent ! important; border: none ! important; } }care-2.2.1/doc/stylesheets/website.xsl0000644000175000017500000000402312430615044017313 0ustar ivoireivoire

      
    
  • []

    []
    care-2.2.1/doc/proot/0000755000175000017500000000000012430615044013711 5ustar ivoireivoirecare-2.2.1/doc/proot/man.10000644000175000017500000005116212430615044014553 0ustar ivoireivoire.\" Man page generated from reStructuredText. . .TH PROOT 1 "2014-09-15" "4.0.3" "" .SH NAME PRoot \- chroot, mount --bind, and binfmt_misc without privilege/setup . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .SH SYNOPSIS .sp \fBproot\fP [\fIoption\fP] ... [\fIcommand\fP] .SH DESCRIPTION .sp PRoot is a user\-space implementation of \fBchroot\fP, \fBmount \-\-bind\fP, and \fBbinfmt_misc\fP\&. This means that users don\(aqt need any privileges or setup to do things like using an arbitrary directory as the new root filesystem, making files accessible somewhere else in the filesystem hierarchy, or executing programs built for another CPU architecture transparently through QEMU user\-mode. Also, developers can use PRoot as a generic Linux process instrumentation engine thanks to its extension mechanism, see \fI\%CARE\fP for an example. Technically PRoot relies on \fBptrace\fP, an unprivileged system\-call available in every Linux kernel. .sp The new root file\-system, a.k.a \fIguest rootfs\fP, typically contains a Linux distribution. By default PRoot confines the execution of programs to the guest rootfs only, however users can use the built\-in \fImount/bind\fP mechanism to access files and directories from the actual root file\-system, a.k.a \fIhost rootfs\fP, just as if they were part of the guest rootfs. .sp When the guest Linux distribution is made for a CPU architecture incompatible with the host one, PRoot uses the CPU emulator QEMU user\-mode to execute transparently guest programs. It\(aqs a convenient way to develop, to build, and to validate any guest Linux packages seamlessly on users\(aq computer, just as if they were in a \fInative\fP guest environment. That way all of the cross\-compilation issues are avoided. .sp PRoot can also \fImix\fP the execution of host programs and the execution of guest programs emulated by QEMU user\-mode. This is useful to use host equivalents of programs that are missing from the guest rootfs and to speed up build\-time by using cross\-compilation tools or CPU\-independent programs, like interpreters. .sp It is worth noting that the guest kernel is never involved, regardless of whether QEMU user\-mode is used or not. Technically, when guest programs perform access to system resources, PRoot translates their requests before sending them to the host kernel. This means that guest programs can use host resources (devices, network, ...) just as if they were "normal" host programs. .SH OPTIONS .sp The command\-line interface is composed of two parts: first PRoot\(aqs options (optional), then the command to launch (\fB/bin/sh\fP if not specified). This section describes the options supported by PRoot, that is, the first part of its command\-line interface. .SS Regular options .INDENT 0.0 .TP .BI \-r \ path\fP,\fB \ \-\-rootfs\fB= path Use \fIpath\fP as the new guest root file\-system, default is \fB/\fP\&. .sp The specified \fIpath\fP typically contains a Linux distribution where all new programs will be confined. The default rootfs is \fB/\fP when none is specified, this makes sense when the bind mechanism is used to relocate host files and directories, see the \fB\-b\fP option and the \fBExamples\fP section for details. .sp It is recommended to use the \fB\-R\fP or \fB\-S\fP options instead. .TP .BI \-b \ path\fP,\fB \ \-\-bind\fB= path\fP,\fB \ \-m \ path\fP,\fB \ \-\-mount\fB= path Make the content of \fIpath\fP accessible in the guest rootfs. .sp This option makes any file or directory of the host rootfs accessible in the confined environment just as if it were part of the guest rootfs. By default the host path is bound to the same path in the guest rootfs but users can specify any other location with the syntax: \fB\-b *host_path*:*guest_location*\fP\&. If the guest location is a symbolic link, it is dereferenced to ensure the new content is accessible through all the symbolic links that point to the overlaid content. In most cases this default behavior shouldn\(aqt be a problem, although it is possible to explicitly not dereference the guest location by appending it the \fB!\fP character: \fB\-b *host_path*:*guest_location!*\fP\&. .TP .BI \-q \ command\fP,\fB \ \-\-qemu\fB= command Execute guest programs through QEMU as specified by \fIcommand\fP\&. .sp Each time a guest program is going to be executed, PRoot inserts the QEMU user\-mode \fIcommand\fP in front of the initial request. That way, guest programs actually run on a virtual guest CPU emulated by QEMU user\-mode. The native execution of host programs is still effective and the whole host rootfs is bound to \fB/host\-rootfs\fP in the guest environment. .TP .BI \-w \ path\fP,\fB \ \-\-pwd\fB= path\fP,\fB \ \-\-cwd\fB= path Set the initial working directory to \fIpath\fP\&. .sp Some programs expect to be launched from a given directory but do not perform any \fBchdir\fP by themselves. This option avoids the need for running a shell and then entering the directory manually. .TP .BI \-v \ value\fP,\fB \ \-\-verbose\fB= value Set the level of debug information to \fIvalue\fP\&. .sp The higher the integer \fIvalue\fP is, the more detailed debug information is printed to the standard error stream. A negative \fIvalue\fP makes PRoot quiet except on fatal errors. .TP .B \-V\fP,\fB \-\-version\fP,\fB \-\-about Print version, copyright, license and contact, then exit. .TP .B \-h\fP,\fB \-\-help\fP,\fB \-\-usage Print the version and the command\-line usage, then exit. .UNINDENT .SS Extension options .sp The following options enable built\-in extensions. Technically developers can add their own features to PRoot or use it as a Linux process instrumentation engine thanks to its extension mechanism, see the sources for further details. .INDENT 0.0 .TP .BI \-k \ string\fP,\fB \ \-\-kernel\-release\fB= string Make current kernel appear as kernel release \fIstring\fP\&. .sp If a program is run on a kernel older than the one expected by its GNU C library, the following error is reported: "FATAL: kernel too old". To be able to run such programs, PRoot can emulate some of the features that are available in the kernel release specified by \fIstring\fP but that are missing in the current kernel. .TP .B \-0\fP,\fB \-\-root\-id Make current user appear as "root" and fake its privileges. .sp Some programs will refuse to work if they are not run with "root" privileges, even if there is no technical reason for that. This is typically the case with package managers. This option allows users to bypass this kind of limitation by faking the user/group identity, and by faking the success of some operations like changing the ownership of files, changing the root directory to \fB/\fP, ... Note that this option is quite limited compared to \fBfakeroot\fP\&. .TP .BI \-i \ string\fP,\fB \ \-\-change\-id\fB= string Make current user and group appear as \fIstring\fP "uid:gid". .sp This option makes the current user and group appear as \fIuid\fP and \fIgid\fP\&. Likewise, files actually owned by the current user and group appear as if they were owned by \fIuid\fP and \fIgid\fP instead. Note that the \fB\-0\fP option is the same as \fB\-i 0:0\fP\&. .UNINDENT .SS Alias options .sp The following options are aliases for handy sets of options. .INDENT 0.0 .TP .BI \-R \ path Alias: \fB\-r *path*\fP + a couple of recommended \fB\-b\fP\&. .sp Programs isolated in \fIpath\fP, a guest rootfs, might still need to access information about the host system, as it is illustrated in the \fBExamples\fP section of the manual. These host information are typically: user/group definition, network setup, run\-time information, users\(aq files, ... On all Linux distributions, they all lie in a couple of host files and directories that are automatically bound by this option: .INDENT 7.0 .IP \(bu 2 /etc/host.conf .IP \(bu 2 /etc/hosts .IP \(bu 2 /etc/hosts.equiv .IP \(bu 2 /etc/mtab .IP \(bu 2 /etc/netgroup .IP \(bu 2 /etc/networks .IP \(bu 2 /etc/passwd .IP \(bu 2 /etc/group .IP \(bu 2 /etc/nsswitch.conf .IP \(bu 2 /etc/resolv.conf .IP \(bu 2 /etc/localtime .IP \(bu 2 /dev/ .IP \(bu 2 /sys/ .IP \(bu 2 /proc/ .IP \(bu 2 /tmp/ .IP \(bu 2 $HOME .IP \(bu 2 \fIpath\fP .UNINDENT .TP .BI \-S \ path Alias: \fB\-0 \-r *path*\fP + a couple of recommended \fB\-b\fP\&. .sp This option is useful to safely create and install packages into the guest rootfs. It is similar to the \fB\-R\fP option expect it enables the \fB\-0\fP option and binds only the following minimal set of paths to avoid unexpected changes on host files: .INDENT 7.0 .IP \(bu 2 /etc/host.conf .IP \(bu 2 /etc/hosts .IP \(bu 2 /etc/nsswitch.conf .IP \(bu 2 /etc/resolv.conf .IP \(bu 2 /dev/ .IP \(bu 2 /sys/ .IP \(bu 2 /proc/ .IP \(bu 2 /tmp/ .IP \(bu 2 $HOME .IP \(bu 2 \fIpath\fP .UNINDENT .UNINDENT .SH EXIT STATUS .sp If an internal error occurs, \fBproot\fP returns a non\-zero exit status, otherwise it returns the exit status of the last terminated program. When an error has occurred, the only way to know if it comes from the last terminated program or from \fBproot\fP itself is to have a look at the error message. .SH FILES .sp PRoot reads links in \fB/proc//fd/\fP to support \fIopenat(2)\fP\-like syscalls made by the guest programs. .SH EXAMPLES .sp In the following examples the directories \fB/mnt/slackware\-8.0\fP and \fB/mnt/armslack\-12.2/\fP contain a Linux distribution respectively made for x86 CPUs and ARM CPUs. .SS \fBchroot\fP equivalent .sp To execute a command inside a given Linux distribution, just give \fBproot\fP the path to the guest rootfs followed by the desired command. The example below executes the program \fBcat\fP to print the content of a file: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C proot \-r /mnt/slackware\-8.0/ cat /etc/motd Welcome to Slackware Linux 8.0 .ft P .fi .UNINDENT .UNINDENT .sp The default command is \fB/bin/sh\fP when none is specified. Thus the shortest way to confine an interactive shell and all its sub\-programs is: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C proot \-r /mnt/slackware\-8.0/ $ cat /etc/motd Welcome to Slackware Linux 8.0 .ft P .fi .UNINDENT .UNINDENT .SS \fBmount \-\-bind\fP equivalent .sp The bind mechanism enables one to relocate files and directories. This is typically useful to trick programs that perform access to hard\-coded locations, like some installation scripts: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C proot \-b /tmp/alternate_opt:/opt $ cd to/sources $ make install [...] install \-m 755 prog "/opt/bin" [...] # prog is installed in "/tmp/alternate_opt/bin" actually .ft P .fi .UNINDENT .UNINDENT .sp As shown in this example, it is possible to bind over files not even owned by the user. This can be used to \fIoverlay\fP system configuration files, for instance the DNS setting: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C ls \-l /etc/hosts \-rw\-r\-\-r\-\- 1 root root 675 Mar 4 2011 /etc/hosts .ft P .fi .UNINDENT .UNINDENT .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C proot \-b ~/alternate_hosts:/etc/hosts $ echo \(aq1.2.3.4 google.com\(aq > /etc/hosts $ resolveip google.com IP address of google.com is 1.2.3.4 $ echo \(aq5.6.7.8 google.com\(aq > /etc/hosts $ resolveip google.com IP address of google.com is 5.6.7.8 .ft P .fi .UNINDENT .UNINDENT .sp Another example: on most Linux distributions \fB/bin/sh\fP is a symbolic link to \fB/bin/bash\fP, whereas it points to \fB/bin/dash\fP on Debian and Ubuntu. As a consequence a \fB#!/bin/sh\fP script tested with Bash might not work with Dash. In this case, the binding mechanism of PRoot can be used to set non\-disruptively \fB/bin/bash\fP as the default \fB/bin/sh\fP on these two Linux distributions: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C proot \-b /bin/bash:/bin/sh [...] .ft P .fi .UNINDENT .UNINDENT .sp Because \fB/bin/sh\fP is initially a symbolic link to \fB/bin/dash\fP, the content of \fB/bin/bash\fP is actually bound over this latter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C proot \-b /bin/bash:/bin/sh $ md5sum /bin/sh 089ed56cd74e63f461bef0fdfc2d159a /bin/sh $ md5sum /bin/bash 089ed56cd74e63f461bef0fdfc2d159a /bin/bash $ md5sum /bin/dash 089ed56cd74e63f461bef0fdfc2d159a /bin/dash .ft P .fi .UNINDENT .UNINDENT .sp In most cases this shouldn\(aqt be a problem, but it is still possible to strictly bind \fB/bin/bash\fP over \fB/bin/sh\fP \-\- without dereferencing it \-\- by specifying the \fB!\fP character at the end: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C proot \-b \(aq/bin/bash:/bin/sh!\(aq $ md5sum /bin/sh 089ed56cd74e63f461bef0fdfc2d159a /bin/sh $ md5sum /bin/bash 089ed56cd74e63f461bef0fdfc2d159a /bin/bash $ md5sum /bin/dash c229085928dc19e8d9bd29fe88268504 /bin/dash .ft P .fi .UNINDENT .UNINDENT .SS \fBchroot\fP + \fBmount \-\-bind\fP equivalent .sp The two features above can be combined to make any file from the host rootfs accessible in the confined environment just as if it were initially part of the guest rootfs. It is sometimes required to run programs that rely on some specific files: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C proot \-r /mnt/slackware\-8.0/ $ ps \-o tty,command Error, do this: mount \-t proc none /proc .ft P .fi .UNINDENT .UNINDENT .sp works better with: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C proot \-r /mnt/slackware\-8.0/ \-b /proc $ ps \-o tty,command TT COMMAND ? \-bash ? proot \-b /proc /mnt/slackware\-8.0/ ? /lib/ld\-linux.so.2 /bin/sh ? /lib/ld\-linux.so.2 /usr/bin/ps \-o tty,command .ft P .fi .UNINDENT .UNINDENT .sp Actually there\(aqs a bunch of such specific files, that\(aqs why PRoot provides the option \fB\-R\fP to bind automatically a pre\-defined list of recommended paths: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C proot \-R /mnt/slackware\-8.0/ $ ps \-o tty,command TT COMMAND pts/6 \-bash pts/6 proot \-R /mnt/slackware\-8.0/ pts/6 /lib/ld\-linux.so.2 /bin/sh pts/6 /lib/ld\-linux.so.2 /usr/bin/ps \-o tty,command .ft P .fi .UNINDENT .UNINDENT .SS \fBchroot\fP + \fBmount \-\-bind\fP + \fBsu\fP equivalent .sp Some programs will not work correctly if they are not run by the "root" user, this is typically the case with package managers. PRoot can fake the root identity and its privileges when the \fB\-0\fP (zero) option is specified: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C proot \-r /mnt/slackware\-8.0/ \-0 # id uid=0(root) gid=0(root) [...] # mkdir /tmp/foo # chmod a\-rwx /tmp/foo # echo \(aqI bypass file\-system permissions.\(aq > /tmp/foo/bar # cat /tmp/foo/bar I bypass file\-system permissions. .ft P .fi .UNINDENT .UNINDENT .sp This option is typically required to create or install packages into the guest rootfs. Note it is \fInot\fP recommended to use the \fB\-R\fP option when installing packages since they may try to update bound system files, like \fB/etc/group\fP\&. Instead, it is recommended to use the \fB\-S\fP option. This latter enables the \fB\-0\fP option and binds only paths that are known to not be updated by packages: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C proot \-S /mnt/slackware\-8.0/ # installpkg perl.tgz Installing package perl... .ft P .fi .UNINDENT .UNINDENT .SS \fBchroot\fP + \fBmount \-\-bind\fP + \fBbinfmt_misc\fP equivalent .sp PRoot uses QEMU user\-mode to execute programs built for a CPU architecture incompatible with the host one. From users\(aq point\-of\-view, guest programs handled by QEMU user\-mode are executed transparently, that is, just like host programs. To enable this feature users just have to specify which instance of QEMU user\-mode they want to use with the option \fB\-q\fP: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C proot \-R /mnt/armslack\-12.2/ \-q qemu\-arm $ cat /etc/motd Welcome to ARMedSlack Linux 12.2 .ft P .fi .UNINDENT .UNINDENT .sp The parameter of the \fB\-q\fP option is actually a whole QEMU user\-mode command, for instance to enable its GDB server on port 1234: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C proot \-R /mnt/armslack\-12.2/ \-q "qemu\-arm \-g 1234" emacs .ft P .fi .UNINDENT .UNINDENT .sp PRoot allows one to mix transparently the emulated execution of guest programs and the native execution of host programs in the same file\-system namespace. It\(aqs typically useful to extend the list of available programs and to speed up build\-time significantly. This mixed\-execution feature is enabled by default when using QEMU user\-mode, and the content of the host rootfs is made accessible through \fB/host\-rootfs\fP: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C proot \-R /mnt/armslack\-12.2/ \-q qemu\-arm $ file /bin/echo [...] ELF 32\-bit LSB executable, ARM [...] $ /bin/echo \(aqHello world!\(aq Hello world! $ file /host\-rootfs/bin/echo [...] ELF 64\-bit LSB executable, x86\-64 [...] $ /host\-rootfs/bin/echo \(aqHello mixed world!\(aq Hello mixed world! .ft P .fi .UNINDENT .UNINDENT .sp Since both host and guest programs use the guest rootfs as \fB/\fP, users may want to deactivate explicitly cross\-filesystem support found in most GNU cross\-compilation tools. For example with GCC configured to cross\-compile to the ARM target: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C proot \-R /mnt/armslack\-12.2/ \-q qemu\-arm $ export CC=/host\-rootfs/opt/cross\-tools/arm\-linux/bin/gcc $ export CFLAGS="\-\-sysroot=/" # could be optional indeed $ ./configure; make .ft P .fi .UNINDENT .UNINDENT .sp As with regular files, a host instance of a program can be bound over its guest instance. Here is an example where the guest binary of \fBmake\fP is overlaid by the host one: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C proot \-R /mnt/armslack\-12.2/ \-q qemu\-arm \-b /usr/bin/make $ which make /usr/bin/make $ make \-\-version # overlaid GNU Make 3.82 Built for x86_64\-slackware\-linux\-gnu .ft P .fi .UNINDENT .UNINDENT .sp It\(aqs worth mentioning that even when mixing the native execution of host programs and the emulated execution of guest programs, they still believe they are running in a native guest environment. As a demonstration, here is a partial output of a typical \fB\&./configure\fP script: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C checking whether the C compiler is a cross\-compiler... no .ft P .fi .UNINDENT .UNINDENT .SH DOWNLOADS .SS PRoot .sp The latest release of PRoot is packaged on \fI\%http://packages.proot.me\fP and sources are hosted on \fI\%http://github.proot.me\fP\&. It is also available as highly compatible static binaries: .INDENT 0.0 .IP \(bu 2 for x86_64: \fI\%http://static.proot.me/proot\-x86_64\fP .IP \(bu 2 for x86: \fI\%http://static.proot.me/proot\-x86\fP .IP \(bu 2 for ARM: \fI\%http://static.proot.me/proot\-arm\fP .IP \(bu 2 other architectures: on demand. .UNINDENT .SS Rootfs .sp Here follows a couple of URLs where some rootfs archives can be freely downloaded. Note that \fBmknod\fP errors reported by \fBtar\fP when extracting these archives can be safely ignored since special files are typically bound (see \fB\-R\fP option for details). .INDENT 0.0 .IP \(bu 2 \fI\%http://download.openvz.org/template/precreated/\fP .IP \(bu 2 \fI\%https://images.linuxcontainers.org/images/\fP .IP \(bu 2 \fI\%http://distfiles.gentoo.org/releases/\fP .IP \(bu 2 \fI\%http://cdimage.ubuntu.com/ubuntu\-core/releases/\fP .IP \(bu 2 \fI\%http://archlinuxarm.org/developers/downloads\fP .UNINDENT .sp Technically such rootfs archive can be created by running the following command on the expected Linux distribution: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C tar \-\-one\-file\-system \-\-create \-\-gzip \-\-file my_rootfs.tar.gz / .ft P .fi .UNINDENT .UNINDENT .SS QEMU user\-mode .sp QEMU user\-mode is required only if the guest rootfs was made for a CPU architecture incompatible with the host one, for instance when using a ARM rootfs on a x86_64 computer. This package can be installed either from \fI\%http://qemu.proot.me\fP or from the host package manager under the name of "qemu\-user" on most Linux distro. In case one would like to build QEMU user\-mode from sources, the \fB\-\-enable\-linux\-user\fP option has to be specified to the \fB\&./configure\fP script. .SH SEE ALSO .sp chroot(1), mount(8), binfmt_misc, ptrace(2), qemu(1), sb2(1), bindfs(1), fakeroot(1), fakechroot(1) .SH COLOPHON .sp Visit \fI\%http://proot.me\fP for help, bug reports, suggestions, patches, ... Copyright (C) 2014 STMicroelectronics, licensed under GPL v2 or later. .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C _____ _____ ___ | __ \e __ \e_____ _____| |_ | __/ / _ \e/ _ \e _| |__| |__|__\e_____/\e_____/\e____| .ft P .fi .UNINDENT .UNINDENT .\" Generated by docutils manpage writer. . care-2.2.1/doc/proot/changelog.txt0000644000175000017500000010407512430615044016410 0ustar ivoireivoireRelease v4.0.3 ============== + Heap emulation is disabled when a "suspicious" call to brk(2) is actually legit, as it might be the case when launching the very first program. + The "-0" and "-S" options ("root" identity emulation) now fake success of mknodat(2), as it was the case for mknod(2) previously. This missing feature was revealed by the AArch64 port. + The "-k" option (kernel compatibility emulation) now works on Linux/AArch64. Thanks to Rémi Duraffort for the bug reports and for his LAVA testing platform! Release v4.0.2 ============== + Fix how the very first program is launched by PRoot. Previously, argv[0] was not preserved when the very first program was launched through a symbolic link. This old behavior used to bug programs like Busybox and python-exec. Thanks to "hhm", Ivailo "fluxer" Monev, and Joakim Tjernlund for the bug reports. + Fix renameat(2) sysexit support. There was a bug in PRoot that was exposed by the Aarch64 (a.k.a arm64) port only but that might affect other architectures. + Fix build for AArch64. Thanks to Rémi Duraffort for the patches and for the Debian/arm64 testing platform. + Fix support for "long" socket paths. These can only be 108 bytes long; this limit might be easily reached with PRoot since the path to the rootfs is always prepended. The solution was to automatically bind this long path to a shorter path. This bug was exposed by LibreOffice and Yocto's pseudo. Thanks to Christophe Guillon for the bug report. Release v4.0.1 ============== + Fix a couple of portability issues in the testsuite. Thanks to Rémi Duraffort for all the tests he made on his instance of Linaro LAVA. + Set $PWD to the value specified by the -w option, otherwise Bash pwd builtin might be confused under some specific circumstances. Thanks to Jérémy Bobbio for the bug report. + Fix support for accessat and fchmodat syscalls: they have only three parameters, not four. This bug was exposed by Gentoo's sandbox: proot -S gentoo-amd64-hardened+nomultilib-rootfs emerge util-linux Release v4.0.0 ============== Highlights ---------- + It is now possible to use GDB, Strace, or any other program based on "ptrace" under PRoot. This was not the case previously because it is not possible to stack ptracers on Linux, so an emulation layer was developed in order to bypass this limitation. This has required a lot of changes in PRoot, hence the major number version bumping. It was mostly tested on x86_64, and partially tested on x86 and ARM. This ptrace emulation support is still experimental, and there are a couple of known issues, but feel free to report unexpected behaviors if you need a fix. + A new command-line option is available: "-S". It is similar to the "-R" option expect it enables the "-0" option and binds only a minimal set of paths that are known to not be updated by package installations, to avoid unexpected changes on host files. This option is useful to safely create and install packages into the guest rootfs. For example: $ proot -S ubuntu-14.04-rootfs/ apt-get install samba or: $ proot -S ubuntu-14.04-rootfs/ # apt-get install samba If "-0 -R" is used instead of "-S", the same command fails since it tries to update "/etc/group", which is bound to the host system and is not writable (assuming PRoot is ran without privileges): $ proot -0 -R ubuntu-14.04-rootfs/ # apt-get install samba [...] Adding group `sambashare' (GID 105) ... Permission denied + The fake_id0 extension can now fake any user and group identifiers. That means, when "-0" is specified, PRoot-ed processes can change their real, effective and saved identifiers, with respect to the rules described in setuid, setfsuid, setreuid, setresuid, and setfsuid manuals. Also, the new command-line option "-i" was added to change explicitly the identifiers to the specified values. This option will be used by CARE to re-execute with the same initial identifiers, but it could also be useful to threaten your teammates ;). Note that the "-0" option is actually the same as "-i 0:0". + The old command-line interface is not supported anymore. That means it is now impossible to specify the path to the guest rootfs without using -r or -R. Also, -Q and -B options are definitively gone, instead the -R option must be specified, respectively with and without -q. See PRoot v3.1 release notes for details. Fixes ----- + getcwd(2) and chdir(2) now return the correct error code when, respectively, the current directory does not exist anymore and the target directory doesn't have the "search" permission. + Named file descriptors (ie. links in /proc//fd/*) are not dereferenced anymore since they may point to special objects like pipes, sockets, inodes, ... Such objects do not exist on the file-system name-space, so dereferencing them used to cause unexpected errors. + Extensions now see every component of canonicalized paths. An optimization in the canonicalization loop used to skip the first part of a path if it was known to be already canonicalized, sadly this short-cut may confuse some extensions, like -0. + Temporary files and directories created by PRoot for its own purpose are now automatically deleted when PRoot exits. Miscellaneous ------------- + PRoot does not rely on GCC C extensions anymore, like nested functions. That means its stack does not have to be executable (this is required for hardened Linux systems), and it can now be compiled with Clang. + The ASLR (Address Space Layout Randomization) is not disabled anymore, and the heap is now emulated on all architectures. Internal changes ---------------- This section is dedicated to developers. + PRoot now remembers the parent of all tracees, it is similar to a traced process tree. This was required for the ptrace emulation support, but this could be useful to some extensions. + It is now possible to restart a tracee with any ptrace restart mode: single-step, single-block, ... + Functions {peek,poke}_mem were replaced with functions {peek,poke}_{,u}int{8,16,32,64}. These new functions performs type conversion and fetch only the necessary amount of data in target tracee's memory to avoid invalid accesses. + There is a new interface to handle ELF auxiliary vectors. See ptrace emulation, kompat and fake_id0 extensions for usage examples. + There is a new interface to create temporary files and directories that are automatically deleted on exit. See CARE extension, glue and auxv support for usage examples. + When built with GCC function instrumentation support, PRoot prints the currently called function on standard error stream (stderr). Thanks ------ Thanks go to Stephen McCamant, Oren Tirosh, Jérôme Audu, and Carlos Hernan Prada Rojas for their bug reports and tests; and to Rémi Duraffort for his contributions. Release v3.2.2 ============== + Remove a useless memory layout constraint on x86_64 that bugs some programs like java and or qemu. + It is now possible to launch the initial program from a relative path without specifying the "./" prefix, for example: $ proot path/to/program + Don't discard fcntl(F_DUPFD_CLOEXEC) systematically when the kompat extension is enabled (-k option). + Don't use syscalls that require Linux >= 2.6.16 anymore. Release v3.2.1 ============== + Make ptrace/seccomp even more portable on Ubuntu. Thanks to Maxence Dalmais for the bug report and tests. Release v3.2 ============ This release was mostly driven by the requirements of "CARE", a new project based on PRoot that will be released publicly soon on http://reproducible.io. For information, "CARE" is the short for "Comprehensive Archiver for Reproducible Execution". Highlights ---------- + Many bugs exposed by a couple of static code analyzers (Coverity, Clang, ...) and some test-suites (Linux Test Project, libuv, ...) are now fixed. + The "kompat" extension ("-k" option) can now emulate most of the kernel features that would be required by the guest system but that are not available on the host kernel. For example, it can now make programs from Ubuntu 13.04 64-bit run on RedHat 5 64-bit without any further tweaks: rh5-64$ proot -k 3.8 -R ubuntu-13.04-64bit/ ... + On ARM and x86_64, the heap segment is now emulated with a regular memory mapping to ensure this former always exists. This was required because some kernels might put a non-fixed memory mapping right after the regular heap when using some GNU ELF interpreters (ld.so) as loaders. Without the heap segment emulation, some programs like Bash would crash because the heap can't grow anymore: bash: xmalloc: locale.c:73: cannot allocate 2 bytes (0 bytes allocated) Miscellaneous ------------- + When using the "-R" option, the path to the guest rootfs is now bound into the guest rootfs itself. This is required to run programs that search for their DSOs in /proc/self/maps, like VLC for instance. + When using the "-v" option with a level greater than 2, syscalls are now printed as strings instead of numbers, à la strace: $ proot -v 3 true [...] proot info: pid 29847: sysenter start: mmap(0x0, 0x2d141, 0x1, 0x2, 0x3, 0x0) [...] [...] + The article about the migration from ScratchBox2 is now publicly available: https://github.com/cedric-vincent/PRoot/blob/v3.2/doc/articles/howto_migrate_from_scratchbox2.txt Internal changes ---------------- + Tools based on PRoot (CARE, DepsTracker, ATOS, ...) can now easily replace the original command-line interface with their own command-line interface. + It is now possible to chain forged syscalls to a regular syscall. Search for "register_chained_syscall" in the sources for details. + A couple of new helpers are now visible from the extensions. Thanks ------ + Bug reports and tests: Corbin Champion, Maxence Dalmais, and Nicolas Cornu. + Static code analysis: Antoine Moynault and Christophe Guillon. + Patches: Rémi Duraffort. + Unexpected hint: Christophe Monat :) Release v3.1 ============ Command-line interface changes ------------------------------ + The initial command is not search in "." anymore, unless the "./" prefix is specified or unless "." is in $PATH, as expected. + The "-B" and "-Q" options are obsoleted by the new "-R" option. This latter is equivalent to "-B -r", as there was actually no point at using the "-B" option without "-r". + A warning is now emitted when the rootfs is specified à la chroot(1), that is, without using "-r" or "-R". The old command-line interface is not documented anymore, but it will be still supported for a couple of releases. Although, users are strongly encouraged to switch to the new one: ====================== ================= old CLI new CLI ====================== ================= proot rootfs proot -r rootfs proot -B rootfs proot -R rootfs proot -B -r rootfs proot -R rootfs proot -Q qemu rootfs proot -R rootfs -q qemu proot -Q qemu -r rootfs proot -R rootfs -q qemu ======================= ======================= Extensions ---------- + The "kompat" extension ("-k" option) has been greatly enhanced. For example, it can now make programs from Ubuntu 13.04 32-bit run on RedHat 5 64-bit: rh5-64$ proot -k 3.8 -R ubuntu-13.04-32bit/ ... + The "fake id0" extension ("-0" option) handles more syscalls: mknod(2), capset(2), setxattr(2), setresuid(2), setresgid(2), getresuid(2), and getresgid(2). Miscellaneous ------------- + PRoot is now compiled with large file-system support (LFS), this make it works with 64-bit file-systems (eg. CIFS) on 32-bit platforms. + The special symbolic link "/proc/self/root" now points to the guest rootfs, that is, to the path specified by "-r" or "-R". Just like with chroot(2), this symlink may be broken as the referenced host path likely does not exist in the guest rootfs. Although, this symlink is typically used to know if a process is under a chroot-ed environment. + Under QEMU, LD_LIBRARY_PATH is not clobbered anymore when a guest program is launched by a host program. + When seccomp-filter is enabled, this release is about 8% faster than the previous one. + A couple of bugs reported by Scan Coverity are fixed. Thanks ------ Special thanks to Stephan Hadamik, Jérôme Audu, and Rémi Duraffort for their valuable help. Release v3.0.2 ============== + Fix the search of the initial command: when the initial command is a symbolic link, PRoot has to dereference it in guest namespace, not in the host one. + Return error code EACCESS instead of EISDIR when trying to execute a directory. Some programs, such as "env", behave differently with respect to this error code. For example: ### setup $ mkdir -p /tmp/foo/python $ export PATH=/tmp/foo:$PATH ### before (PRoot v2.3 ... v3.0.1) before$ proot env python env: python: Is a directory ### now (PRoot v3.0.2 ...) $ proot env python Python 2.7.5 (default, May 29 2013, 02:28:51) [GCC 4.8.0] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> Release v3.0.1 ============== Fix support for bindings where the guest path is explicitly not dereferenced. Be careful, the syntax has changed: before$ proot -b /bin/bash:!/bin/sh now$ proot -b /bin/bash:/bin/sh! Release v3.0 ============ New features ------------ + PRoot can now use the kernel feature named "seccomp-filter", a.k.a "seccomp mode 2", to improve its own performance significantly. For examples on my workstation, the tables below show the time overhead induced by PRoot compared to a native execution: - when generating the Perl 5.16.1 package: =============== =========== ========== command seccomp off seccomp on =============== =========== ========== ./configure.gnu 75% 25% make -j4 70% 45% make -j4 check 25% 9% =============== =========== ========== - when generating the Coreutils 8.19 package: =============== =========== ========== command seccomp off seccomp on =============== =========== ========== ./configure 80% 33% make -j4 75% 33% make -j4 check 80% 8% =============== =========== ========== + It is now possible to explicitly not dereference the guest location of a binding by specifying ``!`` as the first character. For instance:: proot -b /bin/bash:!/bin/sh will not overlay ``/bin/dash`` when this latter is pointed to by ``/bin/sh`` (it's typically the case on Ubuntu and Debian). Fix --- + The initial command is not search in $PATH anymore when it starts with ``/`` or ``./``, and it doesn't exist. For instance:: $ rm test $ proot ./test proot warning: './test not found (root = /, cwd = /usr/local/cedric/git/proot) proot error: see `proot --help` or `man proot`. Thanks ------ Many thanks to Will Drewry and Indan Zupancic, who made possible to accelerate PTRACE_SYSCALL with seccomp-filter. Also, thanks to Paul Moore for his valuable set of seccomp tools. Notes ----- + Unlike what I said, this release is not shipped with a ptrace emulator. It's planned for the next one, though. + Seccomp-filter was first introduced in Linux 3.5 a year ago, it was also officially back-ported to Ubuntu 12.04 (Linux 3.2). To know if PRoot is actually using this accelerator on your system, check the verbose output. For instance:: $ proot -v 1 true ... proot info: ptrace acceleration (seccomp mode 2) enabled ... But first, be sure it was built with this support:: $ proot -V ... built-in accelerators: process_vm = yes, seccomp_filter = yes ... Release v2.4.1 ============== Fixes ----- + Fix all warnings reported by GCC-4.8 "-Wall -Wextra" and Coverity Prevent 4.5. + Fix Unix sockets path translation for some x86_64 systems. + Make the "kompat" extension (-k option) work again. + Fix spurious "can't delete /tmp/proot-$PID-XXXXX" messages. Release v2.4 ============ New architectures ----------------- + PRoot now works natively on Linux ARM64 systems (a.k.a AArch64). Note that PRoot/AArch64 doesn't support 32-bit binaries yet. + PRoot/x86_64 now supports x32 binaries/rootfs. Fixes ----- + Paths from Unix domain sockets are now translated. For example, it wasn't possible previously to use "tmux" in the guest rootfs if another instance were running in the host rootfs. + When a host path is bound to a nonexistent guest path, PRoot tries to create this latter in the guest rootfs, for some technical reasons. Previously, this "dummy" guest path was created with RWX permissions but this might cause troubles when re-using the rootfs for other purpose. Now, this "dummy" guest path is created with minimal permissions, and it is also possible to avoid its creation by defining the PROOT_DONT_POLLUTE_ROOTFS environment variable. Command-line interface changes ------------------------------ + The directory "/run" is removed from the list of recommended bindings (-B option) because this creates to much conflicts with programs that write in the "/run/var" directory. + The -0 option now makes user's files appear as if they were actually owned by root, and it also fakes the success of any mode changes (chmod* syscalls). This is typically useful to create packages where the files belong to the root user (it's almost always the case). Internal changes ---------------- + PRoot should be even more portable now. For instance, there's no need to worry about syscallee-saved registers anymore. Thanks ------ This release was made possible thanks to, in no special order: Yvan Roux, Jerôme Audu, Heehooman, Yann Droneaud, and James Le Cuirot. See "git log" for details. Release v2.3.1 ============== New feature ----------- + The "fake id0" feature was improved by Rémi Duraffort in order to support privileged write operations in read-only files/directories. Some package managers (Fedora, Debian, ...) relies on this special behavior:: # ls -ld /usr/lib dr-xr-xr-x 22 root root 40960 Jan 2 11:19 /usr/lib/ # install -v something.so /usr/lib/ removed ‘/usr/lib/something.so‘ ‘something.so‘ -> ‘/usr/lib/something.so‘ Fixes ----- + Fix bindings to a guest path that contains a symbolic link. For example when the given guest path ``/var/run/dbus`` is a symbolic link to ``/run/dbus``. + Fix a memory corruption when accessing files in "/proc/self/" Special thanks to Rémi Duraffort for the improved "fake id0" feature and for the bug reports. Release v2.3 ============ This release is intended more specifically to developers and advanced users, it was mostly driven by the requirements of an internal STMicroelectronics project named "Auto-Tuning Optimization Service". New features ------------ + There's now an extension mechanism in PRoot that allows developers to add their own features and/or to use PRoot as a Linux process instrumentation engine. The two following old features were moved to this new extension interface: "-k *string*" and "-0" (respectively: set the kernel release and compatibility level to *string*"; and force some syscalls to behave as if executed by "root"). + It is now possible to execute PRoot under PRoot, well somewhat. Actually the initial instance of PRoot detects that it is being called again and recomputes the configuration for the new process tree. This feature is still experimental and was way harder to implement than expected, however it was worth the effort since it enforced the consistency in PRoot. Just one example among many, in PRoot the "chroot" feature is now really equivalent to the "mount/bind" one, that is, ``chroot path/to/rootfs`` is similar to ``mount --bind path/to/rootfs /``. + The "current working directory" (chdir(2), getcwd(2), ...) is now fully emulated by PRoot. Sadly a minor regression was introduced: even if the current working directory has been removed, getcwd(2) returns a "correct" value. This should be fixed in the next release. Command-line interface changes ------------------------------ + The message "proot info: started/exited" isn't printed by default anymore since it might introduce noise when PRoot is used inside a test-suite that compares outputs. This message was initially added to know whether the guest program has exited immediately. + The "-u" and "-W" options have disappeared. The former wasn't really useful and the latter was definitely useless since the default "current working directory" is "." since v2.1, that means the three examples below are equivalent ("-W" was just an alias to "-b . -w ."):: proot -b . [...] proot -b . -w . [...] proot -W [...] Fixes ----- + The option ``-w .`` is now really equivalent to ``-w $PWD``. + A bug almost impossible to describe here has been fixed, it appeared only when specifying relative bindings, for instance: ``-b .``. Internal changes ---------------- + PRoot now relies on Talloc: a hierarchical, reference counted memory pool system with destructors. It is the core memory allocator used in Samba: http://talloc.samba.org. This is definitely a worthwhile dependency for the sake of development scalability and debuggability. For example, PRoot now has an explicit garbage collector (c.f. ``tracee->ctx``), and the full dynamic memory hierarchy can be printed by sending the USR1 signal to PRoot:: native-shell$ proot --mount=$HOME --mount=/proc --rootfs=./slackware-14/ prooted-shell$ kill -s USR1 $(grep Tracer /proc/self/status | cut -f 2) Tracee 0x6150c0 768 bytes 0 ref' (pid = 22495) talloc_new: ./tracee/tracee.c:97 0x615420 0 bytes 0 ref' $exe 0x61bef0 10 bytes 0 ref' ("/bin/bash") @cmdline 0x61bf60 16 bytes 0 ref' ("/bin/sh", ) /bin/sh 0x61bfd0 8 bytes 0 ref' $glue 0x61bae0 24 bytes 0 ref' ("/tmp/proot-22494-UfGAPh") FileSystemNameSpace 0x615480 32 bytes 0 ref' $cwd 0x61b880 13 bytes 0 ref' ("/home/cedric") Bindings 0x61b970 16 bytes 0 ref' (host) Binding 0x615570 8280 bytes 1 ref' (/home/cedric:/home/cedric) Binding 0x6176a0 8280 bytes 1 ref' (/proc:/proc) Binding 0x6197d0 8280 bytes 1 ref' (/usr/local/proot/slackware-14:/) Bindings 0x61b900 16 bytes 0 ref' (guest) Binding -> 0x6176a0 Binding -> 0x615570 Binding -> 0x6197d0 Release v2.2 ============ + This release brings some critical fixes so an upgrade is highly recommended, especially on x86_64 and Ubuntu. + PRoot is now a lot faster: the speed-up can be up to 50% depending on the kind of application. + PRoot can now mount/bind files anywhere in the guest rootfs, even if the mount point has no parent directory (and/or can't be created). With previous versions of PRoot, that would created kinda black hole in the filesystem hierarchy that might bug some programs like "cpio" or "rpm". For example, with the previous version of PRoot:: $ proot -b /etc/motd:/black/holes/and/revelations proot warning: can't create the guest path (binding) ... proot info: started $ find /black find: `/black: No such file or directory $ cat /black/holes/and/revelations Time has come to make things right -- Matthew Bellamy And now:: $ proot -b /etc/motd:/black/holes/and/revelations proot info: started $ find /black /black /black/holes /black/holes/and /black/holes/and/revelations $ cat /black/holes/and/revelations Time has come to make things right -- Matthew Bellamy + "/run" was added to the list of recommended bindings (-B/-Q). + SH4 and ARM architectures are now officially supported. Thanks ------ Huge thanks to Rémi DURAFFORT for all the tests, bug reports, fixes, and for hosting http://proot.me. Thanks to Thomas P. HIGDON for the advanced investigation on a really tricky bug (red zone corruption). Release v2.1 ============ New features ------------ + PRoot can now emulate some of the syscalls that are available in the kernel release specified by -k but that are missing in the host kernel. This allows the execution of guest programs expecting a kernel newer than the actual one, if you encountered the famous "FATAL: kernel too old" or "qemu: Unsupported syscall" messages. + The current working directory isn't changed anymore if it is still accessible in the guest environment (binding). Fixes ----- + Added support for architectures with no misalignment support (SH4). + Fix support for: link(2), linkat(2), symlink(2), and symlinkat(2). Release v2.0.1 ============== + Fix a compatibility issue with QEMU v1.1 + Fix the initialization of bindings that point to "/proc/self". These problems were reported by Alkino: https://github.com/cedric-vincent/PRoot/issues/3 Release v2.0 ============ New features ------------ + There's now a specific support to handle special symlinks in /proc. As of now, only the following ones are correctly handled: * /proc/self, it was already supported previously but now this is done consistently (i.e. not a hack); * /proc//exe, for any monitored by PRoot; and * /proc//fd/. + The list of supported syscalls was updated, as of Linux 3.4.1. Command-line interface changes ------------------------------ + The path to the guest rootfs can now be specified by the new -r/--rootfs option. This permits the use of shell aliases, for example: $ alias armedslack='proot -Q qemu-arm -r /path/to/armedslack' $ armedslack -v 1 -b ~/arm_cpuinfo:/proc/cpuinfo That wasn't possible previously because the path to the guest rootfs had to be the last option. + The -v/--verbose option now takes a parameter, and a negative integer makes PRoot quiet except on fatal errors. + The -h/--help option now prints a detailed message. + The -V/--version and -h/--help options now exit with success. Fix --- + Return correct errno if a non-final path component isn't a directory nor a symlink. + Fix the insertion of an item in the list of host/guest bindings. Internal changes ---------------- This section is dedicated to PRoot developers. + File-system information is now inherited from a traced process to its children. This permits the emulation of symlinks in /proc/self: cmdline, exe, cwd, root, ... + The execution of QEMU is now fully confined to the virtual rootfs namespace: it now relies on the "mixed-execution" feature, just like a regular host program. Release v1.9 ============ Fixes ----- + Be as transparent as possible with respect to SIGSTOP and SIGTRAP signals. For instance, the Open POSIX Test Suite now reports the same level of success whether it is run under PRoot or not (it depends on the kernel version though). + Ignore terminating signals and kill all tracees on abnormal termination signals (^\, segmentation fault, divide by zero, ...). This ensures no tracee will stay alive without being monitored anymore. + Force utsname.machine to "i686" -- instead of "i386" -- for 32-bit programs running on x86_64 systems. This improves the compatibility with package managers that deduce the current architecture from `uname -m`. + Fix x86_64 support for linkat() and fchownat(). + Fix mixed-execution support, LD_LIBRARY_PATH was defined twice for host programs. Release v1.8.4 ============== New feature ----------- + The -0 option now fakes success on ``chroot("/")``. This feature is required by some guest package managers, like ``pacman`` -- the Arch Linux Package Manager. Fix --- + Nested bindings are now correctly supported. For example with these bindings -- nested from the host point-of-view:: host$ proot -b /:/host-rootfs -b /tmp ... guest$ ln -s /tmp/bar /tmp/foo # ... points to "/tmp/bar" instead of "/host-rootfs/tmp/bar" and with these bindings -- nested from the guest point-of-view:: host$ proot -b /bin -b /usr/bin/find:/bin/find ... guest$ /bin/find # ... works instead of "no such file or directory" Internal changes ---------------- This section is dedicated to PRoot developers. + Functions to compare two pathes (equal, prefix, not comparable, ...) are now available, at last. + The "ignore ELF interpreter" option can be (dis|en)able with the ``PROOT_IGNORE_ELF_INTERPRETER`` environment variable and/or with the ``config.ignore_elf_interpreter`` internal variable. Release v1.8.3 ============== New features ------------ + The -0 option now fakes success on ownership changes. This improves the compatibility with package managers that abort if ``chown(2)`` fails. Note that this is quite limited compared to ``fakeroot``. + Force utsname.machine to "i386" for 32-bit programs running on x86_64 systems. This improves the compatibility with package managers that deduce the current architecture from `uname -m`. Fixes ----- + Fix a regression regarding the concatenation of the ``..`` with a path ending with ``.``. For intance you can now do ``ls foo`` where ``foo`` is a symbolic link to ``/bar/.``. + Don't return an error if the specified size for ``getcwd(2)`` and ``readlink(2)`` is greater than PATH_MAX. Technically the result may likely be shorter than this limit. Release v1.8.2 ============== + This is the first public release of PRoot, it's time to increase its maturity artificially ... Actually it's an homage to Blink 182 ;) + User manual finally published. + PRoot can now *mix* the execution of host programs and the execution of guest programs emulated by QEMU. This is useful to use programs that aren't available initially in the guest environment and to speed up build-time by using cross-compilation tools or any CPU independent program, like interpreters. + Absolute symlinks from bound directories that point to any bound directory are kept consistent: for example, given the host symlink ``/bin/sh -> /bin/bash``, and given the command-line option ``-b /bin:/foo``, the symlink will appeared as ``/foo/sh -> /foo/bash``. + Three command-line options are gone: * ``-p`` (don't block the ptrace syscall) wasn't really useful. * ``-e`` (don't use the ELF interpreter) isn't required anymore. * ``-a`` (disable the ASLR mechanism) is now the default. + Don't complain anymore when parent directories of a *recommended binding* (as enabled by ``-B``, ``-M`` and ``-Q`` options) can't be created. + Support job control under ptrace as introduced in Linux 3.0+. + ``LD_`` environment variables are now passed to the QEMUlated program, not to QEMU itself. It means ``ldd`` works (there's a bug in QEMU/ARM though). + Many fixes and improved compatibility thanks to the Open Build Service instantiated at http://build.opensuse.com + Note: v0.7.1 was an experimental release. Release v0.7.0 ============== + Search the guest program in $PATH relatively to the guest rootfs, for instance you can now just write:: proot /path/to/guest/rootfs/ perl instead of:: proot /path/to/guest/rootfs/ /usr/bin/perl + The command-line interface was re-written from scratch, the only incompatible change is that QEMU options are now separated by spaces:: proot -Q "qemu-arm -g 1234" ... instead of:: proot -Q qemu-arm,-g,1234 ... + New option "-0": force syscalls "get*id" to report identity 0, aka "root". + Many fixes, code refactoring, new testing framework, ... Special thanks to Claire ROBINE for her contribution. Release v0.6.2 ============== + Change the default command from $SHELL to "/bin/sh". The previous behaviour led to an unexpected error -- from user's point-of-view -- when $SHELL didn't exit in the new root file-system. + Fix *partially* support for readlink(2) when mirror pathes are in use. Prior this patch, any symbolic link -- that points to an absolute path which prefix is equal to the host-side of any mirror path -- was bugged. For instance, the command "proot -m /bin:/host $ROOTFS /usr/bin/readlink /usr/bin/ps" returned "/host" instead of "/bin/ps". + Add the option "-V" to print the version then exit. + Be more explicit when a wrong command-line argument is used. + Remove the SIGSEGV help message: it was too confusing to the user. + Use a new shining build-system (again :D). Special thanks go to those contributors: Yves JANIN, Remi Duraffort and Christophe GUILLON. Release v0.6.1 ============== + Add `/tmp` to the list of mirrored paths when using -M. + Fix the ELF interpreter extraction. + Rewrite the build system. Release v0.6 ============ New features ------------ + Added support for "asymmetric" path mirrors. The command-line option for mirrors was extended to support the syntax "-m :" where is the location of the mirror within the alternate rootfs and is the path to the real directory/file. For instance you can now mirror the whole host rootfs in the directory "/hostfs" within the alternate rootfs that way:: proot -m /:/hostfs ... + Added an option to disable ASLR (Address Space Layout Randomization). RHEL4 and Ubuntu 10.04 use an ASLR mechanism that creates conflicts between the layout of QEMU and the layout of the target program. This new option is automatically set when QEMU is used. + Added "/etc/resolv.conf" and $HOME to the list of mirrored paths when using the option -M or -Q. Fixes ----- + Fixed the detranslation of getcwd(2) and readlink(2). + Improved the build compatibility on old/broken distro. + Added support for pthread cancellation when QEMU is used. + Set QEMU's fake argv[0] to the program actually launched, not to the initial script name. + Create the path up to the mirror location to cheat "walking" programs. care-2.2.1/doc/proot/stylesheets/0000755000175000017500000000000012430615044016265 5ustar ivoireivoirecare-2.2.1/doc/proot/stylesheets/website.css0000644000175000017500000000023712430615044020443 0ustar ivoireivoire@import url("website.css"); h1 { color: orange; } #contents a:hover { border-bottom: 2px solid orange; } a { border-bottom: 1px solid orange; } care-2.2.1/doc/proot/stylesheets/cli.xsl0000644000175000017500000001166512430615044017575 0ustar ivoireivoire /* This file is automatically generated from the documentation. EDIT AT YOUR OWN RISK. */ #ifndef PROOT_CLI_H #define PROOT_CLI_H #include "cli/cli.h" #ifndef VERSION #define VERSION "" #endif static int pre_initialize_bindings(Tracee *, const Cli *, size_t, char *const *, size_t); static int post_initialize_command(Tracee *, const Cli *, size_t, char *const *, size_t); static Cli proot_cli = { .version = VERSION, .name = "proot", .pre_initialize_bindings = pre_initialize_bindings, .post_initialize_command = post_initialize_command, .options = { END_OF_OPTIONS, }, }; #endif /* PROOT_CLI_H */ .subtitle = " ", .synopsis = " ", .colophon = " ", .logo = "\ ", static const char *recommended_bindings[] = { NULL, }; static const char *recommended_su_bindings[] = { NULL, }; " ", { .class = " ", .arguments = { { .name = NULL, .separator = '\0', .value = NULL } }, .handler = handle_option_ , .description = " ", .detail = " ", }, * * { .name = " ", .separator = ' ', .value = " " \0', .value = NULL }, static int handle_option_ (Tracee *tracee, const Cli *cli, char *value); care-2.2.1/doc/proot/stylesheets/website.xsl0000644000175000017500000000637212430615044020467 0ustar ivoireivoire <xsl:value-of select="document/title" /> — <xsl:value-of select="document/subtitle" />

    PRoot

    By default XSLTproc converts tags with no content to self-closing tags

    Support

    Feel free to send your questions, bug reports, suggestions, and patchs to the mailing-list or to the forum, but please be sure that your answer isn't in the user manual first.

    Also, Rémi Duraffort has written interesting articles on how to use a foreign Debian rootfs with PRoot in order to build and test VLC on this guest Linux distribution.

    care-2.2.1/doc/proot/stylesheets/rpm-spec.xsl0000644000175000017500000000236212430615044020546 0ustar ivoireivoire %define version v Summary : Version : %{version} Release : 1 License : GPL2+ Group : Applications/System Source : proot-%{version}.tar.gz Buildroot : %(mktemp -ud %{_tmppath}/%{name}-%{version}-%{release}-XXXXXX) Prefix : /usr Name : proot BuildRequires: libtalloc-devel %if 0%{?suse_version} >= 1210 || 0%{?fedora_version} >= 15 BuildRequires: glibc-static %endif %if !0%{?suse_version} != 0 BuildRequires: which %endif %description %prep %setup -n proot-%{version} %build make -C src %install make -C src install PREFIX=%{buildroot}/%{prefix} install -D doc/proot/man.1 %{buildroot}/%{_mandir}/man1/proot.1 %check env LD_SHOW_AUXV=1 true cat /proc/cpuinfo ./src/proot -V ./src/proot -v 1 true make -C tests %clean rm -rf %{buildroot} %files %defattr(-,root,root) %{prefix}/bin/proot %doc %{_mandir}/man1/proot.1* %doc COPYING %doc doc/* %changelog care-2.2.1/doc/proot/roadmap.txt0000644000175000017500000001274112430615044016102 0ustar ivoireivoire========= Roadmap ========= PRoot v4.1: custom loader ========================= Highlight --------- Currently, PRoot relies on the loader embedded in the GNU dynamic linker, sadly this latter suffers from a couple of bugs and limitations that avoid some programs to run correctly under PRoot (and CARE). For examples: * programs that use constructors might crash: a typical example is QEMU. There's a workaround in PRoot for QEMU only. * programs that use "rpath" and "symlinks" might not start: a typical example is Java. There's no workaround in PRoot, the user has to by-pass the symlink manually. * programs that read the processes name are confused: typical examples are ps and top. There's no workaround in PRoot. * programs that expect a kernel newer than the actual one (the infamous "FATAL: kernel too old" error). The current workaround in PRoot (-k option) is not enough. * programs not monitored by PRoot get a wrong program name when reading /proc/$PID/cmdline, like "ld-linux.so.2" All these bugs and limitations can be fixed by writing a custom loader. PRoot v5.0: FS cache ==================== Highlight --------- One core feature of PRoot is the path translation. This mechanism heavily relies on "stat", sadly this syscall is quite slow on some FS (like NFS). The idea is to cache the results of the path translation mechanism to avoid the use of "stat" as much as possible in order to speed-up PRoot. The internal structure of this FS cache could also be used to emulate the "getdents" syscall in order to add or hide entries. TODO: insert roadmap sent to CM. Not yet scheduled ================= Fixes ----- * Mention "container" in the documentation. * Fix ptrace emulation support on ARM. * Fix support for GDB v/fork & execve catchpoints. * Add support for unimplemented ptrace requests. * Add new hidden option "PROOT_MIN_HEAP_SIZE". * Replace tracee->reconf.paths with tracee->reconf.env + getenv2() * Fix ``mkdir foo; cd foo; rmdir ../foo; readlink /proc/self/cwd``. * Fix remaining bugs in sub-reconfiguration support:: ./src/proot -B /usr/local/proot/centos-5-x86 make -C tests/ * Forbid rename/unlink on a mount point: $ mv mount_point elsewhere mv: cannot move "mount_point" to "elsewhere": Device or resource busy * Add support for the string $ORIGIN (or equivalently ${ORIGIN}) in an rpath specification * Add support for /etc/ld.so.preload and /etc/ld.so.conf[.d] in mixed-mode. * Fix ``proot -k 1.2.3 proot -k 2.4.6 -k 3.4.5 uname -r | grep 3.4.5``. * Don't use the same prefix for glued path:: $ proot -b /etc/fstab -b /bin/readdir:/bin/readdir -r /tmp/empty-rootfs [...] proot info: binding = /tmp/proot-6738-CMr1hE:/bin proot info: binding = /tmp/proot-6738-CMr1hE:/etc [...] $ readdir /bin DT_DIR .. DT_DIR . DT_REG readdir DT_REG fstab $ readdir /etc DT_DIR .. DT_DIR . DT_REG readdir DT_REG fstab Features -------- * Start a login shell (``env -i sh -l``) instead of ``/bin/sh``. * Use PTRACE_O_EXITKILL if possible. * Do not seccomp-trace any syscalls when there are no bindings. This should improve performance of ATOS extensions a alot. * Add a mechanism to add new [fake] entries in /proc, for instance ``/proc/proot/config``. * Add a way to get the reverse translation of a path:: proot [bindings] -x /usr/local/bin or maybe something like that:: proot [bindings] readlink /proc/proot/detranslate/usr/local/bin * Make ``mount --bind`` change the tracee's configuration dynamically. * Make ``chroot`` change the tracee's configuration dynamically (not only of ``/``). * Add support for a special environment variable to add paths dynamically to the host LD_LIBRARY_PATH ("EXTRA_HOST_LD_LIBRARY_PATH"). * Add a "blind" mode where: * unreadable executable can be executed:: proot mount: OK (rwsr-xr-x) proot ping: failed (rws--x--x) * unreadable directory can be accessed * Add command-line interface to set environment variables. Rename push_env() in change_env() and enhance it to support the "unset" feature. * Add support for coalesced options, for instance ``proot -eM`` * Allow a per-module verbose level * Emulate chown, chmod, and mknod when -0 (fake_id0) is enabled. Documentation ------------- * Explain bindings aren't exclusive, i.e. "-b /tmp:/foo" doesn't invalidate "-b /tmp:/bar". * Explain why PRoot does not work with setuid programs * Document the compatibility with the old command-line interface (without "-r"):: proot [options] [args...] Misc. ----- * Replace "readlink(XXX, path, PATH_MAX)" with "readlink(XXX, path, PATH_MAX - 1)" * read_string should return -ENAMETOOLONG when length(XXX) >= max_size * Check (in ld.so sources) if more than one RPATH/RUNPATH entry is allowed. * Ensure tracees' clone flags has CLONE_PTRACE & ~CLONE_UNTRACED. * Add a stealth mode where over-the-stack content is restored. * Try Trinity/Scrashme (syscall fuzzers) against PRoot Performance ----------- * prefetch_mem(): cache write-through memory access (read_string, fetch_args). * Fallback to /proc//mem when process_vm_readv() isn't available. * Add a "multi-process" mode where there's one fork of PRoot per monitored process. Each time a new_tracee structure is created, PRoot forks itself. Be sure that the tracer of this new process really is the new forked PRoot! (Thanks Yves for this comment) care-2.2.1/doc/proot/manual.txt0000644000175000017500000004453012430615044015735 0ustar ivoireivoire======= PRoot ======= ------------------------------------------------------------------------- ``chroot``, ``mount --bind``, and ``binfmt_misc`` without privilege/setup ------------------------------------------------------------------------- :Date: 2014-09-15 :Version: 4.0.3 :Manual section: 1 Synopsis ======== **proot** [*option*] ... [*command*] Description =========== PRoot is a user-space implementation of ``chroot``, ``mount --bind``, and ``binfmt_misc``. This means that users don't need any privileges or setup to do things like using an arbitrary directory as the new root filesystem, making files accessible somewhere else in the filesystem hierarchy, or executing programs built for another CPU architecture transparently through QEMU user-mode. Also, developers can use PRoot as a generic Linux process instrumentation engine thanks to its extension mechanism, see CARE_ for an example. Technically PRoot relies on ``ptrace``, an unprivileged system-call available in every Linux kernel. The new root file-system, a.k.a *guest rootfs*, typically contains a Linux distribution. By default PRoot confines the execution of programs to the guest rootfs only, however users can use the built-in *mount/bind* mechanism to access files and directories from the actual root file-system, a.k.a *host rootfs*, just as if they were part of the guest rootfs. When the guest Linux distribution is made for a CPU architecture incompatible with the host one, PRoot uses the CPU emulator QEMU user-mode to execute transparently guest programs. It's a convenient way to develop, to build, and to validate any guest Linux packages seamlessly on users' computer, just as if they were in a *native* guest environment. That way all of the cross-compilation issues are avoided. PRoot can also *mix* the execution of host programs and the execution of guest programs emulated by QEMU user-mode. This is useful to use host equivalents of programs that are missing from the guest rootfs and to speed up build-time by using cross-compilation tools or CPU-independent programs, like interpreters. It is worth noting that the guest kernel is never involved, regardless of whether QEMU user-mode is used or not. Technically, when guest programs perform access to system resources, PRoot translates their requests before sending them to the host kernel. This means that guest programs can use host resources (devices, network, ...) just as if they were "normal" host programs. .. _CARE: http://reproducible.io Options ======= The command-line interface is composed of two parts: first PRoot's options (optional), then the command to launch (``/bin/sh`` if not specified). This section describes the options supported by PRoot, that is, the first part of its command-line interface. Regular options --------------- -r path, --rootfs=path Use *path* as the new guest root file-system, default is ``/``. The specified *path* typically contains a Linux distribution where all new programs will be confined. The default rootfs is ``/`` when none is specified, this makes sense when the bind mechanism is used to relocate host files and directories, see the ``-b`` option and the ``Examples`` section for details. It is recommended to use the ``-R`` or ``-S`` options instead. -b path, --bind=path, -m path, --mount=path Make the content of *path* accessible in the guest rootfs. This option makes any file or directory of the host rootfs accessible in the confined environment just as if it were part of the guest rootfs. By default the host path is bound to the same path in the guest rootfs but users can specify any other location with the syntax: ``-b *host_path*:*guest_location*``. If the guest location is a symbolic link, it is dereferenced to ensure the new content is accessible through all the symbolic links that point to the overlaid content. In most cases this default behavior shouldn't be a problem, although it is possible to explicitly not dereference the guest location by appending it the ``!`` character: ``-b *host_path*:*guest_location!*``. -q command, --qemu=command Execute guest programs through QEMU as specified by *command*. Each time a guest program is going to be executed, PRoot inserts the QEMU user-mode *command* in front of the initial request. That way, guest programs actually run on a virtual guest CPU emulated by QEMU user-mode. The native execution of host programs is still effective and the whole host rootfs is bound to ``/host-rootfs`` in the guest environment. -w path, --pwd=path, --cwd=path Set the initial working directory to *path*. Some programs expect to be launched from a given directory but do not perform any ``chdir`` by themselves. This option avoids the need for running a shell and then entering the directory manually. -v value, --verbose=value Set the level of debug information to *value*. The higher the integer *value* is, the more detailed debug information is printed to the standard error stream. A negative *value* makes PRoot quiet except on fatal errors. -V, --version, --about Print version, copyright, license and contact, then exit. -h, --help, --usage Print the version and the command-line usage, then exit. Extension options ----------------- The following options enable built-in extensions. Technically developers can add their own features to PRoot or use it as a Linux process instrumentation engine thanks to its extension mechanism, see the sources for further details. -k string, --kernel-release=string Make current kernel appear as kernel release *string*. If a program is run on a kernel older than the one expected by its GNU C library, the following error is reported: "FATAL: kernel too old". To be able to run such programs, PRoot can emulate some of the features that are available in the kernel release specified by *string* but that are missing in the current kernel. -0, --root-id Make current user appear as "root" and fake its privileges. Some programs will refuse to work if they are not run with "root" privileges, even if there is no technical reason for that. This is typically the case with package managers. This option allows users to bypass this kind of limitation by faking the user/group identity, and by faking the success of some operations like changing the ownership of files, changing the root directory to ``/``, ... Note that this option is quite limited compared to ``fakeroot``. -i string, --change-id=string Make current user and group appear as *string* "uid:gid". This option makes the current user and group appear as *uid* and *gid*. Likewise, files actually owned by the current user and group appear as if they were owned by *uid* and *gid* instead. Note that the ``-0`` option is the same as ``-i 0:0``. Alias options ------------- The following options are aliases for handy sets of options. -R path Alias: ``-r *path*`` + a couple of recommended ``-b``. Programs isolated in *path*, a guest rootfs, might still need to access information about the host system, as it is illustrated in the ``Examples`` section of the manual. These host information are typically: user/group definition, network setup, run-time information, users' files, ... On all Linux distributions, they all lie in a couple of host files and directories that are automatically bound by this option: * /etc/host.conf * /etc/hosts * /etc/hosts.equiv * /etc/mtab * /etc/netgroup * /etc/networks * /etc/passwd * /etc/group * /etc/nsswitch.conf * /etc/resolv.conf * /etc/localtime * /dev/ * /sys/ * /proc/ * /tmp/ * $HOME * *path* -S path Alias: ``-0 -r *path*`` + a couple of recommended ``-b``. This option is useful to safely create and install packages into the guest rootfs. It is similar to the ``-R`` option expect it enables the ``-0`` option and binds only the following minimal set of paths to avoid unexpected changes on host files: * /etc/host.conf * /etc/hosts * /etc/nsswitch.conf * /etc/resolv.conf * /dev/ * /sys/ * /proc/ * /tmp/ * $HOME * *path* Exit Status =========== If an internal error occurs, ``proot`` returns a non-zero exit status, otherwise it returns the exit status of the last terminated program. When an error has occurred, the only way to know if it comes from the last terminated program or from ``proot`` itself is to have a look at the error message. Files ===== PRoot reads links in ``/proc//fd/`` to support `openat(2)`-like syscalls made by the guest programs. Examples ======== In the following examples the directories ``/mnt/slackware-8.0`` and ``/mnt/armslack-12.2/`` contain a Linux distribution respectively made for x86 CPUs and ARM CPUs. ``chroot`` equivalent --------------------- To execute a command inside a given Linux distribution, just give ``proot`` the path to the guest rootfs followed by the desired command. The example below executes the program ``cat`` to print the content of a file:: proot -r /mnt/slackware-8.0/ cat /etc/motd Welcome to Slackware Linux 8.0 The default command is ``/bin/sh`` when none is specified. Thus the shortest way to confine an interactive shell and all its sub-programs is:: proot -r /mnt/slackware-8.0/ $ cat /etc/motd Welcome to Slackware Linux 8.0 ``mount --bind`` equivalent --------------------------- The bind mechanism enables one to relocate files and directories. This is typically useful to trick programs that perform access to hard-coded locations, like some installation scripts:: proot -b /tmp/alternate_opt:/opt $ cd to/sources $ make install [...] install -m 755 prog "/opt/bin" [...] # prog is installed in "/tmp/alternate_opt/bin" actually As shown in this example, it is possible to bind over files not even owned by the user. This can be used to *overlay* system configuration files, for instance the DNS setting:: ls -l /etc/hosts -rw-r--r-- 1 root root 675 Mar 4 2011 /etc/hosts :: proot -b ~/alternate_hosts:/etc/hosts $ echo '1.2.3.4 google.com' > /etc/hosts $ resolveip google.com IP address of google.com is 1.2.3.4 $ echo '5.6.7.8 google.com' > /etc/hosts $ resolveip google.com IP address of google.com is 5.6.7.8 Another example: on most Linux distributions ``/bin/sh`` is a symbolic link to ``/bin/bash``, whereas it points to ``/bin/dash`` on Debian and Ubuntu. As a consequence a ``#!/bin/sh`` script tested with Bash might not work with Dash. In this case, the binding mechanism of PRoot can be used to set non-disruptively ``/bin/bash`` as the default ``/bin/sh`` on these two Linux distributions:: proot -b /bin/bash:/bin/sh [...] Because ``/bin/sh`` is initially a symbolic link to ``/bin/dash``, the content of ``/bin/bash`` is actually bound over this latter:: proot -b /bin/bash:/bin/sh $ md5sum /bin/sh 089ed56cd74e63f461bef0fdfc2d159a /bin/sh $ md5sum /bin/bash 089ed56cd74e63f461bef0fdfc2d159a /bin/bash $ md5sum /bin/dash 089ed56cd74e63f461bef0fdfc2d159a /bin/dash In most cases this shouldn't be a problem, but it is still possible to strictly bind ``/bin/bash`` over ``/bin/sh`` -- without dereferencing it -- by specifying the ``!`` character at the end:: proot -b '/bin/bash:/bin/sh!' $ md5sum /bin/sh 089ed56cd74e63f461bef0fdfc2d159a /bin/sh $ md5sum /bin/bash 089ed56cd74e63f461bef0fdfc2d159a /bin/bash $ md5sum /bin/dash c229085928dc19e8d9bd29fe88268504 /bin/dash ``chroot`` + ``mount --bind`` equivalent ---------------------------------------- The two features above can be combined to make any file from the host rootfs accessible in the confined environment just as if it were initially part of the guest rootfs. It is sometimes required to run programs that rely on some specific files:: proot -r /mnt/slackware-8.0/ $ ps -o tty,command Error, do this: mount -t proc none /proc works better with:: proot -r /mnt/slackware-8.0/ -b /proc $ ps -o tty,command TT COMMAND ? -bash ? proot -b /proc /mnt/slackware-8.0/ ? /lib/ld-linux.so.2 /bin/sh ? /lib/ld-linux.so.2 /usr/bin/ps -o tty,command Actually there's a bunch of such specific files, that's why PRoot provides the option ``-R`` to bind automatically a pre-defined list of recommended paths:: proot -R /mnt/slackware-8.0/ $ ps -o tty,command TT COMMAND pts/6 -bash pts/6 proot -R /mnt/slackware-8.0/ pts/6 /lib/ld-linux.so.2 /bin/sh pts/6 /lib/ld-linux.so.2 /usr/bin/ps -o tty,command ``chroot`` + ``mount --bind`` + ``su`` equivalent ------------------------------------------------- Some programs will not work correctly if they are not run by the "root" user, this is typically the case with package managers. PRoot can fake the root identity and its privileges when the ``-0`` (zero) option is specified:: proot -r /mnt/slackware-8.0/ -0 # id uid=0(root) gid=0(root) [...] # mkdir /tmp/foo # chmod a-rwx /tmp/foo # echo 'I bypass file-system permissions.' > /tmp/foo/bar # cat /tmp/foo/bar I bypass file-system permissions. This option is typically required to create or install packages into the guest rootfs. Note it is *not* recommended to use the ``-R`` option when installing packages since they may try to update bound system files, like ``/etc/group``. Instead, it is recommended to use the ``-S`` option. This latter enables the ``-0`` option and binds only paths that are known to not be updated by packages:: proot -S /mnt/slackware-8.0/ # installpkg perl.tgz Installing package perl... ``chroot`` + ``mount --bind`` + ``binfmt_misc`` equivalent ---------------------------------------------------------- PRoot uses QEMU user-mode to execute programs built for a CPU architecture incompatible with the host one. From users' point-of-view, guest programs handled by QEMU user-mode are executed transparently, that is, just like host programs. To enable this feature users just have to specify which instance of QEMU user-mode they want to use with the option ``-q``:: proot -R /mnt/armslack-12.2/ -q qemu-arm $ cat /etc/motd Welcome to ARMedSlack Linux 12.2 The parameter of the ``-q`` option is actually a whole QEMU user-mode command, for instance to enable its GDB server on port 1234:: proot -R /mnt/armslack-12.2/ -q "qemu-arm -g 1234" emacs PRoot allows one to mix transparently the emulated execution of guest programs and the native execution of host programs in the same file-system namespace. It's typically useful to extend the list of available programs and to speed up build-time significantly. This mixed-execution feature is enabled by default when using QEMU user-mode, and the content of the host rootfs is made accessible through ``/host-rootfs``:: proot -R /mnt/armslack-12.2/ -q qemu-arm $ file /bin/echo [...] ELF 32-bit LSB executable, ARM [...] $ /bin/echo 'Hello world!' Hello world! $ file /host-rootfs/bin/echo [...] ELF 64-bit LSB executable, x86-64 [...] $ /host-rootfs/bin/echo 'Hello mixed world!' Hello mixed world! Since both host and guest programs use the guest rootfs as ``/``, users may want to deactivate explicitly cross-filesystem support found in most GNU cross-compilation tools. For example with GCC configured to cross-compile to the ARM target:: proot -R /mnt/armslack-12.2/ -q qemu-arm $ export CC=/host-rootfs/opt/cross-tools/arm-linux/bin/gcc $ export CFLAGS="--sysroot=/" # could be optional indeed $ ./configure; make As with regular files, a host instance of a program can be bound over its guest instance. Here is an example where the guest binary of ``make`` is overlaid by the host one:: proot -R /mnt/armslack-12.2/ -q qemu-arm -b /usr/bin/make $ which make /usr/bin/make $ make --version # overlaid GNU Make 3.82 Built for x86_64-slackware-linux-gnu It's worth mentioning that even when mixing the native execution of host programs and the emulated execution of guest programs, they still believe they are running in a native guest environment. As a demonstration, here is a partial output of a typical ``./configure`` script:: checking whether the C compiler is a cross-compiler... no Downloads ========= PRoot ----- The latest release of PRoot is packaged on http://packages.proot.me and sources are hosted on http://github.proot.me. It is also available as highly compatible static binaries: * for x86_64: http://static.proot.me/proot-x86_64 * for x86: http://static.proot.me/proot-x86 * for ARM: http://static.proot.me/proot-arm * other architectures: on demand. Rootfs ------ Here follows a couple of URLs where some rootfs archives can be freely downloaded. Note that ``mknod`` errors reported by ``tar`` when extracting these archives can be safely ignored since special files are typically bound (see ``-R`` option for details). * http://download.openvz.org/template/precreated/ * https://images.linuxcontainers.org/images/ * http://distfiles.gentoo.org/releases/ * http://cdimage.ubuntu.com/ubuntu-core/releases/ * http://archlinuxarm.org/developers/downloads Technically such rootfs archive can be created by running the following command on the expected Linux distribution:: tar --one-file-system --create --gzip --file my_rootfs.tar.gz / QEMU user-mode -------------- QEMU user-mode is required only if the guest rootfs was made for a CPU architecture incompatible with the host one, for instance when using a ARM rootfs on a x86_64 computer. This package can be installed either from http://qemu.proot.me or from the host package manager under the name of "qemu-user" on most Linux distro. In case one would like to build QEMU user-mode from sources, the ``--enable-linux-user`` option has to be specified to the ``./configure`` script. See Also ======== chroot(1), mount(8), binfmt_misc, ptrace(2), qemu(1), sb2(1), bindfs(1), fakeroot(1), fakechroot(1) Colophon ======== Visit http://proot.me for help, bug reports, suggestions, patches, ... Copyright (C) 2014 STMicroelectronics, licensed under GPL v2 or later. :: _____ _____ ___ | __ \ __ \_____ _____| |_ | __/ / _ \/ _ \ _| |__| |__|__\_____/\_____/\____| care-2.2.1/doc/proot/rpm-spec0000644000175000017500000010722612430615044015372 0ustar ivoireivoire%define version v4.0.3 Summary : chroot, mount --bind, and binfmt_misc without privilege/setup Version : %{version} Release : 1 License : GPL2+ Group : Applications/System Source : proot-%{version}.tar.gz Buildroot : %(mktemp -ud %{_tmppath}/%{name}-%{version}-%{release}-XXXXXX) Prefix : /usr Name : proot BuildRequires: libtalloc-devel %if 0%{?suse_version} >= 1210 || 0%{?fedora_version} >= 15 BuildRequires: glibc-static %endif %if !0%{?suse_version} != 0 BuildRequires: which %endif %description PRoot is a user-space implementation of chroot, mount --bind, and binfmt_misc. This means that users don't need any privileges or setup to do things like using an arbitrary directory as the new root filesystem, making files accessible somewhere else in the filesystem hierarchy, or executing programs built for another CPU architecture transparently through QEMU user-mode. Also, developers can use PRoot as a generic Linux process instrumentation engine thanks to its extension mechanism, see CARE for an example. Technically PRoot relies on ptrace, an unprivileged system-call available in every Linux kernel. %prep %setup -n proot-%{version} %build make -C src %install make -C src install PREFIX=%{buildroot}/%{prefix} install -D doc/proot/man.1 %{buildroot}/%{_mandir}/man1/proot.1 %check env LD_SHOW_AUXV=1 true cat /proc/cpuinfo ./src/proot -V ./src/proot -v 1 true make -C tests %clean rm -rf %{buildroot} %files %defattr(-,root,root) %{prefix}/bin/proot %doc %{_mandir}/man1/proot.1* %doc COPYING %doc doc/* %changelog * Mon Sep 15 2014 Cédric VINCENT Release v4.0.3 ============== + Heap emulation is disabled when a "suspicious" call to brk(2) is actually legit, as it might be the case when launching the very first program. + The "-0" and "-S" options ("root" identity emulation) now fake success of mknodat(2), as it was the case for mknod(2) previously. This missing feature was revealed by the AArch64 port. + The "-k" option (kernel compatibility emulation) now works on Linux/AArch64. Thanks to Rémi Duraffort for the bug reports and for his LAVA testing platform! Release v4.0.2 ============== + Fix how the very first program is launched by PRoot. Previously, argv[0] was not preserved when the very first program was launched through a symbolic link. This old behavior used to bug programs like Busybox and python-exec. Thanks to "hhm", Ivailo "fluxer" Monev, and Joakim Tjernlund for the bug reports. + Fix renameat(2) sysexit support. There was a bug in PRoot that was exposed by the Aarch64 (a.k.a arm64) port only but that might affect other architectures. + Fix build for AArch64. Thanks to Rémi Duraffort for the patches and for the Debian/arm64 testing platform. + Fix support for "long" socket paths. These can only be 108 bytes long; this limit might be easily reached with PRoot since the path to the rootfs is always prepended. The solution was to automatically bind this long path to a shorter path. This bug was exposed by LibreOffice and Yocto's pseudo. Thanks to Christophe Guillon for the bug report. Release v4.0.1 ============== + Fix a couple of portability issues in the testsuite. Thanks to Rémi Duraffort for all the tests he made on his instance of Linaro LAVA. + Set $PWD to the value specified by the -w option, otherwise Bash pwd builtin might be confused under some specific circumstances. Thanks to Jérémy Bobbio for the bug report. + Fix support for accessat and fchmodat syscalls: they have only three parameters, not four. This bug was exposed by Gentoo's sandbox: proot -S gentoo-amd64-hardened+nomultilib-rootfs emerge util-linux Release v4.0.0 ============== Highlights ---------- + It is now possible to use GDB, Strace, or any other program based on "ptrace" under PRoot. This was not the case previously because it is not possible to stack ptracers on Linux, so an emulation layer was developed in order to bypass this limitation. This has required a lot of changes in PRoot, hence the major number version bumping. It was mostly tested on x86_64, and partially tested on x86 and ARM. This ptrace emulation support is still experimental, and there are a couple of known issues, but feel free to report unexpected behaviors if you need a fix. + A new command-line option is available: "-S". It is similar to the "-R" option expect it enables the "-0" option and binds only a minimal set of paths that are known to not be updated by package installations, to avoid unexpected changes on host files. This option is useful to safely create and install packages into the guest rootfs. For example: $ proot -S ubuntu-14.04-rootfs/ apt-get install samba or: $ proot -S ubuntu-14.04-rootfs/ # apt-get install samba If "-0 -R" is used instead of "-S", the same command fails since it tries to update "/etc/group", which is bound to the host system and is not writable (assuming PRoot is ran without privileges): $ proot -0 -R ubuntu-14.04-rootfs/ # apt-get install samba [...] Adding group `sambashare' (GID 105) ... Permission denied + The fake_id0 extension can now fake any user and group identifiers. That means, when "-0" is specified, PRoot-ed processes can change their real, effective and saved identifiers, with respect to the rules described in setuid, setfsuid, setreuid, setresuid, and setfsuid manuals. Also, the new command-line option "-i" was added to change explicitly the identifiers to the specified values. This option will be used by CARE to re-execute with the same initial identifiers, but it could also be useful to threaten your teammates ;). Note that the "-0" option is actually the same as "-i 0:0". + The old command-line interface is not supported anymore. That means it is now impossible to specify the path to the guest rootfs without using -r or -R. Also, -Q and -B options are definitively gone, instead the -R option must be specified, respectively with and without -q. See PRoot v3.1 release notes for details. Fixes ----- + getcwd(2) and chdir(2) now return the correct error code when, respectively, the current directory does not exist anymore and the target directory doesn't have the "search" permission. + Named file descriptors (ie. links in /proc//fd/*) are not dereferenced anymore since they may point to special objects like pipes, sockets, inodes, ... Such objects do not exist on the file-system name-space, so dereferencing them used to cause unexpected errors. + Extensions now see every component of canonicalized paths. An optimization in the canonicalization loop used to skip the first part of a path if it was known to be already canonicalized, sadly this short-cut may confuse some extensions, like -0. + Temporary files and directories created by PRoot for its own purpose are now automatically deleted when PRoot exits. Miscellaneous ------------- + PRoot does not rely on GCC C extensions anymore, like nested functions. That means its stack does not have to be executable (this is required for hardened Linux systems), and it can now be compiled with Clang. + The ASLR (Address Space Layout Randomization) is not disabled anymore, and the heap is now emulated on all architectures. Internal changes ---------------- This section is dedicated to developers. + PRoot now remembers the parent of all tracees, it is similar to a traced process tree. This was required for the ptrace emulation support, but this could be useful to some extensions. + It is now possible to restart a tracee with any ptrace restart mode: single-step, single-block, ... + Functions {peek,poke}_mem were replaced with functions {peek,poke}_{,u}int{8,16,32,64}. These new functions performs type conversion and fetch only the necessary amount of data in target tracee's memory to avoid invalid accesses. + There is a new interface to handle ELF auxiliary vectors. See ptrace emulation, kompat and fake_id0 extensions for usage examples. + There is a new interface to create temporary files and directories that are automatically deleted on exit. See CARE extension, glue and auxv support for usage examples. + When built with GCC function instrumentation support, PRoot prints the currently called function on standard error stream (stderr). Thanks ------ Thanks go to Stephen McCamant, Oren Tirosh, Jérôme Audu, and Carlos Hernan Prada Rojas for their bug reports and tests; and to Rémi Duraffort for his contributions. Release v3.2.2 ============== + Remove a useless memory layout constraint on x86_64 that bugs some programs like java and or qemu. + It is now possible to launch the initial program from a relative path without specifying the "./" prefix, for example: $ proot path/to/program + Don't discard fcntl(F_DUPFD_CLOEXEC) systematically when the kompat extension is enabled (-k option). + Don't use syscalls that require Linux >= 2.6.16 anymore. Release v3.2.1 ============== + Make ptrace/seccomp even more portable on Ubuntu. Thanks to Maxence Dalmais for the bug report and tests. Release v3.2 ============ This release was mostly driven by the requirements of "CARE", a new project based on PRoot that will be released publicly soon on http://reproducible.io. For information, "CARE" is the short for "Comprehensive Archiver for Reproducible Execution". Highlights ---------- + Many bugs exposed by a couple of static code analyzers (Coverity, Clang, ...) and some test-suites (Linux Test Project, libuv, ...) are now fixed. + The "kompat" extension ("-k" option) can now emulate most of the kernel features that would be required by the guest system but that are not available on the host kernel. For example, it can now make programs from Ubuntu 13.04 64-bit run on RedHat 5 64-bit without any further tweaks: rh5-64$ proot -k 3.8 -R ubuntu-13.04-64bit/ ... + On ARM and x86_64, the heap segment is now emulated with a regular memory mapping to ensure this former always exists. This was required because some kernels might put a non-fixed memory mapping right after the regular heap when using some GNU ELF interpreters (ld.so) as loaders. Without the heap segment emulation, some programs like Bash would crash because the heap can't grow anymore: bash: xmalloc: locale.c:73: cannot allocate 2 bytes (0 bytes allocated) Miscellaneous ------------- + When using the "-R" option, the path to the guest rootfs is now bound into the guest rootfs itself. This is required to run programs that search for their DSOs in /proc/self/maps, like VLC for instance. + When using the "-v" option with a level greater than 2, syscalls are now printed as strings instead of numbers, à la strace: $ proot -v 3 true [...] proot info: pid 29847: sysenter start: mmap(0x0, 0x2d141, 0x1, 0x2, 0x3, 0x0) [...] [...] + The article about the migration from ScratchBox2 is now publicly available: https://github.com/cedric-vincent/PRoot/blob/v3.2/doc/articles/howto_migrate_from_scratchbox2.txt Internal changes ---------------- + Tools based on PRoot (CARE, DepsTracker, ATOS, ...) can now easily replace the original command-line interface with their own command-line interface. + It is now possible to chain forged syscalls to a regular syscall. Search for "register_chained_syscall" in the sources for details. + A couple of new helpers are now visible from the extensions. Thanks ------ + Bug reports and tests: Corbin Champion, Maxence Dalmais, and Nicolas Cornu. + Static code analysis: Antoine Moynault and Christophe Guillon. + Patches: Rémi Duraffort. + Unexpected hint: Christophe Monat :) Release v3.1 ============ Command-line interface changes ------------------------------ + The initial command is not search in "." anymore, unless the "./" prefix is specified or unless "." is in $PATH, as expected. + The "-B" and "-Q" options are obsoleted by the new "-R" option. This latter is equivalent to "-B -r", as there was actually no point at using the "-B" option without "-r". + A warning is now emitted when the rootfs is specified à la chroot(1), that is, without using "-r" or "-R". The old command-line interface is not documented anymore, but it will be still supported for a couple of releases. Although, users are strongly encouraged to switch to the new one: ====================== ================= old CLI new CLI ====================== ================= proot rootfs proot -r rootfs proot -B rootfs proot -R rootfs proot -B -r rootfs proot -R rootfs proot -Q qemu rootfs proot -R rootfs -q qemu proot -Q qemu -r rootfs proot -R rootfs -q qemu ======================= ======================= Extensions ---------- + The "kompat" extension ("-k" option) has been greatly enhanced. For example, it can now make programs from Ubuntu 13.04 32-bit run on RedHat 5 64-bit: rh5-64$ proot -k 3.8 -R ubuntu-13.04-32bit/ ... + The "fake id0" extension ("-0" option) handles more syscalls: mknod(2), capset(2), setxattr(2), setresuid(2), setresgid(2), getresuid(2), and getresgid(2). Miscellaneous ------------- + PRoot is now compiled with large file-system support (LFS), this make it works with 64-bit file-systems (eg. CIFS) on 32-bit platforms. + The special symbolic link "/proc/self/root" now points to the guest rootfs, that is, to the path specified by "-r" or "-R". Just like with chroot(2), this symlink may be broken as the referenced host path likely does not exist in the guest rootfs. Although, this symlink is typically used to know if a process is under a chroot-ed environment. + Under QEMU, LD_LIBRARY_PATH is not clobbered anymore when a guest program is launched by a host program. + When seccomp-filter is enabled, this release is about 8% faster than the previous one. + A couple of bugs reported by Scan Coverity are fixed. Thanks ------ Special thanks to Stephan Hadamik, Jérôme Audu, and Rémi Duraffort for their valuable help. Release v3.0.2 ============== + Fix the search of the initial command: when the initial command is a symbolic link, PRoot has to dereference it in guest namespace, not in the host one. + Return error code EACCESS instead of EISDIR when trying to execute a directory. Some programs, such as "env", behave differently with respect to this error code. For example: ### setup $ mkdir -p /tmp/foo/python $ export PATH=/tmp/foo:$PATH ### before (PRoot v2.3 ... v3.0.1) before$ proot env python env: python: Is a directory ### now (PRoot v3.0.2 ...) $ proot env python Python 2.7.5 (default, May 29 2013, 02:28:51) [GCC 4.8.0] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> Release v3.0.1 ============== Fix support for bindings where the guest path is explicitly not dereferenced. Be careful, the syntax has changed: before$ proot -b /bin/bash:!/bin/sh now$ proot -b /bin/bash:/bin/sh! Release v3.0 ============ New features ------------ + PRoot can now use the kernel feature named "seccomp-filter", a.k.a "seccomp mode 2", to improve its own performance significantly. For examples on my workstation, the tables below show the time overhead induced by PRoot compared to a native execution: - when generating the Perl 5.16.1 package: =============== =========== ========== command seccomp off seccomp on =============== =========== ========== ./configure.gnu 75% 25% make -j4 70% 45% make -j4 check 25% 9% =============== =========== ========== - when generating the Coreutils 8.19 package: =============== =========== ========== command seccomp off seccomp on =============== =========== ========== ./configure 80% 33% make -j4 75% 33% make -j4 check 80% 8% =============== =========== ========== + It is now possible to explicitly not dereference the guest location of a binding by specifying ``!`` as the first character. For instance:: proot -b /bin/bash:!/bin/sh will not overlay ``/bin/dash`` when this latter is pointed to by ``/bin/sh`` (it's typically the case on Ubuntu and Debian). Fix --- + The initial command is not search in $PATH anymore when it starts with ``/`` or ``./``, and it doesn't exist. For instance:: $ rm test $ proot ./test proot warning: './test not found (root = /, cwd = /usr/local/cedric/git/proot) proot error: see `proot --help` or `man proot`. Thanks ------ Many thanks to Will Drewry and Indan Zupancic, who made possible to accelerate PTRACE_SYSCALL with seccomp-filter. Also, thanks to Paul Moore for his valuable set of seccomp tools. Notes ----- + Unlike what I said, this release is not shipped with a ptrace emulator. It's planned for the next one, though. + Seccomp-filter was first introduced in Linux 3.5 a year ago, it was also officially back-ported to Ubuntu 12.04 (Linux 3.2). To know if PRoot is actually using this accelerator on your system, check the verbose output. For instance:: $ proot -v 1 true ... proot info: ptrace acceleration (seccomp mode 2) enabled ... But first, be sure it was built with this support:: $ proot -V ... built-in accelerators: process_vm = yes, seccomp_filter = yes ... Release v2.4.1 ============== Fixes ----- + Fix all warnings reported by GCC-4.8 "-Wall -Wextra" and Coverity Prevent 4.5. + Fix Unix sockets path translation for some x86_64 systems. + Make the "kompat" extension (-k option) work again. + Fix spurious "can't delete /tmp/proot-$PID-XXXXX" messages. Release v2.4 ============ New architectures ----------------- + PRoot now works natively on Linux ARM64 systems (a.k.a AArch64). Note that PRoot/AArch64 doesn't support 32-bit binaries yet. + PRoot/x86_64 now supports x32 binaries/rootfs. Fixes ----- + Paths from Unix domain sockets are now translated. For example, it wasn't possible previously to use "tmux" in the guest rootfs if another instance were running in the host rootfs. + When a host path is bound to a nonexistent guest path, PRoot tries to create this latter in the guest rootfs, for some technical reasons. Previously, this "dummy" guest path was created with RWX permissions but this might cause troubles when re-using the rootfs for other purpose. Now, this "dummy" guest path is created with minimal permissions, and it is also possible to avoid its creation by defining the PROOT_DONT_POLLUTE_ROOTFS environment variable. Command-line interface changes ------------------------------ + The directory "/run" is removed from the list of recommended bindings (-B option) because this creates to much conflicts with programs that write in the "/run/var" directory. + The -0 option now makes user's files appear as if they were actually owned by root, and it also fakes the success of any mode changes (chmod* syscalls). This is typically useful to create packages where the files belong to the root user (it's almost always the case). Internal changes ---------------- + PRoot should be even more portable now. For instance, there's no need to worry about syscallee-saved registers anymore. Thanks ------ This release was made possible thanks to, in no special order: Yvan Roux, Jerôme Audu, Heehooman, Yann Droneaud, and James Le Cuirot. See "git log" for details. Release v2.3.1 ============== New feature ----------- + The "fake id0" feature was improved by Rémi Duraffort in order to support privileged write operations in read-only files/directories. Some package managers (Fedora, Debian, ...) relies on this special behavior:: # ls -ld /usr/lib dr-xr-xr-x 22 root root 40960 Jan 2 11:19 /usr/lib/ # install -v something.so /usr/lib/ removed ‘/usr/lib/something.so‘ ‘something.so‘ -> ‘/usr/lib/something.so‘ Fixes ----- + Fix bindings to a guest path that contains a symbolic link. For example when the given guest path ``/var/run/dbus`` is a symbolic link to ``/run/dbus``. + Fix a memory corruption when accessing files in "/proc/self/" Special thanks to Rémi Duraffort for the improved "fake id0" feature and for the bug reports. Release v2.3 ============ This release is intended more specifically to developers and advanced users, it was mostly driven by the requirements of an internal STMicroelectronics project named "Auto-Tuning Optimization Service". New features ------------ + There's now an extension mechanism in PRoot that allows developers to add their own features and/or to use PRoot as a Linux process instrumentation engine. The two following old features were moved to this new extension interface: "-k *string*" and "-0" (respectively: set the kernel release and compatibility level to *string*"; and force some syscalls to behave as if executed by "root"). + It is now possible to execute PRoot under PRoot, well somewhat. Actually the initial instance of PRoot detects that it is being called again and recomputes the configuration for the new process tree. This feature is still experimental and was way harder to implement than expected, however it was worth the effort since it enforced the consistency in PRoot. Just one example among many, in PRoot the "chroot" feature is now really equivalent to the "mount/bind" one, that is, ``chroot path/to/rootfs`` is similar to ``mount --bind path/to/rootfs /``. + The "current working directory" (chdir(2), getcwd(2), ...) is now fully emulated by PRoot. Sadly a minor regression was introduced: even if the current working directory has been removed, getcwd(2) returns a "correct" value. This should be fixed in the next release. Command-line interface changes ------------------------------ + The message "proot info: started/exited" isn't printed by default anymore since it might introduce noise when PRoot is used inside a test-suite that compares outputs. This message was initially added to know whether the guest program has exited immediately. + The "-u" and "-W" options have disappeared. The former wasn't really useful and the latter was definitely useless since the default "current working directory" is "." since v2.1, that means the three examples below are equivalent ("-W" was just an alias to "-b . -w ."):: proot -b . [...] proot -b . -w . [...] proot -W [...] Fixes ----- + The option ``-w .`` is now really equivalent to ``-w $PWD``. + A bug almost impossible to describe here has been fixed, it appeared only when specifying relative bindings, for instance: ``-b .``. Internal changes ---------------- + PRoot now relies on Talloc: a hierarchical, reference counted memory pool system with destructors. It is the core memory allocator used in Samba: http://talloc.samba.org. This is definitely a worthwhile dependency for the sake of development scalability and debuggability. For example, PRoot now has an explicit garbage collector (c.f. ``tracee->ctx``), and the full dynamic memory hierarchy can be printed by sending the USR1 signal to PRoot:: native-shell$ proot --mount=$HOME --mount=/proc --rootfs=./slackware-14/ prooted-shell$ kill -s USR1 $(grep Tracer /proc/self/status | cut -f 2) Tracee 0x6150c0 768 bytes 0 ref' (pid = 22495) talloc_new: ./tracee/tracee.c:97 0x615420 0 bytes 0 ref' $exe 0x61bef0 10 bytes 0 ref' ("/bin/bash") @cmdline 0x61bf60 16 bytes 0 ref' ("/bin/sh", ) /bin/sh 0x61bfd0 8 bytes 0 ref' $glue 0x61bae0 24 bytes 0 ref' ("/tmp/proot-22494-UfGAPh") FileSystemNameSpace 0x615480 32 bytes 0 ref' $cwd 0x61b880 13 bytes 0 ref' ("/home/cedric") Bindings 0x61b970 16 bytes 0 ref' (host) Binding 0x615570 8280 bytes 1 ref' (/home/cedric:/home/cedric) Binding 0x6176a0 8280 bytes 1 ref' (/proc:/proc) Binding 0x6197d0 8280 bytes 1 ref' (/usr/local/proot/slackware-14:/) Bindings 0x61b900 16 bytes 0 ref' (guest) Binding -> 0x6176a0 Binding -> 0x615570 Binding -> 0x6197d0 Release v2.2 ============ + This release brings some critical fixes so an upgrade is highly recommended, especially on x86_64 and Ubuntu. + PRoot is now a lot faster: the speed-up can be up to 50% depending on the kind of application. + PRoot can now mount/bind files anywhere in the guest rootfs, even if the mount point has no parent directory (and/or can't be created). With previous versions of PRoot, that would created kinda black hole in the filesystem hierarchy that might bug some programs like "cpio" or "rpm". For example, with the previous version of PRoot:: $ proot -b /etc/motd:/black/holes/and/revelations proot warning: can't create the guest path (binding) ... proot info: started $ find /black find: `/black: No such file or directory $ cat /black/holes/and/revelations Time has come to make things right -- Matthew Bellamy And now:: $ proot -b /etc/motd:/black/holes/and/revelations proot info: started $ find /black /black /black/holes /black/holes/and /black/holes/and/revelations $ cat /black/holes/and/revelations Time has come to make things right -- Matthew Bellamy + "/run" was added to the list of recommended bindings (-B/-Q). + SH4 and ARM architectures are now officially supported. Thanks ------ Huge thanks to Rémi DURAFFORT for all the tests, bug reports, fixes, and for hosting http://proot.me. Thanks to Thomas P. HIGDON for the advanced investigation on a really tricky bug (red zone corruption). Release v2.1 ============ New features ------------ + PRoot can now emulate some of the syscalls that are available in the kernel release specified by -k but that are missing in the host kernel. This allows the execution of guest programs expecting a kernel newer than the actual one, if you encountered the famous "FATAL: kernel too old" or "qemu: Unsupported syscall" messages. + The current working directory isn't changed anymore if it is still accessible in the guest environment (binding). Fixes ----- + Added support for architectures with no misalignment support (SH4). + Fix support for: link(2), linkat(2), symlink(2), and symlinkat(2). Release v2.0.1 ============== + Fix a compatibility issue with QEMU v1.1 + Fix the initialization of bindings that point to "/proc/self". These problems were reported by Alkino: https://github.com/cedric-vincent/PRoot/issues/3 Release v2.0 ============ New features ------------ + There's now a specific support to handle special symlinks in /proc. As of now, only the following ones are correctly handled: * /proc/self, it was already supported previously but now this is done consistently (i.e. not a hack); * /proc//exe, for any monitored by PRoot; and * /proc//fd/. + The list of supported syscalls was updated, as of Linux 3.4.1. Command-line interface changes ------------------------------ + The path to the guest rootfs can now be specified by the new -r/--rootfs option. This permits the use of shell aliases, for example: $ alias armedslack='proot -Q qemu-arm -r /path/to/armedslack' $ armedslack -v 1 -b ~/arm_cpuinfo:/proc/cpuinfo That wasn't possible previously because the path to the guest rootfs had to be the last option. + The -v/--verbose option now takes a parameter, and a negative integer makes PRoot quiet except on fatal errors. + The -h/--help option now prints a detailed message. + The -V/--version and -h/--help options now exit with success. Fix --- + Return correct errno if a non-final path component isn't a directory nor a symlink. + Fix the insertion of an item in the list of host/guest bindings. Internal changes ---------------- This section is dedicated to PRoot developers. + File-system information is now inherited from a traced process to its children. This permits the emulation of symlinks in /proc/self: cmdline, exe, cwd, root, ... + The execution of QEMU is now fully confined to the virtual rootfs namespace: it now relies on the "mixed-execution" feature, just like a regular host program. Release v1.9 ============ Fixes ----- + Be as transparent as possible with respect to SIGSTOP and SIGTRAP signals. For instance, the Open POSIX Test Suite now reports the same level of success whether it is run under PRoot or not (it depends on the kernel version though). + Ignore terminating signals and kill all tracees on abnormal termination signals (^\, segmentation fault, divide by zero, ...). This ensures no tracee will stay alive without being monitored anymore. + Force utsname.machine to "i686" -- instead of "i386" -- for 32-bit programs running on x86_64 systems. This improves the compatibility with package managers that deduce the current architecture from `uname -m`. + Fix x86_64 support for linkat() and fchownat(). + Fix mixed-execution support, LD_LIBRARY_PATH was defined twice for host programs. Release v1.8.4 ============== New feature ----------- + The -0 option now fakes success on ``chroot("/")``. This feature is required by some guest package managers, like ``pacman`` -- the Arch Linux Package Manager. Fix --- + Nested bindings are now correctly supported. For example with these bindings -- nested from the host point-of-view:: host$ proot -b /:/host-rootfs -b /tmp ... guest$ ln -s /tmp/bar /tmp/foo # ... points to "/tmp/bar" instead of "/host-rootfs/tmp/bar" and with these bindings -- nested from the guest point-of-view:: host$ proot -b /bin -b /usr/bin/find:/bin/find ... guest$ /bin/find # ... works instead of "no such file or directory" Internal changes ---------------- This section is dedicated to PRoot developers. + Functions to compare two pathes (equal, prefix, not comparable, ...) are now available, at last. + The "ignore ELF interpreter" option can be (dis|en)able with the ``PROOT_IGNORE_ELF_INTERPRETER`` environment variable and/or with the ``config.ignore_elf_interpreter`` internal variable. Release v1.8.3 ============== New features ------------ + The -0 option now fakes success on ownership changes. This improves the compatibility with package managers that abort if ``chown(2)`` fails. Note that this is quite limited compared to ``fakeroot``. + Force utsname.machine to "i386" for 32-bit programs running on x86_64 systems. This improves the compatibility with package managers that deduce the current architecture from `uname -m`. Fixes ----- + Fix a regression regarding the concatenation of the ``..`` with a path ending with ``.``. For intance you can now do ``ls foo`` where ``foo`` is a symbolic link to ``/bar/.``. + Don't return an error if the specified size for ``getcwd(2)`` and ``readlink(2)`` is greater than PATH_MAX. Technically the result may likely be shorter than this limit. Release v1.8.2 ============== + This is the first public release of PRoot, it's time to increase its maturity artificially ... Actually it's an homage to Blink 182 ;) + User manual finally published. + PRoot can now *mix* the execution of host programs and the execution of guest programs emulated by QEMU. This is useful to use programs that aren't available initially in the guest environment and to speed up build-time by using cross-compilation tools or any CPU independent program, like interpreters. + Absolute symlinks from bound directories that point to any bound directory are kept consistent: for example, given the host symlink ``/bin/sh -> /bin/bash``, and given the command-line option ``-b /bin:/foo``, the symlink will appeared as ``/foo/sh -> /foo/bash``. + Three command-line options are gone: * ``-p`` (don't block the ptrace syscall) wasn't really useful. * ``-e`` (don't use the ELF interpreter) isn't required anymore. * ``-a`` (disable the ASLR mechanism) is now the default. + Don't complain anymore when parent directories of a *recommended binding* (as enabled by ``-B``, ``-M`` and ``-Q`` options) can't be created. + Support job control under ptrace as introduced in Linux 3.0+. + ``LD_`` environment variables are now passed to the QEMUlated program, not to QEMU itself. It means ``ldd`` works (there's a bug in QEMU/ARM though). + Many fixes and improved compatibility thanks to the Open Build Service instantiated at http://build.opensuse.com + Note: v0.7.1 was an experimental release. Release v0.7.0 ============== + Search the guest program in $PATH relatively to the guest rootfs, for instance you can now just write:: proot /path/to/guest/rootfs/ perl instead of:: proot /path/to/guest/rootfs/ /usr/bin/perl + The command-line interface was re-written from scratch, the only incompatible change is that QEMU options are now separated by spaces:: proot -Q "qemu-arm -g 1234" ... instead of:: proot -Q qemu-arm,-g,1234 ... + New option "-0": force syscalls "get*id" to report identity 0, aka "root". + Many fixes, code refactoring, new testing framework, ... Special thanks to Claire ROBINE for her contribution. Release v0.6.2 ============== + Change the default command from $SHELL to "/bin/sh". The previous behaviour led to an unexpected error -- from user's point-of-view -- when $SHELL didn't exit in the new root file-system. + Fix *partially* support for readlink(2) when mirror pathes are in use. Prior this patch, any symbolic link -- that points to an absolute path which prefix is equal to the host-side of any mirror path -- was bugged. For instance, the command "proot -m /bin:/host $ROOTFS /usr/bin/readlink /usr/bin/ps" returned "/host" instead of "/bin/ps". + Add the option "-V" to print the version then exit. + Be more explicit when a wrong command-line argument is used. + Remove the SIGSEGV help message: it was too confusing to the user. + Use a new shining build-system (again :D). Special thanks go to those contributors: Yves JANIN, Remi Duraffort and Christophe GUILLON. Release v0.6.1 ============== + Add `/tmp` to the list of mirrored paths when using -M. + Fix the ELF interpreter extraction. + Rewrite the build system. Release v0.6 ============ New features ------------ + Added support for "asymmetric" path mirrors. The command-line option for mirrors was extended to support the syntax "-m :" where is the location of the mirror within the alternate rootfs and is the path to the real directory/file. For instance you can now mirror the whole host rootfs in the directory "/hostfs" within the alternate rootfs that way:: proot -m /:/hostfs ... + Added an option to disable ASLR (Address Space Layout Randomization). RHEL4 and Ubuntu 10.04 use an ASLR mechanism that creates conflicts between the layout of QEMU and the layout of the target program. This new option is automatically set when QEMU is used. + Added "/etc/resolv.conf" and $HOME to the list of mirrored paths when using the option -M or -Q. Fixes ----- + Fixed the detranslation of getcwd(2) and readlink(2). + Improved the build compatibility on old/broken distro. + Added support for pthread cancellation when QEMU is used. + Set QEMU's fake argv[0] to the program actually launched, not to the initial script name. + Create the path up to the mirror location to cheat "walking" programs. care-2.2.1/doc/care/0000755000175000017500000000000012430615044013460 5ustar ivoireivoirecare-2.2.1/doc/care/changelog.txt0000644000175000017500000000672312430615044016160 0ustar ivoireivoireCARE v2.2.1 =========== + Revert "care: Fix sub-reconfiguration support, ie. when executed from proot.". This fix has introduced a regression, and sub-reconfiguration support will be dropped in a near future. Reported-by: Jonathan Passerat-Palmbach Ref.: https://github.com/cedric-vincent/PRoot/issues/72 CARE v2.2 ========= + Special characters in environment variables are now correctly quoted in the generated "re-execute.sh" script. For instance, the value of "COMP_WORDBREAKS" environment variable on Ubuntu used to make CARE generate broken "re-execute.sh" scripts. + It is now possible to deliver CARE as a dynamically linked binary. In this case, the self-extracting format is not supported since one can't assume the re-execution environment contains all the pre-requisites (libtalloc, libarchive, proot). Thus, CARE and PRoot should be deployed independently on the re-execution environment. + Syscall are now emulated only if it is really needed. This release is based on PRoot v4.0.3, so it contains all the following generic fixes too: https://github.com/cedric-vincent/PRoot/releases/tag/v4.0.0 https://github.com/cedric-vincent/PRoot/releases/tag/v4.0.1 https://github.com/cedric-vincent/PRoot/releases/tag/v4.0.2 https://github.com/cedric-vincent/PRoot/releases/tag/v4.0.3 CARE v2.1 ========= This release contains all the fixes from PRoot v3.2.2 [1] and the following new features: + CARE now supports three new archive formats: - a self-extracting format, this has become the default behavior. Here's an example:: somewhere$ care echo OK [...] OK [...] care info: - run `./care-140115135934.bin` to extract the output archive. elsewhere$ ./care-140115135934.bin info: archive found: offset = 195816, size = 3035122 info: extracted: care-140115135934/rootfs/usr info: extracted: care-140115135934/rootfs/usr/local [...] info: extracted: care-140115135934/re-execute.sh info: extracted: care-140115135934/README.txt info: extracted: care-140115135934/proot - the GNU tar format, this is the most commonly used archive format. It can be combined with all the compression formats supported by CARE: ".tar.gz" or ".tar.lzo". - a "copy" format where the content is copied directly into a directory instead of being archived in a file. For instance:: $ care -o foo/ echo OK [...] $ ls foo README.txt proot re-execute.sh rootfs/ + It is recommended to use the new "-x/--extract" option to extract archives created by CARE, since most extracting tools -- that are not based on libarchive -- are too limited to extract them correctly. + CARE now returns the exit code from the re-executed command. For instance, with the previous version: $ care-v2.0 -o test.cpio sh -c 'exit 1' [...] $ echo $? 1 [...] $ test/re-execute.sh $ echo $? 0 $ test/re-execute.sh sh -c 'exit 2' $ echo $? 0 And with this new version: $ care-v2.1 -o test.cpio sh -c 'exit 1' [...] $ echo $? 1 [...] $ test/re-execute.sh $ echo $? 1 $ test/re-execute.sh sh -c 'exit 2' $ echo $? 2 + Applications that rely on DBUS and/or on KDE cache are now supported. Thanks to Antoine Moynault, Christophe Guillon, and Rémi Duraffort for their help. [1] https://github.com/cedric-vincent/PRoot/blob/v3.2.2/doc/changelog.txt care-2.2.1/doc/care/stylesheets/0000755000175000017500000000000012430615044016034 5ustar ivoireivoirecare-2.2.1/doc/care/stylesheets/website.css0000644000175000017500000000024212430615044020206 0ustar ivoireivoire@import url("website.css"); h1 { color: SkyBlue; } #contents a:hover { border-bottom: 2px solid SkyBlue; } a { border-bottom: 1px solid SkyBlue; } care-2.2.1/doc/care/stylesheets/cli.xsl0000644000175000017500000001240412430615044017334 0ustar ivoireivoire /* This file is automatically generated from the documentation. EDIT AT YOUR OWN RISK. */ #ifndef CARE_CLI_H #define CARE_CLI_H #include "cli/cli.h" #ifndef VERSION #define VERSION "" #endif #define CARE_MAX_SIZE 1024 static int pre_initialize_bindings(Tracee *, const Cli *, size_t, char *const *, size_t); static int post_initialize_bindings(Tracee *, const Cli *, size_t, char *const *, size_t); static Cli care_cli = { .version = VERSION, .name = "care", .pre_initialize_bindings = pre_initialize_bindings, .post_initialize_bindings = post_initialize_bindings, .options = { END_OF_OPTIONS, }, }; #endif /* CARE_CLI_H */ .subtitle = " ", .synopsis = " ", .colophon = " ", .logo = "\ ", static char const *default_concealed_paths[] = { NULL, }; static char const *default_revealed_paths[] = { NULL, }; static char const *default_volatile_paths[] = { NULL, }; static char const *default_volatile_envars[] = { NULL, }; { .class = " ", .arguments = { { .name = NULL, .separator = '\0', .value = NULL } }, .handler = handle_option_ , .description = " ", .detail = NULL, }, * * { .name = " ", .separator = ' ', .value = " " \0', .value = NULL }, static int handle_option_ (Tracee *tracee, const Cli *cli, char *value); care-2.2.1/doc/care/stylesheets/website.xsl0000644000175000017500000000560712430615044020236 0ustar ivoireivoire <xsl:value-of select="document/title" /> — <xsl:value-of select="document/subtitle" />

    CARE

    By default XSLTproc converts tags with no content to self-closing tags

    Support

    Feel free to send your questions, bug reports, suggestions, and patchs to the mailing-list or to the forum, but please be sure that your answer isn't in the user manual first.

    care-2.2.1/doc/care/roadmap.txt0000644000175000017500000000144512430615044015650 0ustar ivoireivoire========= Roadmap ========= CARE v2.2 ========= * Based on PRoot v3.3. * Use PRoot "fake id" option ("-i"). * Make "re-execute.sh" print a useful message when -h/--help is specified. * Add an option to archive specified path, no matter whether it is used or not. * In concealed-accesses.txt, print the name of the program that tried to access a concealed path too. * Fake the full utsname structure. CARE v2.3 ========= * Add an option to augment an existing archive with content used from a new monitored execution. Not yet scheduled ================= Fixes ----- * Accept *care as valid CLI name * Bug: an archive for "wine notepad.exe" created on Ubuntu 12.04 x86_64 can't be re-executed on RHEL5 x86_64. * Warn when an unemulated feature is used (ppoll, getcpu, flags, ...) care-2.2.1/doc/care/manual.txt0000644000175000017500000003770012430615044015505 0ustar ivoireivoire====== CARE ====== ------------------------------------------------- Comprehensive Archiver for Reproducible Execution ------------------------------------------------- :Date: 2014-11-12 :Version: 2.2.1 :Manual section: 1 Synopsis ======== **care** [*option*] ... *command* Description =========== CARE monitors the execution of the specified command to create an *archive* that contains all the material required to *re-execute* it in the same context. That way, the command will be reproducible everywhere, even on Linux systems that are supposed to be not compatible with the original Linux system. CARE is typically useful to get reliable bug reports, demonstrations, `artifact evaluation`_, tutorials, portable applications, minimal rootfs, file-system coverage, ... By design, CARE does not record events at all. Instead, it archives environment variables and accessed file-system components -- before modification -- during the so-called *initial* execution. Then, to reproduce this execution, the ``re-execute.sh`` script embedded into the archive restores the environment variables and relaunches the command confined into the saved file-system. That way, both *initial* and *reproduced* executions should produce the same results as they use the same context, assuming they do not rely on external events -- like key strokes or network packets -- or that these external events are replayed manually or automatically, using umockdev_ for instance. That means it is possible to alter explicitly the reproduced executions by changing content of the saved file-system, or by replaying different external events. .. _umockdev: https://github.com/martinpitt/umockdev/ .. _artifact evaluation: http://www.artifact-eval.org/ Privacy ------- To ensure that no sensitive file can possibly leak into the archive, CARE *conceals* recursively the content of ``$HOME`` and ``/tmp``, that is, they appear empty during the original execution. Although, for consistency reasons, the content of ``$PWD`` is *revealed* even if it is nested into the two previous paths. As a consequence, a program executed under CARE may behave unexpectedly because a required path is not accessible anymore. In this case, such a path has to be revealed explicitly. For details, see the options ``--concealed-path`` and ``--revealed-path``, and the file ``concealed-accesses.txt`` as well. It is advised to inspect the archived content before sharing it. Options ======= The command-line interface is composed of two parts: first CARE's options, then the command to launch. This section describes the options supported by CARE, that is, the first part of its command-line interface. -o path, --output=path Archive in *path*, its suffix specifies the format. The suffix of *path* is used to select the archive format, it can be one of the following: ========= ======================================================== suffix comment ========= ======================================================== / don't archive, copy into the specified directory instead .tar most common archive format .cpio most portable archive format, it can archive sockets too ?.gz most common compression format, but slow ?.lzo fast compression format, but uncommon ?.bin see ``Self-extracting format`` section ?.?.bin see ``Self-extracting format`` section .bin see ``Self-extracting format`` section .raw recommended archive format, use `care -x` to extract ========= ======================================================== where "?" means the suffix must be combined with another one. For examples: ".tar.lzo", ".cpio.gz", ".tar.bin", ".cpio.lzo.bin", ... If this option is not specified, the default output path is ``care-.bin`` or ``care-.raw``, depending on whether CARE was built with self-extracting format support or not. -c path, --concealed-path=path Make *path* content appear empty during the original execution. Some paths may contain sensitive data that should never be archived. This is typically the case for most of the files in: * $HOME * /tmp That's why these directories are recursively *concealed* from the original execution, unless the ``-d`` option is specified. Concealed paths appear empty during the original execution, as a consequence their original content can't be accessed nor archived. -r path, --revealed-path=path Make *path* content accessible when nested in a concealed path. Concealed paths might make the original execution with CARE behave differently from an execution without CARE. For example, a lot of ``No such file or directory`` errors might appear. The solution is to *reveal* recursively any required paths that would be nested into a *concealed* path. Note that ``$PWD`` is *revealed*, unless the ``-d`` option is specified. -p path, --volatile-path=path Don't archive *path* content, reuse actual *path* instead. Some paths contain only communication means with programs that can't be monitored by CARE, like the kernel or a remote server. Such paths are said *volatile*; they shouldn't be archived, instead they must be accessed from the *actual* rootfs during the re-execution. This is typically the case for the following pseudo file-systems, sockets, and authority files: * /dev * /proc * /sys * /run/shm * /tmp/.X11-unix * /tmp/.ICE-unix * $XAUTHORITY * $ICEAUTHORITY * /var/run/dbus/system_bus_socket * /var/tmp/kdecache-$LOGNAME This is also typically the case for any other fifos or sockets. These paths are considered *volatile*, unless the ``-d`` option is specified. -e name, --volatile-env=name Don't archive *name* env. variable, reuse actual value instead. Some environment variables are used to communicate with programs that can't be monitored by CARE, like remote servers. Such environment variables are said *volatile*; they shouldn't be archived, instead they must be accessed from the *actual* environment during the re-execution. This is typically the case for the following ones: * DISPLAY * http_proxy * https_proxy * ftp_proxy * all_proxy * HTTP_PROXY * HTTPS_PROXY * FTP_PROXY * ALL_PROXY * DBUS_SESSION_BUS_ADDRESS * SESSION_MANAGER * XDG_SESSION_COOKIE These environment variables are considered *volatile*, unless the ``-d`` option is specified. -m value, --max-archivable-size=value Set the maximum size of archivable files to *value* megabytes. To keep the CPU time and the disk space used by the archiver reasonable, files whose size exceeds *value* megabytes are truncated down to 0 bytes. The default is 1GB, unless the ``-d`` option is specified. A negative *value* means no limit. -d, --ignore-default-config Don't use the default options. -x file, --extract=file Extract content of the archive *file*, then exit. It is recommended to use this option to extract archives created by CARE because most extracting tools -- that are not based on libarchive -- are too limited to extract them correctly. -v value, --verbose=value Set the level of debug information to *value*. The higher the integer *value* is, the more detailed debug information is printed to the standard error stream. A negative *value* makes CARE quiet except on fatal errors. -V, --version, --about Print version, copyright, license and contact, then exit. -h, --help, --usage Print the user manual, then exit. Exit Status =========== If an internal error occurs, ``care`` returns a non-zero exit status, otherwise it returns the exit status of the last terminated program. When an error has occurred, the only way to know if it comes from the last terminated program or from ``care`` itself is to have a look at the error message. Files ===== The output archive contains the following files: ``re-execute.sh`` start the re-execution of the initial command as originally specified. It is also possible to specify an alternate command. For example, assuming ``gcc`` was archived, it can be re-invoked differently: $ ./re-execute.sh gcc --version gcc (Ubuntu/Linaro 4.5.2-8ubuntu4) 4.5.2 $ echo 'int main(void) { return puts(\"OK\"); }' > rootfs/foo.c $ ./re-execute.sh gcc -Wall /foo.c $ foo.c: In function "main": $ foo.c:1:1: warning: implicit declaration of function "puts" ``rootfs/`` directory where all the files used during the original execution were archived, they will be required for the reproduced execution. ``proot`` virtualization tool invoked by re-execute.sh to confine the reproduced execution into the rootfs. It also emulates the missing kernel features if needed. ``concealed-accesses.txt`` list of accessed paths that were concealed during the original execution. Its main purpose is to know what are the paths that should be revealed if the the original execution didn't go as expected. It is absolutely useless for the reproduced execution. Limitations =========== It's not possible to use GDB, strace, or any programs based on *ptrace* under CARE yet. This latter is also based on this syscall, but the Linux kernel allows only one *ptracer* per process. This will be fixed in a future version of CARE thanks to a ptrace emulator. Example ======= In this example, Alice wants to report to Bob that the compilation of PRoot v2.4 raises an unexpected warning:: alice$ make -C PRoot-2.4/src/ make: Entering directory `PRoot-2.4/src' [...] CC path/proc.o ./path/proc.c: In function 'readlink_proc': ./path/proc.c:132:3: warning: ignoring return value of 'strtol' [...] Technically, Alice uses Ubuntu 11.04 for x86, whereas Bob uses Slackware 13.37 on x86_64. Both distros are supposed to be shipped with GCC 4.5.2, however Bob is not able to reproduce this issue on his system:: bob$ make -C PRoot-2.4/src/ make: Entering directory `PRoot-2.4/src' [...] CC path/proc.o [...] Since they don't have much time to investigate this issue by iterating between each other, they decide to use CARE. First, Alice prepends ``care`` to her command:: alice$ care make -C PRoot-2.4/src/ care info: concealed path: $HOME care info: concealed path: /tmp care info: revealed path: $PWD care info: ---------------------------------------------------------------------- make: Entering directory `PRoot-2.4/src' [...] CC path/proc.o ./path/proc.c: In function 'readlink_proc': ./path/proc.c:132:3: warning: ignoring return value of 'strtol' [...] care info: ---------------------------------------------------------------------- care info: Hints: care info: - search for "conceal" in `care -h` if the execution didn't go as expected. care info: - use `./care-130213072430.bin` to extract the output archive. Then she sends the ``care-130213072430.bin`` file to Bob. Now, he should be able to reproduce her issue on his system:: bob$ ./care-130213072430.bin [...] bob$ ./care-130213072430/re-execute.sh make: Entering directory `PRoot-2.4/src' [...] CC path/proc.o ./path/proc.c: In function 'readlink_proc': ./path/proc.c:132:3: warning: ignoring return value of 'strtol' [...] So far so good! This compiler warning doesn't make sense to Bob since ``strtol`` is used there to check a string format; the return value is useless, only the ``errno`` value matters. Further investigations are required, so Bob re-execute Alice's GCC differently to get more details:: bob$ ./care-130213072430/re-execute.sh gcc --version gcc (Ubuntu/Linaro 4.5.2-8ubuntu4) 4.5.2 Copyright (C) 2010 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. The same invocation on his system returns something slightly different:: bob$ gcc --version gcc (GCC) 4.5.2 Copyright (C) 2010 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. This confirms that both GCC versions are the same, however Alice's one seems to have been modified by Ubuntu. Although, according to the web page related to this Ubuntu package [#]_, no changes regarding ``strtol`` were made. So Bob decides to search into the files coming from Alice's system, that is, the ``rootfs`` directory in the archive:: bob$ grep -wIrl strtol ./care-130213072430/rootfs care-130213072430/rootfs/usr/include/inttypes.h care-130213072430/rootfs/usr/include/stdlib.h [...] Here, the file ``usr/include/stdlib.h`` contains a declaration of ``strtol`` with the "warn unused result" attribute. On Ubuntu, this file belongs to the EGLIBC package, and its related web page [#]_ shows that this attribute was actually wrongly introduced by the official EGLIBC developers. Ultimately Bob should notify them in this regard. Thanks to CARE, Bob was able to reproduce the issue reported by Alice without effort. For investigations purpose, he was able to re-execute programs differently and to search into the relevant files. .. [#] https://launchpad.net/ubuntu/oneiric/+source/gcc-4.5/4.5.2-8ubuntu4 .. [#] https://launchpad.net/ubuntu/+source/eglibc/2.13-0ubuntu13.2 Self-extracting format ====================== The self-extracting format used by CARE starts with an extracting program, followed by a regular archive, and it ends with a special footer. This latter contains the signature "I_LOVE_PIZZA" followed by the size of the embedded archive:: +------------------------+ | extracting program | +------------------------+ | | | embedded archive | | | +------------------------+ | uint8_t signature[13] | | uint64_t archive_size | # big-endian +------------------------+ The command ``care -x`` can be used against a self-extracting archive, even if they were not build for the same architecture. For instance, a self-extracting archive produced for ARM can be extracted with a ``care`` program built for x86_64, and vice versa. It is also possible to use external tools to extract the embedded archive, for example:: $ care -o foo.tar.gz.bin /usr/bin/echo OK [...] OK [...] $ hexdump -C foo.tar.gz.bin | tail -3 0015b5b0 00 b0 2e 00 49 5f 4c 4f 56 45 5f 50 49 5a 5a 41 |....I_LOVE_PIZZA| 0015b5c0 00 00 00 00 00 00 12 b4 13 |.........| 0015b5c9 $ file_size=`stat -c %s foo.tar.gz.bin` $ archive_size=$((16#12b413)) $ footer_size=21 $ skip=$(($file_size - $archive_size - $footer_size)) $ dd if=foo.tar.gz.bin of=foo.tar.gz bs=1 skip=$skip count=$archive_size 1225747+0 records in 1225747+0 records out 1225747 bytes (1.2 MB) copied, 2.99546 s, 409 kB/s $ file foo.tar.gz foo.tar.gz: gzip compressed data, from Unix $ tar -tzf foo.tar.gz foo/rootfs/usr/ [...] foo/re-execute.sh foo/README.txt foo/proot Downloads ========= CARE is heavily based on PRoot_, that's why they are both hosted in the same repository: http://github.proot.me. Since CARE is supposed to work on any Linux systems, it is recommended to use following highly compatible static binaries: * for x86_64: http://static.reproducible.io/care-x86_64 * for x86: http://static.reproducible.io/care-x86 * for ARM: http://static.reproducible.io/care-arm * other architectures: on demand. .. _PRoot: http://proot.me Colophon ======== Visit http://reproducible.io for help, bug reports, suggestions, patches, ... Copyright (C) 2014 STMicroelectronics, licensed under GPL v2 or later. :: _____ ____ _____ ____ / __/ __ | __ \ __| / /_/ | / __| \_____|__|__|__|__\____| care-2.2.1/doc/howto-release.txt0000644000175000017500000000243112430615044016065 0ustar ivoireivoireHow to make a release of PRoot? =============================== This document summarizes checks that must be performed before releasing PRoot or CARE. Checks ------ + Sanity checks: * on ARM and *all* OBS distros (x86 and x86_64): `make -C test` * on x86_64, with *and* without seccomp: - `make -C tests` on *all* OBS distros - `make -C tests memcheck` - `CFLAGS=-fsanitize=address LDFLAGS=-lasan` - `make -C tests V=1 2>&1 | grep talloc` + Fonctional checks: no regressions must appear with respect to tests/validation.mk and to the configurations tested in the previous release (`git show tags/...`). + Performance checks: the following command must not suffer from unexpected performance regression:: time proot -R / perl -e 'system("/usr/bin/true") for (1..10000)' where "/usr/bin/true" is a symlink to "/bin/true". + Static analysis: Coverity Scan and Clang Scan Build must not report new issues. Documentation Update -------------------- 0. update "doc/changelog.txt" 1. update the release number in "doc/proot/manual.txt" 2. regenerate the documentation: `make -C doc; cp doc/proot.1 doc/proot/man.1` 3. regenerate the command-line interface: `cp doc/*.h src/cli/; $EDITOR src/cli/{proot,care}.h` 4. upload the generated RPM .spec file to OBS care-2.2.1/doc/GNUmakefile0000644000175000017500000000140012430615044014613 0ustar ivoireivoireOUTPUTS = proot/man.1 proot.h proot/rpm-spec proot/index.html care.h care/index.html all: $(OUTPUTS) %/man.1: %/manual.txt rst2man $< $@ %.xml: %.txt rst2xml --no-doctype $< $@ %.html: %.txt rst2html $< $@ # Workaround to avoid unescaped C character. %/manual-quoted.txt: %/manual.txt sed 's/"/\\\\"/g' $^ > $@ %.h: %/stylesheets/cli.xsl %/manual-quoted.xml xsltproc --output $@ $^ %/rpm-spec: %/stylesheets/rpm-spec.xsl %/manual.xml xsltproc --output $@ $^ echo "* $(shell date +'%a %b %d %Y') Cédric VINCENT " >> $@ cat $*/changelog.txt >> $@ %/index.html: stylesheets/website.xsl %/stylesheets/website.xsl %/manual.xml xsltproc --output $@ $*/stylesheets/website.xsl $*/manual.xml clean: rm -f *.xml $(OUTPUTS) *-quoted.* care-2.2.1/doc/articles/0000755000175000017500000000000012430615044014354 5ustar ivoireivoirecare-2.2.1/doc/articles/stylesheets/0000755000175000017500000000000012430615044016730 5ustar ivoireivoirecare-2.2.1/doc/articles/stylesheets/article-html.txt0000644000175000017500000000026612430615044022062 0ustar ivoireivoire.. raw:: html care-2.2.1/doc/articles/howto_debian_rootfs.txt0000644000175000017500000001566212430615044021165 0ustar ivoireivoire.. include:: stylesheets/article-html.txt ======================================= How to Set Up a Debian RootFS for PRoot ======================================= :Original Title: PRoot sorcery :URL: http://ivoire.dinauz.org/blog/post/2012/04/16/PRoot-sorcery :Part of: http://ivoire.dinauz.org/blog/tag/PRoot :Author: Rémi Duraffort :PRoot Version: 1.8.3 :Abstract: A good practice for software developer is to provide a test suite while developing a software. When developing for Linux, it's also a good practice to compile the software and run the test suite on many distributions like Debian, Ubuntu, Fedora, ArchLinux, Centos, Slackware and for both i386 and x86_64. Usually, softwares are compiled and tested on only one distribution because setting up the right environment is long and painful. Most of the time root privileges are also required to setup such environment. In this article and the following one, I will show that using PRoot_, such testing is quite handy and can be done by any users. .. _PRoot: http://proot.me Getting PRoot up and running ============================ In order to test PRoot, you can download the latest version on the `official website`_ and compile it. You can also grab a package for your distribution on the `Open Build Service`_. .. _official website: http://proot.me .. _Open Build Service: http://software.opensuse.org/download.html?project=home:cedric-vincent&package=proot If you choose to compile PRoot, that's just a matter of:: #!/bin/sh git clone git://github.com/cedric-vincent/PRoot.git cd PRoot/src make [...] ./proot Grabbing a RootFS ================= The second step to test VLC media player in different distributions is to get a root file system for every distribution we want to try. The first and easy way to have a working root file system is to download it from `OpenVZ repository`_ or `OpenVZ contribs`_. .. _OpenVZ repository: http://download.openvz.org/template/precreated/ .. _OpenVZ contribs: http://download.openvz.org/template/precreated/contrib/ It's also possible, under Debian and Ubuntu, to create a root file system using debootstrap, but let's take the easy way for today:: #!/bin/sh % mkdir debian-6.0-x86_64 % cd debian-6.0-x86_64 % wget http://download.openvz.org/template/precreated/debian-6.0-x86_64.tar.gz [...] % tar xf debian-6.0-x86_64.tar.gz % cd .. As we will see later, you can safely ignore the warnings printed by tar when extracting the file system. Let us note that everything is run as a normal user. Now you can "jump" into this new root file system using PRoot:: #!/bin/sh % cat /etc/debian_version wheezy/sid % proot debian-6.0-x86_64 ~ cat /etc/debian_version 6.0.4 For now on, the root file system is the one you just downloaded. For instance:: #!/bin/sh ~ gcc --version gcc (Debian 4.4.5-8) 4.4.5 ~ logout % gcc --version gcc (Debian 4.6.3-1) 4.6.3 You just tested the first feature provided by PRoot: * Changing the root file system of a process As you may have noticed, I used ``%`` for the host file system shell prompt (a Debian Sid) and ``~`` for the PRooted one (a Debian Squeeze). Setting up the new RootFS ========================= We now have a basic and working root file systems but some configuration has to be done before any real usages: * Adding the right users and groups to /etc/passwd and /etc/group * Configuring DNS resolution * Binding some special directories * Updating the system Normally, all this tasks can only be done by root as they will modifies files owned by root. As we extracted the archive as a normal user, the current user can modify any files in the root file system though making root privileges pointless. Adding some user and groups --------------------------- In order to keep the same user inside the PRooted file system, you just have to copy the right lines from ``/etc/passwd`` to the corresponding file in the PRooted file system. You can do the same thing for groups in ``/etc/group``. Configuring DNS resolution -------------------------- Just copy ``/etc/resolv.conf`` from the host root file system to the PRooted one. This way the same mechanism will be used in the host system and in the PRooted one. Another solution is to ask PRoot to do the job for you: binding /etc/resolv.conf to the same file in the PRooted file system. Adding ``-b /etc/resolv.conf`` to the PRoot command line will do the trick:: #!/bin/sh % proot debian-6.0-x86_66 ~ cat /etc/resolv.conf cat: /etc/resolv.conf: No such file or directory ~ logout % proot -b /etc/resolv.conf debian-6.0-x86_64 ~ cat /etc/resolv.conf [...]same file as /etc/resolv.conf on the host[...] You just tested the second feature provided by PRoot: * Binding some files to another location in the file system. In this case you bound /etc/resolv.conf to the same location inside the PRooted environment. It's also possible to bind it somewhere else with: ``-b /etc/resolv.conf:/Somewere_else_in_the_PRooted_FS``. Binding some special directories -------------------------------- For the moment, the ``/dev`` and ``/proc`` directories are empty. However some programs need it in order to work correctly. For example ssh-keygen will refuse to work without ``/dev/random``. We should bind the real /dev and /proc in the new root file system. Adding ``-b /dev -b /proc`` to the PRoot command line will solve this issue. Updating the system ------------------- We already noticed that the current user can modify any file on the PRooted file system because it was extracted by this user. However most tools like dpkg required the current user id to be root in order to work. For this reason, PRoot can be launched with the ``-0`` (zero) option which fake some syscalls and makes the programs think the current user is root:: #!/bin/sh % proot -b /etc/resolv.conf -0 debian-6.0-x86_64 ~ id -a uid=0(root) gid=0(root) groupes=0(root) ~ cat /etc/apt/source.list deb http://ftp.debian.org/debian squeeze main contrib non-free deb http://security.debian.org squeeze/updates main contrib non-free ~ apt-get update Hit http://ftp.debian.org squeeze Release.gpg Ign http://ftp.debian.org/debian/ squeeze/contrib Translation-en [...] Reading package lists... Done ~ apt-get upgrade [...] You can manage this root file system like a classical one. Pay attention that some services that really required root privileges to work (like apache or some daemons) could not run correctly under PRoot as we only fake root privileges. Future work =========== This article is beginning to be really long so I will finish here for today. We saw a simple way to get a working Debian root file systems that we can manage without real root privileges. This work will be useful for the next article which will cover the compilation and testing of VLC media player in this new root file system. care-2.2.1/doc/articles/extending_qemu.txt0000644000175000017500000002300012430615044020124 0ustar ivoireivoire.. include:: stylesheets/article-html.txt ================================================ Extending QEMU User-Mode with PRoot: Why and How ================================================ :Original Title: PRoot: a Step Forward for QEMU User-Mode :Published at: 1st International QEMU Users' Forum :Authors: Cédric Vincent & Yves Janin :PRoot Version: 0.5 :Abstract: This paper introduces PRoot, a new tool specifically designed to extend QEMU user-mode. We detail our motivations, compare with other similar solutions and give first results. Extending QEMU User-Mode ======================== QEMU [C1]_ is an open-source hardware emulator with two main usages: in *system-mode* a whole guest machine is emulated to run a full operating system. It can benefit from virtualization support like KVM to avoid the emulation of most CPU operations. By contrast, QEMU *user-mode* only emulates the CPU to run one guest process at a time, and communications with the host kernel are converted by a thin layer. Our initial motivation was to use QEMU to ease the development of embedded Linux applications by emulating their build system and test-suites directly on developers' workstations. During our experiments we found out that the system-mode was not fast enough and we decided to focus on the user-mode. Unfortunately this latter cannot execute a tree of guest processes within a guest Linux distribution for reasons we detail in the next section. To get the best of these two modes we developed PRoot, a tool fully compatible with QEMU that can be seen as an *extension* of the user-mode. Overcoming QEMU user-mode limitations with PRoot ------------------------------------------------ As illustrated by Figure 1 (a) and (b), QEMU should run faster for a given process in user-mode than in system-mode since it does not emulate the peripherals nor the guest kernel. However the boundary between the guest environment and the host becomes *thinner*, and the host kernel now has to support some of the operations that were devoted to the guest kernel. We can define three requirements that will guide us throughout the rest of this paper: R1 an efficient and correct path translation mechanism between the guest and the host. This feature is typically required to emulate programs that perform absolute accesses to architecture specific files such as the dynamic linker ``/lib/ld-linux.so``. Note that a correct translation scheme must support relative paths and symbolic links too. R2 the ability to spawn a new process and keep emulation running for this new process. R3 the ability to run a host process program. Strictly speaking R3 is not mandatory, but using *un-emulated* cross-compilers and interpreters is particularly useful to speed-up build process and test-suites. QEMU user-mode status for R1 and R2 is currently not satisfying. For R1, QEMU user-mode has the ability to redirect file operations made by the guest process but it can take several minutes at each process creation to scan the guest Linux distribution. Moreover there is no support for relative paths and symbolic links. Let us now consider R2. From the point-of-view of the host kernel, an emulated process image P1 is actually an image of QEMU |Q1P1|. When |Q1P1| executes a new program, the resultant process P2 is totally out of the control of QEMU. As a conclusion R2 is not supported either. .. |Q1P1| replace:: Q1\ :sub:`P1` .. figure:: extending_qemu-fig1.svg Figure 1: Guest process[es] running under QEMU, arrows are system calls flow The ambition of PRoot is to fulfill R1, R2 and later on R3, while keeping design, implementation and use as simple as possible. Since PRoot is also used for validation purpose, it has to reproduce kernel results and errors as if the emulated applications were run in a native environment. PRoot is based on ``ptrace``, a Linux system call [C2]_ that is mainly used by debuggers or system call tracers such as ``strace`` and the GNU Debugger ``gdb``. However since it provides unique facility to monitor and control processes, it can also be applied to a much wider range of applications bringing kernel-like features in user-space [C3]_ [C4]_. With ``ptrace``, PRoot is able to catch every system call emitted by a QEMUlated process and dynamically redirect them as shown in Figure 2. One of the main task in PRoot is the path canonicalization that translates paths from the guest world to the host world and vice versa (R1). Additionally, when PRoot detects a new process about to be created, it can change the initial request and get a new instance of QEMU running the new process (R2) as shown in Figure 1 (c). .. figure:: extending_qemu-fig2.svg Figure 2: Control flow of a syscall done by a guest process under QEMU+PRoot Results ======= Experiments presented in this section were performed on a Pentium-4 2.4GHz workstation with 1GiB of RAM running Slackware-13.1. Concerning the guest system, we used ARMedSlack-12.2 with QEMU-0.12.5 configured to provide 256MiB of RAM when used in system-mode. Functional Comparison With Similar Tools ---------------------------------------- Table 1 exemplifies two Linux packages that are representative of the complexity of an embedded Linux distribution; Perl-5 requires a guest environment for its bootstrap whereas the Coreutils test-suite exhibits numerous limit configurations that can help to find unexpected behaviors or bugs. Many path translation tools already exist but few pass these tests (R1). Fakechroot-2.14 and Fakeroot-Ng-0.17 fail. Plash is a modified GNU C library and was therefore not considered, and we did not consider ``chroot`` either since it requires administration privileges. Generally speaking, all these tools were not designed to support the execution of processes through an emulator (R2). Only PRoot and ScratchBox2 [C5]_ proved complete enough to configure, build and validate the two complex Linux packages. Table 1 gives the results of their test-suites run with PRoot and ScratchBox2, it also gives system-mode results as a reference. .. table:: Table 1: Results of the packages' test-suites ==================== =============== ========== =========== Package ScratchBox-2.2 PRoot-0.5 system-mode ==================== =============== ========== =========== Perl-5.10.0 99.6% 99.6% 99.8% GNU Coreutils-6.12 94.9% 97.3% 96.7% ==================== =============== ========== =========== ScratchBox2 and PRoot have similar path canonicalization algorithms but differ in their design [#]_ and usage. Unlike ScratchBox2, PRoot does not need any configuration and does not assume anything about the guest and host environments. By contrast, ScratchBox2 is a highly flexible tool with scripting facility. A complete technical comparison between ScratchBox2 and PRoot is beyond the scope of this paper, but we can safely conclude that both are functional enough to be already used in an industrial environment. Choosing one instead another one depends on your expectations: simplicity with PRoot vs powerful customization with ScratchBox2. .. [#] ScratchBox2 relies on the dynamic linker to override system call wrappers in the C library. Performance Comparison With QEMU System-Mode -------------------------------------------- Table 2 compares performance of QEMU-0.12.5, first in user-mode with PRoot-0.5 and then in system-mode. Timings were measured with the command ``time`` and we checked that the system-mode results were coherent with the host clock. .. table:: Table 2: Performance of PRoot+QEMU user-mode vs QEMU system-mode ================== =============== =============== stage Perl-5.10.0 Coreutils-6.12 ================== =============== =============== archive extraction 3.6x faster 2.7x faster configuration 2.0x faster 4.0x faster build 2.9x faster 3.5x faster validation 4.1x faster 3.6x faster ================== =============== =============== The speed-up with QEMU user-mode is quite impressive and comes from the fact that the guest kernel and the hardware are not emulated. We think that all these results will be further improved when PRoot handles *un-emulated* cross-compilers and interpreters (R3). This feature is still under development and looks promising. Conclusion ========== The simple fact that PRoot requires no privilege or setup is a great advantage that should ease the usage of this *extended* user-mode. It is worth mentioning that PRoot should work with any version of QEMU user-mode since no patch is needed, and in any Linux environment as well. Finally, the current version of PRoot is mature enough to be already used in an industrial environment. References ========== .. [C1] Bellard, F.: QEMU, a Fast and Portable Dynamic Translator. USENIX Annual Technical Conference, FREENIX Track (2005) 41--46 .. [C2] Kerrisk, M.: The Linux Programming Interface. No Starch Press (2010) 23, 43--46, 367--370 .. [C3] Spillane, R. P., Wright, C. P., Sivathanu, G., Zadok, E.: Rapid File System Development Using ptrace. Proceedings of the 2007 Workshop on Experimental Computer Science (ExpCS'07), 22. ACM (2007) .. [C4] Dike, J.: User-mode Linux. Proceedings of the 5th Annual Linux Showcase & Conference - Volume 5 (ALS'01). USENIX Association (2001) .. [C5] ScratchBox2. http://www.freedesktop.org/wiki/Software/sbox2 care-2.2.1/doc/articles/extending_qemu-fig1.svg0000644000175000017500000012460012430615044020740 0ustar ivoireivoire image/svg+xml process 1 QEMU PRoot host kernel ... process N QEMU process 1 QEMU host kernel host kernel ... QEMU process 1 guest kernel process N (a) system-mode (b) user-mode (c) user-mode + PRoot care-2.2.1/doc/articles/extending_qemu-fig2.svg0000644000175000017500000002405312430615044020742 0ustar ivoireivoire image/svg+xml QEMU: PRoot: host kernel: translation kernel operation system call result & errno t care-2.2.1/doc/articles/howto_migrate_from_scratchbox2.txt0000644000175000017500000000716612430615044023324 0ustar ivoireivoire.. include:: stylesheets/article-html.txt ======================================== How to migrate from Scratchbox2 to PRoot ======================================== :Author: Cédric Vincent :PRoot Version: 3.1 :Abstract: PRoot is designed with simplicity and consistency in mind, that's why most of the Scratchbox2's concepts can be simulated in PRoot with bindings and a wrapper only. Obviously the comparison between Scratchbox2 and PRoot is biased as I'm the maintainer of PRoot. Target ====== In Scratchbox2, "target" is a name for a configuration that specifies the path to the guest rootfs and the path to the cross-compiler. PRoot doesn't consider a cross-compiler like a special tool, thus the user is free to execute it just like any other host programs, either through the /host-rootfs binding (default when using QEMU): proot -q qemu-arm -r rootfs [...] $ /host-rootfs/opt/cross-tools/arm-linux-gcc [...] or through a user-specified binding: proot -b /opt/cross-tools/arm-linux-gcc:/usr/bin/gcc -q qemu-arm -r rootfs [...] $ /usr/bin/gcc Also, PRoot has no configuration support on purpose, this lets the user free to forge the command-line with any programming languages (shell, Perl, Python, ...). To me, this is more powerful than any configuration syntax. Execution Policy ================ In Scratchbox2, the view of the virtual file-system is automatically altered according to the executed program. Here is a quote from "Scratchbox2: Internals and Architecture": For example, /usr/lib/perl must be mapped to the target root when target's perl is running, but to the corresponding place in the tools directory [ed: where host tools are] when the other perl is running. In PRoot, all processes see exactly the same virtual file-system, there's *no exception* at all. For example, to use the host Perl the user can to adjust PERL5LIB explicitly or bind the host instance over the guest one: proot -b /usr/bin/perl $bind_perl_modules [...] where $bind_perl_modules is the output of the following command: perl -e 'print map { "-b $_ " } grep { m#^/# } @INC' Modes ===== Scratchbox2 comes with 3 predefined mapping rule sets ("emulate", "simple" and "accel") that define how programs are executed (emulated or natively) and what are the default bindings. PRoot has 2 predefined binding sets: "none" ("-r" option) or "recommended" ("-R" option). Regarding how a program is executed (emulated or natively), PRoot detects automatically if the binary is made for the host architecture or not, wherever it lies on the virtual file-system. Sessions ======== In Scratchbox2, a "session" is a "target" instance where the rules from a given "mode" (a.k.a mapping rule sets) are applied. Also, the /tmp directory is "private" to each session. About the "target" and "mode" support in PRoot, please refer to the sections above. About "private" directories, the user can simulate this feature by binding dedicated directories or files over "private" ones:: proot -b ~/tmp-session-01:/tmp [...] Network Namespace ================= Scratchbox2 can translate network addresses. This feature doesn't exist in PRoot but can be implemented easily, as an extension for instance. Permission Namespace ==================== Scratchbox2 (v2.4) has a permission engine that allows one to fake privileged operations, this engine is even more powerful than fakeroot's one. PRoot has a permission engine ("-0" option) that I want to keep as small as possible, its only purpose is to cheat package managers that does some [useless] sanity checks. Note that fakeroot can also be used under PRoot. care-2.2.1/AUTHORS0000644000175000017500000000176512430615044013062 0ustar ivoireivoireThe copyright holder for PRoot and CARE is STMicroelectronics, these tools are developed in the Compilation Expertise Center by: Cédric VINCENT Original author, maintainer. Rémi DURAFFORT Improved the -0 feature, many experiments, bug reports, fixes, and articles. Christophe GUILLON Improved CARE, plugin experiments, bug reports, and fixes. Yves JANIN Support for the i386 architecture, many experiments and bug reports. Antoine MOYNAULT Many experiments and bug reports. Claire ROBINE Many bug fixes. Clément BAZIN Plugins interface, dependency tracker plugin, and bug reports. Christian BERTIN Valuable support. Denis FERRANTI Valuable support. Paul GHALEB User manual review. care-2.2.1/COPYING0000644000175000017500000004325412430615044013044 0ustar ivoireivoire GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. care-2.2.1/src/0000755000175000017500000000000012430615044012570 5ustar ivoireivoirecare-2.2.1/src/extension/0000755000175000017500000000000012430615044014604 5ustar ivoireivoirecare-2.2.1/src/extension/extension.c0000644000175000017500000001023212430615044016762 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* assert(3), */ #include /* talloc_*, */ #include /* LIST_*, */ #include /* bzero(3), */ #include "extension/extension.h" #include "cli/notice.h" #include "build.h" #include "compat.h" /** * Remove an @extension from its tracee's list, then send it the * "REMOVED" event. * * Note: this is a Talloc destructor. */ static int remove_extension(Extension *extension) { LIST_REMOVE(extension, link); extension->callback(extension, REMOVED, 0, 0); bzero(extension, sizeof(Extension)); return 0; } /** * Allocate a new extension for the given @callback then attach it to * its @tracee. This function returns NULL on error, otherwise the * new extension. */ static Extension *new_extension(Tracee *tracee, extension_callback_t callback) { Extension *extension; /* Lazy allocation of the list head. */ if (tracee->extensions == NULL) { tracee->extensions = talloc_zero(tracee, Extensions); if (tracee->extensions == NULL) return NULL; } /* Allocate a new extension. */ extension = talloc_zero(tracee->extensions, Extension); if (extension == NULL) return NULL; extension->callback = callback; /* Attach it to its tracee. */ LIST_INSERT_HEAD(tracee->extensions, extension, link); talloc_set_destructor(extension, remove_extension); return extension; } /** * Initialize a new extension for the given @callback then attach it * to its @tracee. The parameter @cli is its argument that was passed * to the command-line interface. This function return -1 if an error * occurred, otherwise 0. */ int initialize_extension(Tracee *tracee, extension_callback_t callback, const char *cli) { Extension *extension; int status; extension = new_extension(tracee, callback); if (extension == NULL) { notice(tracee, WARNING, INTERNAL, "can't create a new extension"); return -1; } /* Remove the new extension if its initialized has failed. */ status = extension->callback(extension, INITIALIZATION, (intptr_t) cli, 0); if (status < 0) { TALLOC_FREE(extension); return status; } return 0; } /** * Rebuild a new list of extensions for this @child from its @parent. * The inheritance model is controlled by the @parent. */ void inherit_extensions(Tracee *child, Tracee *parent, word_t clone_flags) { Extension *parent_extension; Extension *child_extension; int status; if (parent->extensions == NULL) return; /* Sanity check. */ assert(child->extensions == NULL || clone_flags == CLONE_RECONF); LIST_FOREACH(parent_extension, parent->extensions, link) { /* Ask the parent how this extension is * inheritable. */ status = parent_extension->callback(parent_extension, INHERIT_PARENT, (intptr_t)child, clone_flags); /* Not inheritable. */ if (status < 0) continue; /* Inheritable... */ child_extension = new_extension(child, parent_extension->callback); if (child_extension == NULL) { notice(parent, WARNING, INTERNAL, "can't create a new extension for pid %d", child->pid); continue; } if (status == 0) { /* ... with a shared config or ... */ child_extension->config = talloc_reference(child_extension, parent_extension->config); } else { /* ... with another inheritance model. */ child_extension->callback(child_extension, INHERIT_CHILD, (intptr_t)parent_extension, clone_flags); } } } care-2.2.1/src/extension/kompat/0000755000175000017500000000000012430615044016077 5ustar ivoireivoirecare-2.2.1/src/extension/kompat/kompat.c0000644000175000017500000005510012430615044017537 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* intptr_t, */ #include /* strtoul(3), */ #include /* KERNEL_VERSION, */ #include /* assert(3), */ #include /* uname(2), utsname, */ #include /* strncpy(3), */ #include /* talloc_*, */ #include /* AT_*, */ #include /* linux.git:c0a3a20b */ #include /* errno, */ #include /* AT_, */ #include /* FUTEX_PRIVATE_FLAG */ #include "extension/extension.h" #include "syscall/seccomp.h" #include "syscall/sysnum.h" #include "syscall/chain.h" #include "tracee/tracee.h" #include "tracee/reg.h" #include "tracee/abi.h" #include "tracee/mem.h" #include "execve/auxv.h" #include "cli/notice.h" #include "arch.h" #include "attribute.h" #include "compat.h" #define MAX_ARG_SHIFT 2 typedef struct { int expected_release; word_t new_sysarg_num; struct { Reg sysarg; /* first argument to be moved. */ size_t nb_args; /* number of arguments to be moved. */ int offset; /* offset to be applied. */ } shifts[MAX_ARG_SHIFT]; } Modif; #define NONE {{0, 0, 0}} typedef struct { const char *release; int emulated_release; int actual_release; } Config; /** * Return whether the @expected_release is newer than * @config->actual_release and older than @config->emulated_release. */ static bool needs_kompat(const Config *config, int expected_release) { return (expected_release > config->actual_release && expected_release <= config->emulated_release); } /** * Modify the current syscall of @tracee as described by @modif * regarding the given @config. This function returns whether the * syscall was modified or not. */ static bool modify_syscall(Tracee *tracee, const Config *config, const Modif *modif) { size_t i, j; word_t syscall; assert(config != NULL); if (!needs_kompat(config, modif->expected_release)) return false; /* Check if this syscall is supported on this architecture. */ syscall = detranslate_sysnum(get_abi(tracee), modif->new_sysarg_num); if (syscall == SYSCALL_AVOIDER) return false; set_sysnum(tracee, modif->new_sysarg_num); /* Shift syscall arguments. */ for (i = 0; i < MAX_ARG_SHIFT; i++) { Reg sysarg = modif->shifts[i].sysarg; size_t nb_args = modif->shifts[i].nb_args; int offset = modif->shifts[i].offset; for (j = 0; j < nb_args; j++) { word_t arg = peek_reg(tracee, CURRENT, sysarg + j); poke_reg(tracee, sysarg + j + offset, arg); } } return true; } /** * Return the numeric value for the given kernel @release. */ static int parse_kernel_release(const char *release) { unsigned long major = 0; unsigned long minor = 0; unsigned long revision = 0; char *cursor = (char *)release; major = strtoul(cursor, &cursor, 10); if (*cursor == '.') { cursor++; minor = strtoul(cursor, &cursor, 10); } if (*cursor == '.') { cursor++; revision = strtoul(cursor, &cursor, 10); } return KERNEL_VERSION(major, minor, revision); } /** * Remove @discarded_flags from the given @tracee's @sysarg register * if the actual kernel release is not compatible with the * @expected_release. */ static void discard_fd_flags(Tracee *tracee, const Config *config, int discarded_flags, int expected_release, Reg sysarg) { word_t flags; if (!needs_kompat(config, expected_release)) return; flags = peek_reg(tracee, CURRENT, sysarg); poke_reg(tracee, sysarg, flags & ~discarded_flags); } /** * Replace current @tracee's syscall with an older and compatible one * whenever it's required, i.e. when the syscall is supported by the * kernel as specified by @config->release but it isn't supported by * the actual kernel. */ static int handle_sysenter_end(Tracee *tracee, Config *config) { /* Note: syscalls like "openat" can be replaced by "open" since PRoot * has canonicalized "fd + path" into "path". */ switch (get_sysnum(tracee, ORIGINAL)) { case PR_accept4: { Modif modif = { .expected_release = KERNEL_VERSION(2,6,28), .new_sysarg_num = PR_accept, .shifts = NONE }; modify_syscall(tracee, config, &modif); return 0; } case PR_dup3: { Modif modif = { .expected_release = KERNEL_VERSION(2,6,27), .new_sysarg_num = PR_dup2, .shifts = NONE }; /* "If oldfd equals newfd, then dup3() fails with the * error EINVAL" -- man dup3 */ if (peek_reg(tracee, CURRENT, SYSARG_1) == peek_reg(tracee, CURRENT, SYSARG_2)) return -EINVAL; modify_syscall(tracee, config, &modif); return 0; } case PR_epoll_create1: { bool modified; Modif modif = { .expected_release = KERNEL_VERSION(2,6,27), .new_sysarg_num = PR_epoll_create, .shifts = NONE }; /* "the size argument is ignored, but must be greater * than zero" -- man epoll_create */ modified = modify_syscall(tracee, config, &modif); if (modified) poke_reg(tracee, SYSARG_1, 1); return 0; } case PR_epoll_pwait: { Modif modif = { .expected_release = KERNEL_VERSION(2,6,19), .new_sysarg_num = PR_epoll_wait, .shifts = NONE }; modify_syscall(tracee, config, &modif); return 0; } case PR_eventfd2: { bool modified; word_t flags; Modif modif = { .expected_release = KERNEL_VERSION(2,6,27), .new_sysarg_num = PR_eventfd, .shifts = NONE }; modified = modify_syscall(tracee, config, &modif); if (modified) { /* EFD_SEMAPHORE can't be emulated with eventfd. */ flags = peek_reg(tracee, CURRENT, SYSARG_2); if ((flags & EFD_SEMAPHORE) != 0) return -EINVAL; } return 0; } case PR_faccessat: { Modif modif = { .expected_release = KERNEL_VERSION(2,6,16), .new_sysarg_num = PR_access, .shifts = { [0] = { .sysarg = SYSARG_2, .nb_args = 2, .offset = -1 } } }; modify_syscall(tracee, config, &modif); return 0; } case PR_fchmodat: { Modif modif = { .expected_release = KERNEL_VERSION(2,6,16), .new_sysarg_num = PR_chmod, .shifts = { [0] = { .sysarg = SYSARG_2, .nb_args = 2, .offset = -1 } } }; modify_syscall(tracee, config, &modif); return 0; } case PR_fchownat: { word_t flags; Modif modif = { .expected_release = KERNEL_VERSION(2,6,16), .shifts = { [0] = { .sysarg = SYSARG_2, .nb_args = 3, .offset = -1 } } }; flags = peek_reg(tracee, CURRENT, SYSARG_5); modif.new_sysarg_num = ((flags & AT_SYMLINK_NOFOLLOW) != 0 ? PR_lchown : PR_chown); modify_syscall(tracee, config, &modif); return 0; } case PR_fcntl: { word_t command; if (!needs_kompat(config, KERNEL_VERSION(2,6,24))) return 0; command = peek_reg(tracee, ORIGINAL, SYSARG_2); if (command == F_DUPFD_CLOEXEC) poke_reg(tracee, SYSARG_2, F_DUPFD); return 0; } case PR_newfstatat: case PR_fstatat64: { word_t flags; Modif modif = { .expected_release = KERNEL_VERSION(2,6,16), .shifts = { [0] = { .sysarg = SYSARG_2, .nb_args = 2, .offset = -1 } } }; flags = peek_reg(tracee, CURRENT, SYSARG_4); if ((flags & ~AT_SYMLINK_NOFOLLOW) != 0) return -EINVAL; /* Exposed by LTP. */ #if defined(ARCH_X86_64) if ((flags & AT_SYMLINK_NOFOLLOW) != 0) modif.new_sysarg_num = (get_abi(tracee) != ABI_2 ? PR_lstat : PR_lstat64); else modif.new_sysarg_num = (get_abi(tracee) != ABI_2 ? PR_stat : PR_stat64); #else if ((flags & AT_SYMLINK_NOFOLLOW) != 0) modif.new_sysarg_num = PR_lstat64; else modif.new_sysarg_num = PR_stat64; #endif modify_syscall(tracee, config, &modif); return 0; } case PR_futex: { word_t operation; static bool warned = false; if (!needs_kompat(config, KERNEL_VERSION(2,6,22)) || config->actual_release == 0) return 0; operation = peek_reg(tracee, CURRENT, SYSARG_2); if ((operation & FUTEX_PRIVATE_FLAG) == 0) return 0; if (!warned) { warned = true; notice(tracee, WARNING, USER, "kompat: this kernel doesn't support private futexes " "and PRoot can't emulate them. Expect some troubles..."); } poke_reg(tracee, SYSARG_2, operation & ~FUTEX_PRIVATE_FLAG); return 0; } case PR_futimesat: { Modif modif = { .expected_release = KERNEL_VERSION(2,6,16), .new_sysarg_num = PR_utimes, .shifts = { [0] = { .sysarg = SYSARG_2, .nb_args = 2, .offset = -1 } } }; modify_syscall(tracee, config, &modif); return 0; } case PR_inotify_init1: { Modif modif = { .expected_release = KERNEL_VERSION(2,6,27), .new_sysarg_num = PR_inotify_init, .shifts = NONE }; modify_syscall(tracee, config, &modif); return 0; } case PR_linkat: { word_t flags; Modif modif = { .expected_release = KERNEL_VERSION(2,6,16), .new_sysarg_num = PR_link, .shifts = { [0] = { .sysarg = SYSARG_2, .nb_args = 1, .offset = -1 }, [1] = { .sysarg = SYSARG_4, .nb_args = 1, .offset = -2 } } }; flags = peek_reg(tracee, CURRENT, SYSARG_5); if ((flags & ~AT_SYMLINK_FOLLOW) != 0) return -EINVAL; /* Exposed by LTP. */ modify_syscall(tracee, config, &modif); return 0; } case PR_mkdirat: { Modif modif = { .expected_release = KERNEL_VERSION(2,6,16), .new_sysarg_num = PR_mkdir, .shifts = { [0] = { .sysarg = SYSARG_2, .nb_args = 2, .offset = -1 } } }; modify_syscall(tracee, config, &modif); return 0; } case PR_mknodat: { Modif modif = { .expected_release = KERNEL_VERSION(2,6,16), .new_sysarg_num = PR_mknod, .shifts = { [0] = { .sysarg = SYSARG_2, .nb_args = 3, .offset = -1 } } }; modify_syscall(tracee, config, &modif); return 0; } case PR_openat: { bool modified; Modif modif = { .expected_release = KERNEL_VERSION(2,6,16), .new_sysarg_num = PR_open, .shifts = { [0] = { .sysarg = SYSARG_2, .nb_args = 3, .offset = -1 } } }; modified = modify_syscall(tracee, config, &modif); discard_fd_flags(tracee, config, O_CLOEXEC, KERNEL_VERSION(2,6,23), modified ? SYSARG_2 : SYSARG_3); return 0; } case PR_open: discard_fd_flags(tracee, config, O_CLOEXEC, KERNEL_VERSION(2,6,23), SYSARG_2); return 0; case PR_pipe2: { Modif modif = { .expected_release = KERNEL_VERSION(2,6,27), .new_sysarg_num = PR_pipe, .shifts = NONE }; modify_syscall(tracee, config, &modif); return 0; } case PR_pselect6: { Modif modif = { .expected_release = KERNEL_VERSION(2,6,16), .shifts = NONE }; #if defined(ARCH_X86_64) modif.new_sysarg_num = (get_abi(tracee) != ABI_2 ? PR_select : PR__newselect); #else modif.new_sysarg_num = PR__newselect; #endif modify_syscall(tracee, config, &modif); return 0; } case PR_readlinkat: { Modif modif = { .expected_release = KERNEL_VERSION(2,6,16), .new_sysarg_num = PR_readlink, .shifts = { [0] = { .sysarg = SYSARG_2, .nb_args = 3, .offset = -1} } }; modify_syscall(tracee, config, &modif); return 0; } case PR_renameat: { Modif modif = { .expected_release = KERNEL_VERSION(2,6,16), .new_sysarg_num = PR_rename, .shifts = { [0] = { .sysarg = SYSARG_2, .nb_args = 1, .offset =-1 }, [1] = { .sysarg = SYSARG_4, .nb_args = 1, .offset = -2 } } }; modify_syscall(tracee, config, &modif); return 0; } case PR_signalfd4: { bool modified; Modif modif = { .expected_release = KERNEL_VERSION(2,6,27), .new_sysarg_num = PR_signalfd, .shifts = NONE }; /* "In Linux up to version 2.6.26, the flags argument * is unused, and must be specified as zero." -- man * signalfd */ modified = modify_syscall(tracee, config, &modif); if (modified) poke_reg(tracee, SYSARG_4, 0); return 0; } case PR_socket: case PR_socketpair: case PR_timerfd_create: discard_fd_flags(tracee, config, O_CLOEXEC | O_NONBLOCK, KERNEL_VERSION(2,6,27), SYSARG_2); return 0; case PR_symlinkat: { Modif modif = { .expected_release = KERNEL_VERSION(2,6,16), .new_sysarg_num = PR_symlink, .shifts = { [0] = { .sysarg = SYSARG_3, .nb_args = 1, .offset = -1 } } }; modify_syscall(tracee, config, &modif); return 0; } case PR_unlinkat: { word_t flags; Modif modif = { .expected_release = KERNEL_VERSION(2,6,16), .shifts = { [0] = { .sysarg = SYSARG_2, .nb_args = 1, .offset = -1 } } }; flags = peek_reg(tracee, CURRENT, SYSARG_3); modif.new_sysarg_num = ((flags & AT_REMOVEDIR) != 0 ? PR_rmdir : PR_unlink); modify_syscall(tracee, config, &modif); return 0; } default: return 0; } } /** * Adjust some ELF auxiliary vectors to improve the compatibility. * This function assumes the "argv, envp, auxv" stuff is pointed to by * @tracee's stack pointer, as expected right after a successful call * to execve(2). */ static void adjust_elf_auxv(Tracee *tracee, Config *config) { ElfAuxVector *vectors; ElfAuxVector *vector; word_t vectors_address; word_t stack_pointer; void *argv_envp; size_t size; int status; vectors_address = get_elf_aux_vectors_address(tracee); if (vectors_address == 0) return; vectors = fetch_elf_aux_vectors(tracee, vectors_address); if (vectors == NULL) return; /* Discard AT_SYSINFO* vectors: they can be used to get the OS * release number from memory instead of from the uname * syscall, and only this latter is currently hooked by * PRoot. */ vector = find_elf_aux_vector(vectors, AT_SYSINFO_EHDR); if (vector != NULL) { vector->type = AT_IGNORE; vector->value = 0; } vector = find_elf_aux_vector(vectors, AT_SYSINFO); if (vector != NULL) { vector->type = AT_IGNORE; vector->value = 0; } /* Add the AT_RANDOM vector only if needed. */ if (!needs_kompat(config, KERNEL_VERSION(2,6,29))) goto end; vector = find_elf_aux_vector(vectors, AT_RANDOM); if (vector != NULL && config->actual_release != 0) goto end; status = add_elf_aux_vector(&vectors, AT_RANDOM, vectors_address); if (status < 0) return; /* Since a new vector needs to be added, the ELF auxiliary * vectors array can't be pushed in place. As a consequence, * argv[] and envp[] arrays are moved one vector downward to * make room for the new ELF auxiliary vectors array. * Remember, the stack layout is as follow right after execve: * * argv[], envp[], auxv[] */ stack_pointer = peek_reg(tracee, CURRENT, STACK_POINTER); size = vectors_address - stack_pointer; argv_envp = talloc_size(tracee->ctx, size); if (argv_envp == NULL) goto end; status = read_data(tracee, argv_envp, stack_pointer, size); if (status < 0) goto end; stack_pointer -= 2 * sizeof_word(tracee); status = write_data(tracee, stack_pointer, argv_envp, size); if (status < 0) goto end; /* The content of argv[] and env[] is now copied to its new * location; the stack pointer can be safely updated. */ poke_reg(tracee, STACK_POINTER, stack_pointer); vectors_address -= 2 * sizeof_word(tracee); end: push_elf_aux_vectors(tracee, vectors, vectors_address); return; } /** * Append to the @tracee's current syscall enough calls to fcntl(@fd) * in order to set the flags from the original @sysarg register, if * there are also set in @emulated_flags. */ static void emulate_fd_flags(Tracee *tracee, word_t fd, Reg sysarg, int emulated_flags) { word_t flags; flags = peek_reg(tracee, ORIGINAL, sysarg); if (flags == 0) return; if ((emulated_flags & flags & O_CLOEXEC) != 0) register_chained_syscall(tracee, PR_fcntl, fd, F_SETFD, FD_CLOEXEC, 0, 0, 0); if ((emulated_flags & flags & O_NONBLOCK) != 0) register_chained_syscall(tracee, PR_fcntl, fd, F_SETFL, O_NONBLOCK, 0, 0, 0); force_chain_final_result(tracee, peek_reg(tracee, CURRENT, SYSARG_RESULT)); } /** * Adjust the results/output parameters for syscalls that were * modified in handle_sysenter_end(). This function returns -errno if * an error occured, otherwise 0. */ static int handle_sysexit_end(Tracee *tracee, Config *config) { word_t result; word_t sysnum; int status; result = peek_reg(tracee, CURRENT, SYSARG_RESULT); sysnum = get_sysnum(tracee, ORIGINAL); /* Error reported by the kernel. */ status = (int) result; if (status < 0) return 0; switch (sysnum) { case PR_execve: adjust_elf_auxv(tracee, config); return 0; case PR_uname: { struct utsname utsname; word_t address; size_t size; address = peek_reg(tracee, ORIGINAL, SYSARG_1); status = read_data(tracee, &utsname, address, sizeof(utsname)); if (status < 0) return status; assert(config->release != NULL); /* Note: on x86_64, we can handle the two modes (32/64) with * the same code since struct utsname has always the same * layout. */ size = sizeof(utsname.release); strncpy(utsname.release, config->release, size); utsname.release[size - 1] = '\0'; status = write_data(tracee, address, &utsname, sizeof(utsname)); if (status < 0) return status; return 0; } case PR_accept4: if (get_sysnum(tracee, MODIFIED) == PR_accept) emulate_fd_flags(tracee, result, SYSARG_4, O_CLOEXEC | O_NONBLOCK); return 0; case PR_dup3: if (get_sysnum(tracee, MODIFIED) == PR_dup2) emulate_fd_flags(tracee, peek_reg(tracee, ORIGINAL, SYSARG_2), SYSARG_3, O_CLOEXEC | O_NONBLOCK); return 0; case PR_epoll_create1: if (get_sysnum(tracee, MODIFIED) == PR_epoll_create) emulate_fd_flags(tracee, result, SYSARG_1, O_CLOEXEC | O_NONBLOCK); return 0; case PR_eventfd2: if (get_sysnum(tracee, MODIFIED) == PR_eventfd) emulate_fd_flags(tracee, result, SYSARG_2, O_CLOEXEC | O_NONBLOCK); return 0; case PR_fcntl: { word_t command; if (!needs_kompat(config, KERNEL_VERSION(2,6,24))) return 0; command = peek_reg(tracee, ORIGINAL, SYSARG_2); if (command != F_DUPFD_CLOEXEC) return 0; register_chained_syscall(tracee, PR_fcntl, result, F_SETFD, FD_CLOEXEC, 0, 0, 0); force_chain_final_result(tracee, peek_reg(tracee, CURRENT, SYSARG_RESULT)); return 0; } case PR_inotify_init1: if (get_sysnum(tracee, MODIFIED) == PR_inotify_init) emulate_fd_flags(tracee, result, SYSARG_1, O_CLOEXEC | O_NONBLOCK); return 0; case PR_open: if (needs_kompat(config, KERNEL_VERSION(2,6,23))) emulate_fd_flags(tracee, result, SYSARG_2, O_CLOEXEC); return 0; case PR_openat: if (needs_kompat(config, KERNEL_VERSION(2,6,23))) emulate_fd_flags(tracee, result, SYSARG_3, O_CLOEXEC); return 0; case PR_pipe2: { int fds[2]; if (get_sysnum(tracee, MODIFIED) != PR_pipe) return 0; status = read_data(tracee, fds, peek_reg(tracee, MODIFIED, SYSARG_1), sizeof(fds)); if (status < 0) return 0; emulate_fd_flags(tracee, fds[0], SYSARG_2, O_CLOEXEC | O_NONBLOCK); emulate_fd_flags(tracee, fds[1], SYSARG_2, O_CLOEXEC | O_NONBLOCK); return 0; } case PR_signalfd4: if (get_sysnum(tracee, MODIFIED) == PR_signalfd) emulate_fd_flags(tracee, result, SYSARG_4, O_CLOEXEC | O_NONBLOCK); return 0; case PR_socket: case PR_timerfd_create: if (needs_kompat(config, KERNEL_VERSION(2,6,27))) emulate_fd_flags(tracee, result, SYSARG_2, O_CLOEXEC | O_NONBLOCK); return 0; case PR_socketpair: { int fds[2]; if (!needs_kompat(config, KERNEL_VERSION(2,6,27))) return 0; status = read_data(tracee, fds, peek_reg(tracee, MODIFIED, SYSARG_4), sizeof(fds)); if (status < 0) return 0; emulate_fd_flags(tracee, fds[0], SYSARG_2, O_CLOEXEC | O_NONBLOCK); emulate_fd_flags(tracee, fds[1], SYSARG_2, O_CLOEXEC | O_NONBLOCK); return 0; } default: return 0; } return 0; } /* List of syscalls handled by this extensions. */ static FilteredSysnum filtered_sysnums[] = { { PR_accept4, FILTER_SYSEXIT }, { PR_dup3, FILTER_SYSEXIT }, { PR_epoll_create1, FILTER_SYSEXIT }, { PR_epoll_pwait, 0 }, { PR_eventfd2, FILTER_SYSEXIT }, { PR_execve, FILTER_SYSEXIT }, { PR_faccessat, 0 }, { PR_fchmodat, 0 }, { PR_fchownat, 0 }, { PR_fcntl, FILTER_SYSEXIT }, { PR_fstatat64, 0 }, { PR_futimesat, 0 }, { PR_futex, 0 }, { PR_inotify_init1, FILTER_SYSEXIT }, { PR_linkat, 0 }, { PR_mkdirat, 0 }, { PR_mknodat, 0 }, { PR_newfstatat, 0 }, { PR_open, FILTER_SYSEXIT }, { PR_openat, FILTER_SYSEXIT }, { PR_pipe2, FILTER_SYSEXIT }, { PR_pselect6, 0 }, { PR_readlinkat, 0 }, { PR_renameat, 0 }, { PR_signalfd4, FILTER_SYSEXIT }, { PR_socket, FILTER_SYSEXIT }, { PR_socketpair, FILTER_SYSEXIT }, { PR_symlinkat, 0 }, { PR_timerfd_create, FILTER_SYSEXIT }, { PR_uname, FILTER_SYSEXIT }, { PR_unlinkat, 0 }, FILTERED_SYSNUM_END, }; /** * Handler for this @extension. It is triggered each time an @event * occured. See ExtensionEvent for the meaning of @data1 and @data2. */ int kompat_callback(Extension *extension, ExtensionEvent event, intptr_t data1, intptr_t data2 UNUSED) { int status; switch (event) { case INITIALIZATION: { struct utsname utsname; const char *release; Config *config; extension->config = talloc_zero(extension, Config); if (extension->config == NULL) return -1; config = extension->config; release = (const char *) data1; status = uname(&utsname); if (status < 0 || getenv("PROOT_FORCE_KOMPAT") != NULL || release == NULL) config->actual_release = 0; else config->actual_release = parse_kernel_release(utsname.release); config->release = talloc_strdup(config, release ?: utsname.release); if (config->release == NULL) return -1; talloc_set_name_const(config->release, "$release"); config->emulated_release = parse_kernel_release(config->release); extension->filtered_sysnums = filtered_sysnums; return 0; } case SYSCALL_ENTER_END: { Tracee *tracee = TRACEE(extension); Config *config = talloc_get_type_abort(extension->config, Config); /* Nothing to do if this syscall is being discarded * (because of an error detected by PRoot). */ if ((int) data1 < 0) return 0; return handle_sysenter_end(tracee, config); } case SYSCALL_EXIT_END: { Tracee *tracee = TRACEE(extension); Config *config = talloc_get_type_abort(extension->config, Config); return handle_sysexit_end(tracee, config); } default: return 0; } } care-2.2.1/src/extension/fake_id0/0000755000175000017500000000000012430615044016246 5ustar ivoireivoirecare-2.2.1/src/extension/fake_id0/fake_id0.c0000644000175000017500000005426212430615044020065 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* assert(3), */ #include /* intptr_t, */ #include /* E*, */ #include /* chmod(2), stat(2) */ #include /* uid_t, gid_t, get*id(2), */ #include /* get*id(2), */ #include /* linux.git:c0a3a20b */ #include /* AUDIT_ARCH_*, */ #include /* memcpy(3), */ #include /* strtol(3), */ #include /* AT_, */ #include "extension/extension.h" #include "syscall/syscall.h" #include "syscall/sysnum.h" #include "syscall/seccomp.h" #include "tracee/tracee.h" #include "tracee/abi.h" #include "tracee/mem.h" #include "execve/auxv.h" #include "path/binding.h" #include "arch.h" typedef struct { uid_t ruid; uid_t euid; uid_t suid; uid_t fsuid; gid_t rgid; gid_t egid; gid_t sgid; gid_t fsgid; } Config; typedef struct { char *path; mode_t mode; } ModifiedNode; /* List of syscalls handled by this extensions. */ static FilteredSysnum filtered_sysnums[] = { { PR_capset, FILTER_SYSEXIT }, { PR_chmod, FILTER_SYSEXIT }, { PR_chown, FILTER_SYSEXIT }, { PR_chown32, FILTER_SYSEXIT }, { PR_chroot, FILTER_SYSEXIT }, { PR_execve, FILTER_SYSEXIT }, { PR_fchmod, FILTER_SYSEXIT }, { PR_fchmodat, FILTER_SYSEXIT }, { PR_fchown, FILTER_SYSEXIT }, { PR_fchown32, FILTER_SYSEXIT }, { PR_fchownat, FILTER_SYSEXIT }, { PR_fstat, FILTER_SYSEXIT }, { PR_fstat, FILTER_SYSEXIT }, { PR_fstat64, FILTER_SYSEXIT }, { PR_fstatat64, FILTER_SYSEXIT }, { PR_getegid, FILTER_SYSEXIT }, { PR_getegid32, FILTER_SYSEXIT }, { PR_geteuid, FILTER_SYSEXIT }, { PR_geteuid32, FILTER_SYSEXIT }, { PR_getgid, FILTER_SYSEXIT }, { PR_getgid32, FILTER_SYSEXIT }, { PR_getgroups, FILTER_SYSEXIT }, { PR_getgroups32, FILTER_SYSEXIT }, { PR_getresgid, FILTER_SYSEXIT }, { PR_getresgid32, FILTER_SYSEXIT }, { PR_getresuid, FILTER_SYSEXIT }, { PR_getresuid32, FILTER_SYSEXIT }, { PR_getuid, FILTER_SYSEXIT }, { PR_getuid32, FILTER_SYSEXIT }, { PR_lchown, FILTER_SYSEXIT }, { PR_lchown32, FILTER_SYSEXIT }, { PR_lstat, FILTER_SYSEXIT }, { PR_lstat64, FILTER_SYSEXIT }, { PR_mknod, FILTER_SYSEXIT }, { PR_mknodat, FILTER_SYSEXIT }, { PR_newfstatat, FILTER_SYSEXIT }, { PR_oldlstat, FILTER_SYSEXIT }, { PR_oldstat, FILTER_SYSEXIT }, { PR_setfsgid, FILTER_SYSEXIT }, { PR_setfsgid32, FILTER_SYSEXIT }, { PR_setfsuid, FILTER_SYSEXIT }, { PR_setfsuid32, FILTER_SYSEXIT }, { PR_setgid, FILTER_SYSEXIT }, { PR_setgid32, FILTER_SYSEXIT }, { PR_setgroups, FILTER_SYSEXIT }, { PR_setgroups32, FILTER_SYSEXIT }, { PR_setregid, FILTER_SYSEXIT }, { PR_setregid32, FILTER_SYSEXIT }, { PR_setreuid, FILTER_SYSEXIT }, { PR_setreuid32, FILTER_SYSEXIT }, { PR_setresgid, FILTER_SYSEXIT }, { PR_setresgid32, FILTER_SYSEXIT }, { PR_setresuid, FILTER_SYSEXIT }, { PR_setresuid32, FILTER_SYSEXIT }, { PR_setuid, FILTER_SYSEXIT }, { PR_setuid32, FILTER_SYSEXIT }, { PR_setxattr, FILTER_SYSEXIT }, { PR_lsetxattr, FILTER_SYSEXIT }, { PR_fsetxattr, FILTER_SYSEXIT }, { PR_stat, FILTER_SYSEXIT }, { PR_stat64, FILTER_SYSEXIT }, { PR_statfs, FILTER_SYSEXIT }, { PR_statfs64, FILTER_SYSEXIT }, FILTERED_SYSNUM_END, }; /** * Restore the @node->mode for the given @node->path. * * Note: this is a Talloc destructor. */ static int restore_mode(ModifiedNode *node) { (void) chmod(node->path, node->mode); return 0; } /** * Force permissions of @path to "rwx" during the path translation of * current @tracee's syscall, in order to simulate CAP_DAC_OVERRIDE. * The original permissions are restored through talloc destructors. * See canonicalize() for the meaning of @is_final. */ static void override_permissions(const Tracee *tracee, const char *path, bool is_final) { ModifiedNode *node; struct stat perms; mode_t new_mode; int status; /* Get the meta-data */ status = stat(path, &perms); if (status < 0) return; /* Copy the current permissions */ new_mode = perms.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO); /* Add read and write permissions to everything. */ new_mode |= (S_IRUSR | S_IWUSR); /* Always add 'x' bit to directories */ if (S_ISDIR(perms.st_mode)) new_mode |= S_IXUSR; /* Patch the permissions only if needed. */ if (new_mode == (perms.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) return; node = talloc_zero(tracee->ctx, ModifiedNode); if (node == NULL) return; if (!is_final) { /* Restore the previous mode of any non final components. */ node->mode = perms.st_mode; } else { switch (get_sysnum(tracee, ORIGINAL)) { /* For chmod syscalls: restore the new mode of the final component. */ case PR_chmod: node->mode = peek_reg(tracee, ORIGINAL, SYSARG_2); break; case PR_fchmodat: node->mode = peek_reg(tracee, ORIGINAL, SYSARG_3); break; /* For stat syscalls: don't touch the mode of the final component. */ case PR_fstatat64: case PR_lstat: case PR_lstat64: case PR_newfstatat: case PR_oldlstat: case PR_oldstat: case PR_stat: case PR_stat64: case PR_statfs: case PR_statfs64: return; /* Otherwise: restore the previous mode of the final component. */ default: node->mode = perms.st_mode; break; } } node->path = talloc_strdup(node, path); if (node->path == NULL) { /* Keep only consistent nodes. */ TALLOC_FREE(node); return; } /* The mode restoration works because Talloc destructors are * called in reverse order. */ talloc_set_destructor(node, restore_mode); (void) chmod(path, new_mode); return; } /** * Adjust current @tracee's syscall parameters according to @config. * This function always returns 0. */ static int handle_sysenter_end(Tracee *tracee, const Config *config) { word_t sysnum; sysnum = get_sysnum(tracee, ORIGINAL); switch (sysnum) { case PR_setuid: case PR_setuid32: case PR_setgid: case PR_setgid32: case PR_setreuid: case PR_setreuid32: case PR_setregid: case PR_setregid32: case PR_setresuid: case PR_setresuid32: case PR_setresgid: case PR_setresgid32: case PR_setfsuid: case PR_setfsuid32: case PR_setfsgid: case PR_setfsgid32: /* These syscalls are fully emulated. */ set_sysnum(tracee, PR_void); return 0; case PR_chown: case PR_chown32: case PR_lchown: case PR_lchown32: case PR_fchown: case PR_fchown32: case PR_fchownat: { Reg uid_sysarg; Reg gid_sysarg; uid_t uid; gid_t gid; if (sysnum == PR_fchownat) { uid_sysarg = SYSARG_3; gid_sysarg = SYSARG_4; } else { uid_sysarg = SYSARG_2; gid_sysarg = SYSARG_3; } uid = peek_reg(tracee, ORIGINAL, uid_sysarg); gid = peek_reg(tracee, ORIGINAL, gid_sysarg); /* Swap actual and emulated ids to get a chance of * success. */ if (uid == config->ruid) poke_reg(tracee, uid_sysarg, getuid()); if (gid == config->rgid) poke_reg(tracee, gid_sysarg, getgid()); return 0; } case PR_setgroups: case PR_setgroups32: case PR_getgroups: case PR_getgroups32: /* TODO */ default: return 0; } /* Never reached */ assert(0); return 0; } /** * Copy config->@field to the tracee's memory location pointed to by @sysarg. */ #define POKE_MEM_ID(sysarg, field) do { \ poke_uint16(tracee, peek_reg(tracee, ORIGINAL, sysarg), config->field); \ if (errno != 0) \ return -errno; \ } while (0) /** * Emulate setuid(2) and setgid(2). */ #define SETXID(id) do { \ id ## _t id = peek_reg(tracee, ORIGINAL, SYSARG_1); \ bool allowed; \ \ /* "EPERM: The user is not privileged (does not have the \ * CAP_SETUID capability) and uid does not match the real UID \ * or saved set-user-ID of the calling process." -- man \ * setuid */ \ allowed = (config->euid == 0 /* TODO: || HAS_CAP(SETUID) */ \ || id == config->r ## id \ || id == config->e ## id \ || id == config->s ## id); \ if (!allowed) \ return -EPERM; \ \ /* "If the effective UID of the caller is root, the real UID \ * and saved set-user-ID are also set." -- man setuid */ \ if (config->e ## id == 0) { \ config->r ## id = id; \ config->s ## id = id; \ } \ \ /* "whenever the effective user ID is changed, fsuid will also \ * be changed to the new value of the effective user ID." -- \ * man setfsuid */ \ config->e ## id = id; \ config->fs ## id = id; \ \ poke_reg(tracee, SYSARG_RESULT, 0); \ return 0; \ } while (0) /** * Check whether @id is set or not. */ #define UNSET_ID(id) (id == (uid_t) -1) /** * Check whether @id is change or not. */ #define UNCHANGED_ID(id) (UNSET_ID(id) || id == config->id) /** * Emulate setreuid(2) and setregid(2). */ #define SETREXID(id) do { \ id ## _t r ## id = peek_reg(tracee, ORIGINAL, SYSARG_1); \ id ## _t e ## id = peek_reg(tracee, ORIGINAL, SYSARG_2); \ bool allowed; \ \ /* "Unprivileged processes may only set the effective user ID \ * to the real user ID, the effective user ID, or the saved \ * set-user-ID. \ * \ * Unprivileged users may only set the real user ID to the \ * real user ID or the effective user ID." \ * * "EPERM: The calling process is not privileged (does not \ * have the CAP_SETUID) and a change other than: \ * 1. swapping the effective user ID with the real user ID, \ * or; \ * 2. setting one to the value of the other, or ; \ * 3. setting the effective user ID to the value of the saved \ * set-user-ID \ * was specified." -- man setreuid \ * \ * Is it possible to "ruid <- euid" and "euid <- suid" at the \ * same time? */ \ allowed = (config->euid == 0 /* TODO: || HAS_CAP(SETUID) */ \ || (UNCHANGED_ID(e ## id) && UNCHANGED_ID(r ## id)) \ || (r ## id == config->e ## id && (e ## id == config->r ## id || UNCHANGED_ID(e ## id))) \ || (e ## id == config->r ## id && (r ## id == config->e ## id || UNCHANGED_ID(r ## id))) \ || (e ## id == config->s ## id && UNCHANGED_ID(r ## id))); \ if (!allowed) \ return -EPERM; \ \ /* "Supplying a value of -1 for either the real or effective \ * user ID forces the system to leave that ID unchanged. \ * [...] If the real user ID is set or the effective user ID \ * is set to a value not equal to the previous real user ID, \ * the saved set-user-ID will be set to the new effective user \ * ID." -- man setreuid */ \ if (!UNSET_ID(e ## id)) { \ if (e ## id != config->r ## id) \ config->s ## id = e ## id; \ \ config->e ## id = e ## id; \ config->fs ## id = e ## id; \ } \ \ /* Since it changes the current ruid value, this has to be \ * done after euid handling. */ \ if (!UNSET_ID(r ## id)) { \ if (!UNSET_ID(e ## id)) \ config->s ## id = e ## id; \ config->r ## id = r ## id; \ } \ \ poke_reg(tracee, SYSARG_RESULT, 0); \ return 0; \ } while (0) /** * Check if @var is equal to any config->r{@type}id's. */ #define EQUALS_ANY_ID(var, type) (var == config->r ## type ## id \ || var == config->e ## type ## id \ || var == config->s ## type ## id) /** * Emulate setresuid(2) and setresgid(2). */ #define SETRESXID(type) do { \ type ## id_t r ## type ## id = peek_reg(tracee, ORIGINAL, SYSARG_1); \ type ## id_t e ## type ## id = peek_reg(tracee, ORIGINAL, SYSARG_2); \ type ## id_t s ## type ## id = peek_reg(tracee, ORIGINAL, SYSARG_3); \ bool allowed; \ \ /* "Unprivileged user processes may change the real UID, \ * effective UID, and saved set-user-ID, each to one of: the \ * current real UID, the current effective UID or the current \ * saved set-user-ID. \ * \ * Privileged processes (on Linux, those having the CAP_SETUID \ * capability) may set the real UID, effective UID, and saved \ * set-user-ID to arbitrary values." -- man setresuid */ \ allowed = (config->euid == 0 /* || HAS_CAP(SETUID) */ \ || ((UNSET_ID(r ## type ## id) || EQUALS_ANY_ID(r ## type ## id, type)) \ && (UNSET_ID(e ## type ## id) || EQUALS_ANY_ID(e ## type ## id, type)) \ && (UNSET_ID(s ## type ## id) || EQUALS_ANY_ID(s ## type ## id, type)))); \ if (!allowed) \ return -EPERM; \ \ /* "If one of the arguments equals -1, the corresponding value \ * is not changed." -- man setresuid */ \ if (!UNSET_ID(r ## type ## id)) \ config->r ## type ## id = r ## type ## id; \ \ if (!UNSET_ID(e ## type ## id)) { \ /* "the file system UID is always set to the same \ * value as the (possibly new) effective UID." -- man \ * setresuid */ \ config->e ## type ## id = e ## type ## id; \ config->fs ## type ## id = e ## type ## id; \ } \ \ if (!UNSET_ID(s ## type ## id)) \ config->s ## type ## id = s ## type ## id; \ \ poke_reg(tracee, SYSARG_RESULT, 0); \ return 0; \ } while (0) /** * Emulate setfsuid(2) and setfsgid(2). */ #define SETFSXID(type) do { \ uid_t fs ## type ## id = peek_reg(tracee, ORIGINAL, SYSARG_1); \ uid_t old_fs ## type ## id = config->fs ## type ## id; \ bool allowed; \ \ /* "setfsuid() will succeed only if the caller is the \ * superuser or if fsuid matches either the real user ID, \ * effective user ID, saved set-user-ID, or the current value \ * of fsuid." -- man setfsuid */ \ allowed = (config->euid == 0 /* TODO: || HAS_CAP(SETUID) */ \ || fs ## type ## id == config->fs ## type ## id \ || EQUALS_ANY_ID(fs ## type ## id, type)); \ if (allowed) \ config->fs ## type ## id = fs ## type ## id; \ \ /* "On success, the previous value of fsuid is returned. On \ * error, the current value of fsuid is returned." -- man \ * setfsuid */ \ poke_reg(tracee, SYSARG_RESULT, old_fs ## type ## id); \ return 0; \ } while (0) /** * Adjust current @tracee's syscall result according to @config. This * function returns -errno if an error occured, otherwise 0. */ static int handle_sysexit_end(Tracee *tracee, Config *config) { word_t sysnum; word_t result; sysnum = get_sysnum(tracee, ORIGINAL); switch (sysnum) { case PR_setuid: case PR_setuid32: SETXID(uid); case PR_setgid: case PR_setgid32: SETXID(gid); case PR_setreuid: case PR_setreuid32: SETREXID(uid); case PR_setregid: case PR_setregid32: SETREXID(gid); case PR_setresuid: case PR_setresuid32: SETRESXID(u); case PR_setresgid: case PR_setresgid32: SETRESXID(g); case PR_setfsuid: case PR_setfsuid32: SETFSXID(u); case PR_setfsgid: case PR_setfsgid32: SETFSXID(g); case PR_getuid: case PR_getuid32: poke_reg(tracee, SYSARG_RESULT, config->ruid); return 0; case PR_getgid: case PR_getgid32: poke_reg(tracee, SYSARG_RESULT, config->rgid); return 0; case PR_geteuid: case PR_geteuid32: poke_reg(tracee, SYSARG_RESULT, config->euid); return 0; case PR_getegid: case PR_getegid32: poke_reg(tracee, SYSARG_RESULT, config->egid); return 0; case PR_getresuid: case PR_getresuid32: POKE_MEM_ID(SYSARG_1, ruid); POKE_MEM_ID(SYSARG_2, euid); POKE_MEM_ID(SYSARG_3, suid); return 0; case PR_getresgid: case PR_getresgid32: POKE_MEM_ID(SYSARG_1, rgid); POKE_MEM_ID(SYSARG_2, egid); POKE_MEM_ID(SYSARG_3, sgid); return 0; case PR_setgroups: case PR_setgroups32: case PR_mknod: case PR_mknodat: case PR_capset: case PR_setxattr: case PR_lsetxattr: case PR_fsetxattr: case PR_chmod: case PR_chown: case PR_fchmod: case PR_fchown: case PR_lchown: case PR_chown32: case PR_fchown32: case PR_lchown32: case PR_fchmodat: case PR_fchownat: { word_t result; /* Override only permission errors. */ result = peek_reg(tracee, CURRENT, SYSARG_RESULT); if ((int) result != -EPERM) return 0; /* Force success if the tracee was supposed to have * the capability. */ if (config->euid == 0) /* TODO: || HAS_CAP(...) */ poke_reg(tracee, SYSARG_RESULT, 0); return 0; } case PR_fstatat64: case PR_newfstatat: case PR_stat64: case PR_lstat64: case PR_fstat64: case PR_stat: case PR_lstat: case PR_fstat: { word_t address; Reg sysarg; uid_t uid; gid_t gid; /* Override only if it succeed. */ result = peek_reg(tracee, CURRENT, SYSARG_RESULT); if (result != 0) return 0; /* Get the address of the 'stat' structure. */ if (sysnum == PR_fstatat64 || sysnum == PR_newfstatat) sysarg = SYSARG_3; else sysarg = SYSARG_2; address = peek_reg(tracee, ORIGINAL, sysarg); /* Sanity checks. */ assert(__builtin_types_compatible_p(uid_t, uint32_t)); assert(__builtin_types_compatible_p(gid_t, uint32_t)); /* Get the uid & gid values from the 'stat' structure. */ uid = peek_uint32(tracee, address + offsetof_stat_uid(tracee)); if (errno != 0) uid = 0; /* Not fatal. */ gid = peek_uint32(tracee, address + offsetof_stat_gid(tracee)); if (errno != 0) gid = 0; /* Not fatal. */ /* Override only if the file is owned by the current user. * Errors are not fatal here. */ if (uid == getuid()) poke_uint32(tracee, address + offsetof_stat_uid(tracee), config->ruid); if (gid == getgid()) poke_uint32(tracee, address + offsetof_stat_gid(tracee), config->rgid); return 0; } case PR_chroot: { char path[PATH_MAX]; word_t input; int status; if (config->euid != 0) /* TODO: && !HAS_CAP(SYS_CHROOT) */ return 0; /* Override only permission errors. */ result = peek_reg(tracee, CURRENT, SYSARG_RESULT); if ((int) result != -EPERM) return 0; input = peek_reg(tracee, MODIFIED, SYSARG_1); status = read_path(tracee, path, input); if (status < 0) return status; /* Only "new rootfs == current rootfs" is supported yet. */ status = compare_paths(get_root(tracee), path); if (status != PATHS_ARE_EQUAL) return 0; /* Force success. */ poke_reg(tracee, SYSARG_RESULT, 0); return 0; } case PR_execve: { ElfAuxVector *vectors; ElfAuxVector *vector; word_t vectors_address; /* Override only if it succeed. */ result = peek_reg(tracee, CURRENT, SYSARG_RESULT); if ((int) result < 0) return 0; vectors_address = get_elf_aux_vectors_address(tracee); if (vectors_address == 0) return 0; vectors = fetch_elf_aux_vectors(tracee, vectors_address); if (vectors == NULL) return 0; vector = find_elf_aux_vector(vectors, AT_UID); if (vector != NULL) vector->value = config->ruid; vector = find_elf_aux_vector(vectors, AT_EUID); if (vector != NULL) vector->value = config->euid; vector = find_elf_aux_vector(vectors, AT_GID); if (vector != NULL) vector->value = config->rgid; vector = find_elf_aux_vector(vectors, AT_EGID); if (vector != NULL) vector->value = config->egid; push_elf_aux_vectors(tracee, vectors, vectors_address); return 0; } default: return 0; } } #undef POKE_MEM_ID #undef SETXID #undef UNSET_ID #undef UNCHANGED_ID #undef SETREXID #undef EQUALS_ANY_ID #undef SETRESXID #undef SETFSXID /** * Handler for this @extension. It is triggered each time an @event * occurred. See ExtensionEvent for the meaning of @data1 and @data2. */ int fake_id0_callback(Extension *extension, ExtensionEvent event, intptr_t data1, intptr_t data2) { switch (event) { case INITIALIZATION: { const char *uid_string = (const char *) data1; const char *gid_string; Config *config; int uid, gid; errno = 0; uid = strtol(uid_string, NULL, 10); if (errno != 0) uid = getuid(); gid_string = strchr(uid_string, ':'); if (gid_string == NULL) { errno = EINVAL; } else { errno = 0; gid = strtol(gid_string + 1, NULL, 10); } /* Fallback to the current gid if an error occured. */ if (errno != 0) gid = getgid(); extension->config = talloc(extension, Config); if (extension->config == NULL) return -1; config = talloc_get_type_abort(extension->config, Config); config->ruid = uid; config->euid = uid; config->suid = uid; config->fsuid = uid; config->rgid = gid; config->egid = gid; config->sgid = gid; config->fsgid = gid; extension->filtered_sysnums = filtered_sysnums; return 0; } case INHERIT_PARENT: /* Inheritable for sub reconfiguration ... */ return 1; case INHERIT_CHILD: { /* Copy the parent configuration to the child. The * structure should not be shared as uid/gid changes * in one process should not affect other processes. * This assertion is not true for POSIX threads * sharing the same group, however Linux threads never * share uid/gid information. As a consequence, the * GlibC emulates the POSIX behavior on Linux by * sending a signal to all group threads to cause them * to invoke the system call too. Finally, PRoot * doesn't have to worry about clone flags. */ Extension *parent = (Extension *) data1; extension->config = talloc_zero(extension, Config); if (extension->config == NULL) return -1; memcpy(extension->config, parent->config, sizeof(Config)); return 0; } case HOST_PATH: { Tracee *tracee = TRACEE(extension); Config *config = talloc_get_type_abort(extension->config, Config); /* Force permissions if the tracee was supposed to * have the capability. */ if (config->euid == 0) /* TODO: || HAS_CAP(DAC_OVERRIDE) */ override_permissions(tracee, (char*) data1, (bool) data2); return 0; } case SYSCALL_ENTER_END: { Tracee *tracee = TRACEE(extension); Config *config = talloc_get_type_abort(extension->config, Config); return handle_sysenter_end(tracee, config); } case SYSCALL_EXIT_END: { Tracee *tracee = TRACEE(extension); Config *config = talloc_get_type_abort(extension->config, Config); return handle_sysexit_end(tracee, config); } default: return 0; } } care-2.2.1/src/extension/care/0000755000175000017500000000000012430615044015516 5ustar ivoireivoirecare-2.2.1/src/extension/care/care.c0000644000175000017500000003376612430615044016613 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* struct stat, */ #include /* struct stat, */ #include /* lstat(2), */ #include /* PATH_MAX, */ #include /* strlen(3), */ #include /* assert(3), */ #include /* time(2), localtime(3), */ #include /* offsetof(3), */ #include /* talloc*, */ #include /* ut*, UT*, HASH*, */ #include /* STAILQ_*, */ #include /* PRI*, */ #include "extension/care/care.h" #include "extension/care/final.h" #include "extension/care/archive.h" #include "extension/extension.h" #include "tracee/tracee.h" #include "tracee/mem.h" #include "path/canon.h" #include "path/path.h" #include "path/binding.h" #include "cli/notice.h" /* Make uthash use talloc. */ #undef uthash_malloc #undef uthash_free #define uthash_malloc(size) talloc_size(care, size) #define uthash_free(pointer, size) TALLOC_FREE(pointer) /* Hash entry. */ typedef struct Entry { UT_hash_handle hh; char *path; } Entry; /** * Add a copy of @value at the end if the given @list. All the newly * talloc'ed elements (duplicated value, item, list head) are attached * to the given @context. This function returns NULL if an error * occurred, otherwise the newly talloc'ed item. */ Item *queue_item(TALLOC_CTX *context, List **list, const char *value) { Item *item; if (*list == NULL) { *list = talloc_zero(context, List); if (*list == NULL) return NULL; STAILQ_INIT(*list); } item = talloc_zero(*list, Item); if (item == NULL) return NULL; item->load = talloc_strdup(item, value); if (item->load == NULL) return NULL; STAILQ_INSERT_TAIL(*list, item, link); return item; } /** * Generate a valid archive @care->output from @care. */ static void generate_output_name(const Tracee *tracee, Care *care) { struct tm *splitted_time; time_t flat_time; flat_time = time(NULL); splitted_time = localtime(&flat_time); if (splitted_time == NULL) { notice(tracee, ERROR, INTERNAL, "can't generate a valid output name from the current time, " "please specify an ouput name explicitly"); return; } care->output = talloc_asprintf(care, "care-%02d%02d%02d%02d%02d%02d.%s", splitted_time->tm_year - 100, splitted_time->tm_mon + 1, splitted_time->tm_mday, splitted_time->tm_hour, splitted_time->tm_min, splitted_time->tm_sec, #if defined(CARE_BINARY_IS_PORTABLE) "bin" #else "raw" #endif ); if (care->output == NULL) { notice(tracee, ERROR, INTERNAL, "can't generate a valid output name from the current time, " "please specify an ouput name explicitly"); return; } } /** * Genereate @extension->config from @options. This function returns * -1 if an error ocurred, otherwise 0. */ static int generate_care(Extension *extension, const Options *options) { size_t suffix_length; const char *cursor; Tracee *tracee; Item *item2; Item *item; Care *care; tracee = TRACEE(extension); extension->config = talloc_zero(extension, Care); if (extension->config == NULL) return -1; care = extension->config; care->command = options->command; care->ipc_are_volatile = !options->ignore_default_config; if (options->output != NULL) care->output = talloc_strdup(care, options->output); else generate_output_name(tracee, care); if (care->output == NULL) { notice(tracee, WARNING, INTERNAL, "can't get output name"); return -1; } care->initial_cwd = talloc_strdup(care, tracee->fs->cwd); if (care->initial_cwd == NULL) { notice(tracee, WARNING, INTERNAL, "can't allocate cwd"); return -1; } care->archive = new_archive(care, tracee, care->output, &suffix_length); if (care->archive == NULL) return -1; cursor = strrchr(care->output, '/'); if (cursor == NULL || strlen(cursor) == 1) cursor = care->output; else cursor++; care->prefix = talloc_strndup(care, cursor, strlen(cursor) - suffix_length); if (care->prefix == NULL) { notice(tracee, WARNING, INTERNAL, "can't allocate archive prefix"); return -1; } /* Copy & canonicalize volatile paths. */ if (options->volatile_paths != NULL) { char path[PATH_MAX]; int status; STAILQ_FOREACH(item, options->volatile_paths, link) { /* Initial state before canonicalization. */ strcpy(path, "/"); status = canonicalize(tracee, (const char *) item->load, false, path, 0); if (status < 0) continue; /* Sanity check. */ if (strcmp(path, "/") == 0) { const char *string; const char *name; name = talloc_get_name(item); string = name == NULL || name[0] != '$' ? talloc_asprintf(tracee->ctx, "'%s'", (const char *) item->load) : talloc_asprintf(tracee->ctx, "'%s' (%s)", (const char *) item->load, name); notice(tracee, WARNING, USER, "path %s was declared volatile but it leads to '/', " "as a consequence it will *not* be considered volatile.", string); continue; } item2 = queue_item(care, &care->volatile_paths, path); if (item2 == NULL) continue; /* Preserve the non expanded form. */ talloc_set_name_const(item2, talloc_get_name(item)); VERBOSE(tracee, 1, "volatile path: %s", (const char *) item2->load); } } /* Copy volatile env. variables. */ if (options->volatile_envars != NULL) { STAILQ_FOREACH(item, options->volatile_envars, link) { item2 = queue_item(care, &care->volatile_envars, item->load); if (item2 == NULL) continue; VERBOSE(tracee, 1, "volatile envar: %s", (const char *) item2->load); } } /* Convert the limit from megabytes to bytes, as expected by * handle_host_path(). */ care->max_size = options->max_size * 1024 * 1024; /* handle_host_path() can now be safely used. */ care->is_ready = true; talloc_set_destructor(care, finalize_care); return 0; } /** * Add @path_ to the list of @care->concealed_accesses. This function * does *not* check for duplicated entries. */ static void register_concealed_access(const Tracee *tracee, Care *care, const char *path_) { char path[PATH_MAX]; size_t length; int status; length = strlen(path_); if (length >= PATH_MAX) return; memcpy(path, path_, length + 1); /* It was a concealed access if, and only if, the path was * part of a asymmetric binding. */ status = substitute_binding(tracee, HOST, path); if (status != 1) return; /* Do not register accesses that would not succeed even if the * path was revealed, i.e. the path does not exist at all. */ status = access(path, F_OK); if (status < 0) return; queue_item(care, &care->concealed_accesses, path); VERBOSE(tracee, 1, "concealed: %s", path); } /** * Archive @path if needed. */ static void handle_host_path(Extension *extension, const char *path) { struct stat statl; bool as_dentries; char *location; Tracee *tracee; Entry *entry; Care *care; int status; care = talloc_get_type_abort(extension->config, Care); tracee = TRACEE(extension); if (!care->is_ready) return; /* Don't archive if the path was already seen before. * This ensures the rootfs is re-created as it was * before any file creation or modification. */ HASH_FIND_STR(care->entries, path, entry); if (entry != NULL) return; switch (get_sysnum(tracee, ORIGINAL)) { case PR_getdents: case PR_getdents64: /* Don't archive if the dentry was already seen * before, it would be useless. */ HASH_FIND_STR(care->dentries, path, entry); if (entry != NULL) return; as_dentries = true; break; default: as_dentries = false; break; } entry = talloc_zero(care, Entry); if (entry == NULL) { notice(tracee, WARNING, INTERNAL, "can't allocate entry for '%s'", path); return; } entry->path = talloc_strdup(entry, path); if (entry->path == NULL) { notice(tracee, WARNING, INTERNAL, "can't allocate name for '%s'", path); return; } /* Remember this new entry. */ if (as_dentries) HASH_ADD_KEYPTR(hh, care->dentries, entry->path, strlen(entry->path), entry); else HASH_ADD_KEYPTR(hh, care->entries, entry->path, strlen(entry->path), entry); /* Don't use faccessat(2) here since it would require Linux >= * 2.6.16 and Glibc >= 2.4, whereas CARE is supposed to work * on any Linux 2.6 systems. */ status = lstat(path, &statl); if (status < 0) { register_concealed_access(tracee, care, path); return; } /* FIFOs and Unix domain sockets should be volatile. */ if (S_ISFIFO(statl.st_mode) || S_ISSOCK(statl.st_mode)) { if (care->ipc_are_volatile) { Item *item = queue_item(care, &care->volatile_paths, path); if (item != NULL) VERBOSE(tracee, 0, "volatile path: %s", path); else notice(tracee, WARNING, USER, "can't declare '%s' (fifo or socket) as volatile", path); return; } else notice(tracee, WARNING, USER, "'%1$s' might be explicitely declared volatile (-p %1$s)", path); } /* Don't archive the content of dentries, this save a lot of * space! */ if (as_dentries) statl.st_size = 0; if (care->volatile_paths != NULL) { Item *item; STAILQ_FOREACH(item, care->volatile_paths, link) { switch (compare_paths(item->load, path)) { case PATHS_ARE_EQUAL: /* It's a volatile path, archive it as * empty to preserve its dentry. */ statl.st_size = 0; break; case PATH1_IS_PREFIX: /* Don't archive it's a sub-part of a * volatile path. */ return; default: continue; } break; } } if (care->max_size >= 0 && statl.st_size > care->max_size) { notice(tracee, WARNING, USER, "file '%s' is archived with a null size since it is bigger than %" PRIi64 "MB, you can specify an alternate limit with the option -m.", path, care->max_size / 1024 / 1024); statl.st_size = 0; } /* Format the location within the archive. */ location = NULL; assert(path[0] == '/'); location = talloc_asprintf(tracee->ctx, "%s/rootfs%s", care->prefix, path); if (location == NULL) { notice(tracee, WARNING, INTERNAL, "can't allocate location for '%s'", path); return; } status = archive(tracee, care->archive, path, location, &statl); if (status == 0) VERBOSE(tracee, 1, "archived: %s", path); } typedef struct { uint32_t d_ino; uint32_t next; uint16_t size; char name[]; } Dirent32; typedef struct { uint64_t d_ino; uint64_t next; uint16_t size; char name[]; } Dirent64; typedef struct { uint64_t inode; int64_t next; uint16_t size; uint8_t type; char name[]; } NewDirent; /** * Archive all the entries returned by getdents syscalls. */ static void handle_getdents(Tracee *tracee, bool is_new_getdents) { char component[PATH_MAX]; char path[PATH_MAX]; uint64_t offset; int status; word_t result; word_t buffer; word_t fd; Dirent32 dirent32; Dirent64 dirent64; NewDirent new_dirent; result = peek_reg(tracee, CURRENT, SYSARG_RESULT); if ((int) result < 0) return; fd = peek_reg(tracee, ORIGINAL, SYSARG_1); buffer = peek_reg(tracee, ORIGINAL, SYSARG_2); offset = 0; while (offset < result) { word_t name_offset; word_t address; size_t size; address = buffer + offset; if (!is_new_getdents) { #if defined(ARCH_X86_64) const bool is_32bit = is_32on64_mode(tracee); #else const bool is_32bit = true; #endif if (is_32bit) { name_offset = offsetof(Dirent32, name); status = read_data(tracee, &dirent32, address, sizeof(dirent32)); size = dirent32.size; } else { name_offset = offsetof(Dirent64, name); status = read_data(tracee, &dirent64, address, sizeof(dirent64)); size = dirent64.size; } } else { name_offset = offsetof(NewDirent, name); status = read_data(tracee, &new_dirent, address, sizeof(new_dirent)); size = new_dirent.size; } if (status < 0) { notice(tracee, WARNING, INTERNAL, "can't read dentry"); break; } status = read_string(tracee, component, address + name_offset, PATH_MAX); if (status < 0 || status >= PATH_MAX) { notice(tracee, WARNING, INTERNAL, "can't read dentry" ); goto next; } /* Archive through the host_path notification. */ strcpy(path, "/"); translate_path(tracee, path, fd, component, false); next: offset += size; } if (offset != result) notice(tracee, WARNING, INTERNAL, "dentry table out of sync."); } /* List of syscalls handled by this extensions. */ static FilteredSysnum filtered_sysnums[] = { { PR_getdents, FILTER_SYSEXIT }, { PR_getdents64, FILTER_SYSEXIT }, FILTERED_SYSNUM_END, }; /** * Handler for this @extension. It is triggered each time an @event * occurred. See ExtensionEvent for the meaning of @data1 and @data2. */ int care_callback(Extension *extension, ExtensionEvent event, intptr_t data1, intptr_t data2 UNUSED) { Tracee *tracee; switch (event) { case INITIALIZATION: extension->filtered_sysnums = filtered_sysnums; return generate_care(extension, (Options *) data1); case NEW_STATUS: { int status = (int) data1; if (WIFEXITED(status)) { Care *care = talloc_get_type_abort(extension->config, Care); care->last_exit_status = WEXITSTATUS(status); } return 0; } case HOST_PATH: handle_host_path(extension, (const char *) data1); return 0; case SYSCALL_EXIT_START: tracee = TRACEE(extension); switch (get_sysnum(tracee, ORIGINAL)) { case PR_getdents: handle_getdents(tracee, false); break; case PR_getdents64: handle_getdents(tracee, true); break; default: break; } return 0; default: return 0; } } care-2.2.1/src/extension/care/care.h0000644000175000017500000000351112430615044016601 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef CARE_H #define CARE_H #include #include /* STAILQ_*, */ #include "extension/care/archive.h" /* Generic item for a STAILQ list. */ typedef struct item { const void *load; STAILQ_ENTRY(item) link; } Item; typedef STAILQ_HEAD(list, item) List; /* CARE CLI configuration. */ typedef struct { const char *output; char *const *command; List *concealed_paths; List *revealed_paths; List *volatile_paths; List *volatile_envars; bool ignore_default_config; int max_size; } Options; /* CARE internal configuration. */ typedef struct { struct Entry *entries; struct Entry *dentries; char *const *command; List *volatile_paths; List *volatile_envars; List *concealed_accesses; const char *prefix; const char *output; const char *initial_cwd; bool ipc_are_volatile; Archive *archive; int64_t max_size; int last_exit_status; bool is_ready; } Care; extern Item *queue_item(TALLOC_CTX *context, List **list, const char *value); #endif /* CARE_H */ care-2.2.1/src/extension/care/extract.h0000644000175000017500000000222712430615044017344 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef EXTRACT_H #define EXTRACT_H #include #include "attribute.h" #define AUTOEXTRACT_SIGNATURE "I_LOVE_PIZZA" typedef struct { char signature[sizeof(AUTOEXTRACT_SIGNATURE)]; uint64_t size; } PACKED AutoExtractInfo; extern int WEAK extract_archive_from_file(const char *path); #endif /* EXTRACT_H */ care-2.2.1/src/extension/care/final.h0000644000175000017500000000172612430615044016766 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef CARE_FINAL_H #define CARE_FINAL_H #include "care.h" extern int finalize_care(Care *care); #endif /* CARE_FINAL_H */ care-2.2.1/src/extension/care/extract.c0000644000175000017500000002155312430615044017342 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* open(2), fstat(2), lseek(2), */ #include /* open(2), fstat(2), */ #include /* open(2), */ #include /* open(2), */ #include /* *int*_t, *INT*_MAX, */ #include /* fstat(2), read(2), lseek(2), */ #include /* mmap(2), MAP_*, */ #include /* bool, true, false, */ #include /* assert(3), */ #include /* errno(3), */ #include /* strerror(3), */ #include /* PRI*, */ #include /* be64toh(3), */ #include /* archive_*(3), */ #include /* archive_entry*(3), */ #include "extension/care/extract.h" #include "cli/notice.h" /** * Extract the given @archive into the current working directory. * This function returns -1 if an error occured, otherwise 0. */ static int extract_archive(struct archive *archive) { struct archive_entry *entry; int result = 0; int status; int flags = ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_ACL | ARCHIVE_EXTRACT_FFLAGS | ARCHIVE_EXTRACT_XATTR; /* Avoid spurious warnings. One should test for the CAP_CHOWN * capability instead but libarchive only does this test: */ if (geteuid() == 0) flags |= ARCHIVE_EXTRACT_OWNER; while (archive_read_next_header(archive, &entry) == ARCHIVE_OK) { status = archive_read_extract(archive, entry, flags); switch (status) { case ARCHIVE_OK: notice(NULL, INFO, USER, "extracted: %s", archive_entry_pathname(entry)); break; default: result = -1; notice(NULL, ERROR, INTERNAL, "%s: %s", archive_error_string(archive), strerror(archive_errno(archive))); break; } } return result; } /* Data used by archive_[open/read/close] callbacks. */ typedef struct { uint8_t buffer[4096]; const char *path; size_t size; int fd; } CallbackData; /** * This callback is invoked by archive_open(). It returns ARCHIVE_OK * if the underlying file or data source is successfully opened. If * the open fails, it calls archive_set_error() to register an error * code and message and returns ARCHIVE_FATAL. * * -- man 3 archive_read_open. */ static int open_callback(struct archive *archive, void *data_) { CallbackData *data = talloc_get_type_abort(data_, CallbackData); AutoExtractInfo info; struct stat statf; off_t offset; int status; /* Note: data->fd will be closed by close_callback(). */ data->fd = open(data->path, O_RDONLY); if (data->fd < 0) { archive_set_error(archive, errno, "can't open archive"); return ARCHIVE_FATAL; } status = fstat(data->fd, &statf); if (status < 0) { archive_set_error(archive, errno, "can't stat archive"); return ARCHIVE_FATAL; } /* Assume it's a regular archive if it physically can't be a * self-extracting one. */ if (statf.st_size < (off_t) sizeof(AutoExtractInfo)) return ARCHIVE_OK; offset = lseek(data->fd, statf.st_size - sizeof(AutoExtractInfo), SEEK_SET); if (offset == (off_t) -1) { archive_set_error(archive, errno, "can't seek in archive"); return ARCHIVE_FATAL; } status = read(data->fd, &info, sizeof(AutoExtractInfo)); if (status < 0) { archive_set_error(archive, errno, "can't read archive"); return ARCHIVE_FATAL; } if ( status == sizeof(AutoExtractInfo) && strcmp(info.signature, AUTOEXTRACT_SIGNATURE) == 0) { /* This is a self-extracting archive, retrive it's * offset and size. */ data->size = be64toh(info.size); offset = statf.st_size - data->size - sizeof(AutoExtractInfo); notice(NULL, INFO, USER, "archive found: offset = %" PRIu64 ", size = %" PRIu64 "", (uint64_t) offset, data->size); } else { /* This is not a self-extracting archive, assume it's * a regular one... */ offset = 0; /* ... unless a self-extracting archive really was * expected. */ if (strcmp(data->path, "/proc/self/exe") == 0) return ARCHIVE_FATAL; } offset = lseek(data->fd, offset, SEEK_SET); if (offset == (off_t) -1) { archive_set_error(archive, errno, "can't seek in archive"); return ARCHIVE_FATAL; } return ARCHIVE_OK; } /** * This callback is invoked whenever the library requires raw bytes * from the archive. The read callback reads data into a buffer, set * the @buffer argument to point to the available data, and return a * count of the number of bytes available. The library will invoke * the read callback again only after it has consumed this data. The * library imposes no constraints on the size of the data blocks * returned. On end-of-file, the read callback returns zero. On * error, the read callback should invoke archive_set_error() to * register an error code and message and returns -1. * * -- man 3 archive_read_open. */ static ssize_t read_callback(struct archive *archive, void *data_, const void **buffer) { CallbackData *data = talloc_get_type_abort(data_, CallbackData); ssize_t size; size = read(data->fd, data->buffer, sizeof(data->buffer)); if (size < 0) { archive_set_error(archive, errno, "can't read archive"); return -1; } *buffer = data->buffer; return size; } /** * This callback is invoked by archive_close() when the archive * processing is complete. The callback returns ARCHIVE_OK on * success. On failure, the callback invokes archive_set_error() to * register an error code and message and returns ARCHIVE_FATAL. * * -- man 3 archive_read_open */ static int close_callback(struct archive *archive, void *data_) { CallbackData *data = talloc_get_type_abort(data_, CallbackData); int status; status = close(data->fd); if (status < 0) { archive_set_error(archive, errno, "can't close archive"); return ARCHIVE_WARN; } return ARCHIVE_OK; } /** * Extract the archive stored at the given @path. This function * returns -1 if an error occurred, otherwise 0. */ int extract_archive_from_file(const char *path) { struct archive *archive = NULL; CallbackData *data = NULL; int status2; int status; archive = archive_read_new(); if (archive == NULL) { notice(NULL, ERROR, INTERNAL, "can't initialize archive structure"); status = -1; goto end; } status = archive_read_support_format_cpio(archive); if (status != ARCHIVE_OK) { notice(NULL, ERROR, INTERNAL, "can't set archive format: %s", archive_error_string(archive)); status = -1; goto end; } status = archive_read_support_format_gnutar(archive); if (status != ARCHIVE_OK) { notice(NULL, ERROR, INTERNAL, "can't set archive format: %s", archive_error_string(archive)); status = -1; goto end; } status = archive_read_support_filter_gzip(archive); if (status != ARCHIVE_OK) { notice(NULL, ERROR, INTERNAL, "can't add archive filter: %s", archive_error_string(archive)); status = -1; goto end; } status = archive_read_support_filter_lzop(archive); if (status != ARCHIVE_OK) { notice(NULL, ERROR, INTERNAL, "can't add archive filter: %s", archive_error_string(archive)); status = -1; goto end; } data = talloc_zero(NULL, CallbackData); if (data == NULL) { notice(NULL, ERROR, INTERNAL, "can't allocate callback data"); status = -1; goto end; } data->path = talloc_strdup(data, path); if (data->path == NULL) { notice(NULL, ERROR, INTERNAL, "can't allocate callback data path"); status = -1; goto end; } status = archive_read_open(archive, data, open_callback, read_callback, close_callback); if (status != ARCHIVE_OK) { /* Don't complain if no error message were registered, * ie. when testing for a self-extracting archive. */ if (archive_error_string(archive) != NULL) notice(NULL, ERROR, INTERNAL, "can't read archive: %s", archive_error_string(archive)); status = -1; goto end; } status = extract_archive(archive); end: if (archive != NULL) { status2 = archive_read_close(archive); if (status2 != ARCHIVE_OK) { notice(NULL, WARNING, INTERNAL, "can't close archive: %s", archive_error_string(archive)); } status2 = archive_read_free(archive); if (status2 != ARCHIVE_OK) { notice(NULL, WARNING, INTERNAL, "can't free archive: %s", archive_error_string(archive)); } } TALLOC_FREE(data); return status; } care-2.2.1/src/extension/care/archive.c0000644000175000017500000003422212430615044017306 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* open(2), lseek(2), */ #include /* open(2), */ #include /* open(2), */ #include /* read(2), readlink(2), close(2), lseek(2), */ #include /* errno, EACCES, */ #include /* assert(3), */ #include /* PATH_MAX, */ #include /* strlen(3), strcmp(3), */ #include /* bool, true, false, */ #include /* talloc(3), */ #include /* archive_*(3), */ #include /* archive_entry*(3), */ #include "extension/care/archive.h" #include "tracee/tracee.h" #include "cli/notice.h" typedef struct { int (*set_format)(struct archive *); int (*add_filter)(struct archive *); int hardlink_resolver_strategy; const char *options; enum { NOT_SPECIAL = 0, SELF_EXTRACTING, RAW } special; } Format; /** * Move *@cursor backward -- within in the given @string -- if it * reads @suffix once moved. */ static bool slurp_suffix(const char *string, const char **cursor, const char *suffix) { size_t length; length = strlen(suffix); if (*cursor - length < string || strncmp(*cursor - length, suffix, length) != 0) return false; *cursor -= length; return true; } /** * Detect the expected format for the given @string. This function * returns -1 if an error occurred, otherwise it returns 0 and updates * the @format structure and @suffix_length with the number of * characters that describes the parsed format. */ static int parse_suffix(const Tracee* tracee, Format *format, const char *string, size_t *suffix_length) { const char *cursor; bool found; bool no_wrapper_found = false; bool no_filter_found = false; bool no_format_found = false; cursor = string + strlen(string); bzero(format, sizeof(Format)); /* parse_special: */ found = slurp_suffix(string, &cursor, "/"); if (found) goto end; found = slurp_suffix(string, &cursor, ".raw"); if (found) { format->special = SELF_EXTRACTING; goto parse_filter; } found = slurp_suffix(string, &cursor, ".bin"); if (found) { #if defined(CARE_BINARY_IS_PORTABLE) format->special = SELF_EXTRACTING; goto parse_filter; #else notice(tracee, ERROR, USER, "This version of CARE was built " "without self-extracting (.bin) support"); return -1; #endif } no_wrapper_found = true; parse_filter: found = slurp_suffix(string, &cursor, ".gz"); if (found) { format->add_filter = archive_write_add_filter_gzip; format->options = "gzip:compression-level=1"; goto parse_format; } found = slurp_suffix(string, &cursor, ".lzo"); if (found) { format->add_filter = archive_write_add_filter_lzop; format->options = "lzop:compression-level=1"; goto parse_format; } found = slurp_suffix(string, &cursor, ".tgz"); if (found) { format->add_filter = archive_write_add_filter_gzip; format->options = "gzip:compression-level=1"; format->set_format = archive_write_set_format_gnutar; format->hardlink_resolver_strategy = ARCHIVE_FORMAT_TAR_GNUTAR; goto sanity_checks; } found = slurp_suffix(string, &cursor, ".tzo"); if (found) { format->add_filter = archive_write_add_filter_lzop; format->options = "lzop:compression-level=1"; format->set_format = archive_write_set_format_gnutar; format->hardlink_resolver_strategy = ARCHIVE_FORMAT_TAR_GNUTAR; goto sanity_checks; } no_filter_found = true; parse_format: found = slurp_suffix(string, &cursor, ".cpio"); if (found) { format->set_format = archive_write_set_format_cpio; format->hardlink_resolver_strategy = ARCHIVE_FORMAT_CPIO_POSIX; goto sanity_checks; } found = slurp_suffix(string, &cursor, ".tar"); if (found) { format->set_format = archive_write_set_format_gnutar; format->hardlink_resolver_strategy = ARCHIVE_FORMAT_TAR_GNUTAR; goto sanity_checks; } no_format_found = true; sanity_checks: if (no_filter_found && no_format_found) { format->add_filter = archive_write_add_filter_lzop; format->options = "lzop:compression-level=1"; format->set_format = archive_write_set_format_gnutar; format->hardlink_resolver_strategy = ARCHIVE_FORMAT_TAR_GNUTAR; #if defined(CARE_BINARY_IS_PORTABLE) format->special = SELF_EXTRACTING; if (no_wrapper_found) notice(tracee, WARNING, USER, "unknown suffix, assuming self-extracting format."); #else format->special = RAW; if (no_wrapper_found) notice(tracee, WARNING, USER, "unknown suffix, assuming raw format."); #endif no_wrapper_found = false; no_filter_found = false; no_format_found = false; } if (no_format_found) { notice(tracee, WARNING, USER, "unknown format, assuming tar format."); format->set_format = archive_write_set_format_gnutar; format->hardlink_resolver_strategy = ARCHIVE_FORMAT_TAR_GNUTAR; no_format_found = false; } end: *suffix_length = strlen(cursor); return 0; } /** * Copy "/proc/self/exe" into @destination. This function returns -1 * if an error occured, otherwise the file descriptor of the * destination. */ static int copy_self_exe(const Tracee *tracee, const char *destination) { int output_fd; int input_fd; int status; input_fd = open("/proc/self/exe", O_RDONLY); if (input_fd < 0) { notice(tracee, ERROR, SYSTEM, "can't open '/proc/self/exe'"); return -1; } output_fd = open(destination, O_RDWR|O_CREAT|O_TRUNC, S_IRWXU|S_IRGRP|S_IXGRP); if (output_fd < 0) { notice(tracee, ERROR, SYSTEM, "can't open/create '%s'", destination); status = -1; goto end; } while (1) { uint8_t buffer[4 * 1024]; ssize_t size; status = read(input_fd, buffer, sizeof(buffer)); if (status < 0) { notice(tracee, ERROR, SYSTEM, "can't read '/proc/self/exe'"); goto end; } if (status == 0) break; size = status; status = write(output_fd, buffer, size); if (status < 0) { notice(tracee, ERROR, SYSTEM, "can't write '%s'", destination); goto end; } if (status != size) notice(tracee, WARNING, INTERNAL, "wrote %zd bytes instead of %zd", (size_t) status, size); } end: (void) close(input_fd); if (status < 0) { (void) close(output_fd); return -1; } return output_fd; } /** * Create a new archive structure (memory allocation attached to * @context) for the given @output file. This function returns NULL * on error, otherwise the newly allocated archive structure. See * parse_suffix() for the meaning of @suffix_length. */ Archive *new_archive(TALLOC_CTX *context, const Tracee* tracee, const char *output, size_t *suffix_length) { Format format; Archive *archive; int status; assert(output != NULL); status = parse_suffix(tracee, &format, output, suffix_length); if (status < 0) return NULL; archive = talloc_zero(context, Archive); if (archive == NULL) { notice(tracee, ERROR, INTERNAL, "can't allocate archive structure"); return NULL; } archive->fd = -1; /* No format was set, content will be copied into a directory * instead of being archived. */ if (format.set_format == NULL) { int flags = ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_ACL | ARCHIVE_EXTRACT_FFLAGS | ARCHIVE_EXTRACT_XATTR | (geteuid() == 0 ? ARCHIVE_EXTRACT_OWNER : 0); archive->handle = archive_write_disk_new(); if (archive->handle == NULL) { notice(tracee, WARNING, INTERNAL, "can't initialize archive structure"); return NULL; } status = archive_write_disk_set_options(archive->handle, flags); if (status != ARCHIVE_OK) { notice(tracee, ERROR, INTERNAL, "can't set archive options: %s", archive_error_string(archive->handle)); return NULL; } status = archive_write_disk_set_standard_lookup(archive->handle); if (status != ARCHIVE_OK) { notice(tracee, ERROR, INTERNAL, "can't set archive lookup: %s", archive_error_string(archive->handle)); return NULL; } archive->hardlink_resolver = archive_entry_linkresolver_new(); if (archive->hardlink_resolver != NULL) archive_entry_linkresolver_set_strategy(archive->hardlink_resolver, ARCHIVE_FORMAT_TAR); return archive; } archive->handle = archive_write_new(); if (archive->handle == NULL) { notice(tracee, WARNING, INTERNAL, "can't initialize archive structure"); return NULL; } assert(format.set_format != NULL); status = format.set_format(archive->handle); if (status != ARCHIVE_OK) { notice(tracee, ERROR, INTERNAL, "can't set archive format: %s", archive_error_string(archive->handle)); return NULL; } if (format.hardlink_resolver_strategy != 0) { archive->hardlink_resolver = archive_entry_linkresolver_new(); if (archive->hardlink_resolver != NULL) archive_entry_linkresolver_set_strategy(archive->hardlink_resolver, format.hardlink_resolver_strategy); } if (format.add_filter != NULL) { status = format.add_filter(archive->handle); if (status != ARCHIVE_OK) { notice(tracee, ERROR, INTERNAL, "can't add archive filter: %s", archive_error_string(archive->handle)); return NULL; } } if (format.options != NULL) { status = archive_write_set_options(archive->handle, format.options); if (status != ARCHIVE_OK) { notice(tracee, ERROR, INTERNAL, "can't set archive options: %s", archive_error_string(archive->handle)); return NULL; } } switch (format.special) { case SELF_EXTRACTING: archive->fd = copy_self_exe(tracee, output); if (archive->fd < 0) return NULL; /* Remember where the CARE binary ends. */ archive->offset = lseek(archive->fd, 0, SEEK_CUR); status = archive_write_open_fd(archive->handle, archive->fd); break; case RAW: archive->fd = open(output, O_RDWR|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR|S_IRGRP); if (archive->fd < 0) { notice(tracee, ERROR, SYSTEM, "can't open/create '%s'", output); return NULL; } status = write(archive->fd, "RAW", strlen("RAW")); if (status != strlen("RAW")) { notice(tracee, ERROR, SYSTEM, "can't write '%s'", output); (void) close(archive->fd); return NULL; } /* Remember where the "RAW" string ends. */ archive->offset = lseek(archive->fd, 0, SEEK_CUR); status = archive_write_open_fd(archive->handle, archive->fd); break; default: status = archive_write_open_filename(archive->handle, output); break; } if (status != ARCHIVE_OK) { notice(tracee, ERROR, INTERNAL, "can't open archive '%s': %s", output, archive_error_string(archive->handle)); return NULL; } return archive; } /** * Finalize the given @archive. This function returns -1 if an error * occurred, otherwise 0. */ int finalize_archive(Archive *archive) { int status; if (archive == NULL || archive->handle == NULL) return -1; if (archive->hardlink_resolver != NULL) archive_entry_linkresolver_free(archive->hardlink_resolver); status = archive_write_close(archive->handle); if (status != ARCHIVE_OK) return -1; status = archive_write_free(archive->handle); if (status != ARCHIVE_OK) return -1; return 0; } /** * Put the content of @path into @archive, with the specified @statl * status, at the given @alternate_path (NULL if unchanged). This * function returns -1 if an error occurred, otherwise 0. Note: this * function can be called with @tracee == NULL. */ int archive(const Tracee* tracee, Archive *archive, const char *path, const char *alternate_path, const struct stat *statl) { struct archive_entry *entry = NULL; ssize_t status; mode_t type; size_t size; int fd = -1; if (archive == NULL || archive->handle == NULL) return -1; entry = archive_entry_new(); if (entry == NULL) { notice(tracee, WARNING, INTERNAL, "can't create archive entry for '%s': %s", path, archive_error_string(archive->handle)); status = -1; goto end; } archive_entry_set_pathname(entry, alternate_path ?: path); archive_entry_copy_stat(entry, statl); if (archive->hardlink_resolver != NULL) { struct archive_entry *unused; archive_entry_linkify(archive->hardlink_resolver, &entry, &unused); } /* Get status only once hardlinks were resolved. */ size = archive_entry_size(entry); type = archive_entry_filetype(entry); if (type == AE_IFLNK) { char target[PATH_MAX]; status = readlink(path, target, PATH_MAX); if (status >= PATH_MAX) { status = -1; errno = ENAMETOOLONG; } if (status < 0) { notice(tracee, WARNING, SYSTEM, "can't readlink '%s'", path); status = -1; goto end; } target[status] = '\0'; /* Must be done before archive_write_header(). */ archive_entry_set_symlink(entry, target); } status = archive_write_header(archive->handle, entry); if (status != ARCHIVE_OK) { notice(tracee, WARNING, INTERNAL, "can't write header for '%s': %s", path, archive_error_string(archive->handle)); status = -1; goto end; } /* No content to archive? */ if (type != AE_IFREG || size == 0) { status = 0; goto end; } fd = open(path, O_RDONLY); if (fd < 0) { if (errno != EACCES) notice(tracee, WARNING, SYSTEM, "can't open '%s'", path); status = -1; goto end; } /* Copy the content from the file into the archive. */ do { uint8_t buffer[4096]; status = read(fd, buffer, sizeof(buffer)); if (status < 0) { notice(tracee, WARNING, SYSTEM, "can't read '%s'", path); status = -1; goto end; } size = archive_write_data(archive->handle, buffer, status); if ((size_t) status != size) { notice(tracee, WARNING, INTERNAL, "can't archive '%s' content: %s", path, archive_error_string(archive->handle)); status = -1; goto end; } } while (status > 0); status = 0; end: if (fd >= 0) (void) close(fd); if (entry != NULL) archive_entry_free(entry); return status; } care-2.2.1/src/extension/care/final.c0000644000175000017500000003254512430615044016764 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* lstat(2), readlink(2), getpid(2), wirte(2), lseek(2), */ #include /* struct stat, fchmod(2), */ #include /* PATH_MAX, */ #include /* uname(2), */ #include /* fprintf(3), fclose(3), */ #include /* errno, ENAMETOOLONG, */ #include /* strcpy(3), */ #include /* htobe64(3), */ #include /* assert(3), */ #include "extension/care/final.h" #include "extension/care/care.h" #include "extension/care/extract.h" #include "execve/ldso.h" #include "path/path.h" #include "path/temp.h" #include "cli/notice.h" /** * Find in @care->volatile_envars the given @envar (format * "name=value"). This function returns the name of the variable if * found (format "name"), NULL otherwise. */ static const char *find_volatile_envar(const Care *care, const char *envar) { const Item *volatile_envar; if (care->volatile_envars == NULL) return NULL; STAILQ_FOREACH(volatile_envar, care->volatile_envars, link) { if (is_env_name(envar, volatile_envar->load)) return volatile_envar->load; } return NULL; } extern char **environ; /** * Archive in @care->archive the content of @file with the given * @name, then close it. This function returns < 0 if an error * occured, otherwise 0. Note: this function is called in @care's * destructor. */ static int archive_close_file(const Care *care, FILE *file, const char *name) { char path[PATH_MAX]; struct stat statl; char *location; int status; int fd; /* Ensure everything is written into the file before archiving * it. */ fflush(file); fd = fileno(file); status = fstat(fd, &statl); if (status < 0) { notice(NULL, ERROR, SYSTEM, "can't get '%s' status", name); goto end; } location = talloc_asprintf(care, "%s/%s", care->prefix, name); if (location == NULL) { notice(NULL, ERROR, INTERNAL, "can't allocate location for '%s'", name); status = -1; goto end; } status = readlink_proc_pid_fd(getpid(), fd, path); if (status < 0) { notice(NULL, ERROR, INTERNAL, "can't readlink(/proc/%d/fd/%d)", getpid(), fd); goto end; } status = archive(NULL, care->archive, path, location, &statl); end: (void) fclose(file); return status; } /** * Return a copy -- attached to @context -- of @input with all ' * (single quote) characters escaped. */ static const char *escape_quote(TALLOC_CTX *context, const char *input) { char *output; size_t length; size_t i; output = talloc_strdup(context, ""); if (output == NULL) return NULL; length = strlen(input); for (i = 0; i < length; i++) { char buffer[2] = { input[i], '\0' }; if (buffer[0] == '\'') output = talloc_strdup_append_buffer(output, "'\\''"); else output = talloc_strdup_append_buffer(output, buffer); if (output == NULL) return NULL; } return output; } /* Helpers for archive_* functions. */ #define N(format, ...) \ do { \ if (fprintf(file, format "\n", ##__VA_ARGS__) < 0) { \ notice(NULL, ERROR, INTERNAL, "can't write file"); \ (void) fclose(file); \ return -1; \ } \ } while (0) #define C(format, ...) N(format " \\", ##__VA_ARGS__) /** * Archive the "re-execute.sh" file, according to the given @care. * This function returns < 0 if an error occured, 0 otherwise. Note: * this function is called in @care's destructor. */ static int archive_re_execute_sh(Care *care) { struct utsname utsname; const Item *item; FILE *file; int status; int i; file = open_temp_file(NULL, "care"); if (file == NULL) { notice(NULL, ERROR, INTERNAL, "can't create temporary file for 're-execute.sh'"); return -1; } status = fchmod(fileno(file), 0755); if (status < 0) notice(NULL, WARNING, SYSTEM, "can't make 're-execute.sh' executable"); N("#! /bin/sh"); N(""); N("export XAUTHORITY=\"${XAUTHORITY:-$HOME/.Xauthority}\""); N("export ICEAUTHORITY=\"${ICEAUTHORITY:-$HOME/.ICEauthority}\""); N(""); N("nbargs=$#"); C("[ $nbargs -ne 0 ] || set --"); for (i = 0; care->command != NULL && care->command[i] != NULL; i++) C("'%s'", care->command[i]); N(""); N("PROOT=\"${PROOT-$(dirname $0)/proot}\""); N(""); N("if [ ! -e ${PROOT} ]; then"); N(" PROOT=$(which proot)"); N("fi"); N(""); N("if [ -z ${PROOT} ]; then"); N(" echo '**********************************************************************'"); N(" echo '\"proot\" command not found, please get it from http://proot.me'"); N(" echo '**********************************************************************'"); N(" exit 1"); N("fi"); N(""); N("if [ x$PROOT_NO_SECCOMP != x ]; then"); N(" PROOT_NO_SECCOMP=\"PROOT_NO_SECCOMP=$PROOT_NO_SECCOMP\""); N("fi"); N(""); C("env --ignore-environment"); C("PROOT_IGNORE_MISSING_BINDINGS=1"); C("$PROOT_NO_SECCOMP"); for (i = 0; environ[i] != NULL; i++) { const char *volatile_envar; volatile_envar = find_volatile_envar(care, environ[i]); if (volatile_envar != NULL) C("'%1$s'=\"$%1$s\" ", volatile_envar); else { const char *string = escape_quote(care, environ[i]); C("'%s' ", string ?: environ[i]); } } C("\"${PROOT-$(dirname $0)/proot}\""); if (care->volatile_paths != NULL) { /* If a volatile path is relative to $HOME, use an * asymmetric binding. For instance: * * -b $HOME/.Xauthority:/home/user/.Xauthority * * where "/home/user" was the $HOME during the * original execution. */ STAILQ_FOREACH(item, care->volatile_paths, link) { const char *name = talloc_get_name(item); if (name[0] == '$') C("-b \"%s:%s\" ", name, (char *) item->load); else C("-b \"%s\" ", (char *) item->load); } } status = uname(&utsname); if (status < 0) { notice(NULL, WARNING, SYSTEM, "can't get kernel release"); C("-k 3.11.0"); } else C("-k '%s' ", utsname.release); C("-w '%s' ", care->initial_cwd); C("-r \"$(dirname $0)/rootfs\""); /* In case the program retrieves its DSOs from /proc/self/maps * (eg. VLC). */ C("-b \"$(dirname $0)/rootfs\""); N("${1+\"$@\"}"); N(""); N("status=$?"); N("if [ $status -ne %d ] && [ $nbargs -eq 0 ]; then", care->last_exit_status); N("echo \"care: The reproduced execution didn't return the same exit status as the\""); N("echo \"care: original execution. If it is unexpected, please report this bug\""); N("echo \"care: to CARE/PRoot developers:\""); N("echo \"care: * mailing list: reproducible@googlegroups.com; or\""); N("echo \"care: * forum: https://groups.google.com/forum/?fromgroups#!forum/reproducible; or\""); N("echo \"care: * issue tracker: https://github.com/cedric-vincent/PRoot/issues/\""); N("fi"); N(""); N("exit $status"); return archive_close_file(care, file, "re-execute.sh"); } /** * Archive the "concealed-accesses.txt" file in @care->archive, * according to the content of @care->concealed_accesses. This * function returns < 0 if an error occured, 0 otherwise. Note: this * function is called in @care's destructor. */ static int archive_concealed_accesses_txt(const Care *care) { const Item *item; FILE *file; if (care->concealed_accesses == NULL) return 0; file = open_temp_file(NULL, "care"); if (file == NULL) { notice(NULL, WARNING, INTERNAL, "can't create temporary file for 'concealed-accesses.txt'"); return -1; } STAILQ_FOREACH(item, care->concealed_accesses, link) N("%s", (char *) item->load); return archive_close_file(care, file, "concealed-accesses.txt"); } /** * Archive the "README.txt" file in @care->archive. This function * returns < 0 if an error occured, 0 otherwise. Note: this function * is called in @care's destructor. */ static int archive_readme_txt(const Care *care) { FILE *file; file = open_temp_file(NULL, "care"); if (file == NULL) { notice(NULL, WARNING, INTERNAL, "can't create temporary file for 'README.txt'"); return -1; } N("This archive was created with CARE: http://reproducible.io. It contains:"); N(""); N("re-execute.sh"); N(" start the re-execution of the initial command as originally"); N(" specified. It is also possible to specify an alternate command."); N(" For example, assuming gcc was archived, it can be re-invoked"); N(" differently:"); N(""); N(" $ ./re-execute.sh gcc --version"); N(" gcc (Ubuntu/Linaro 4.5.2-8ubuntu4) 4.5.2"); N(""); N(" $ echo 'int main(void) { return puts(\"OK\"); }' > rootfs/foo.c"); N(" $ ./re-execute.sh gcc -Wall /foo.c"); N(" $ foo.c: In function \"main\":"); N(" $ foo.c:1:1: warning: implicit declaration of function \"puts\""); N(""); N("rootfs/"); N(" directory where all the files used during the original execution"); N(" were archived, they will be required for the reproduced execution."); N(""); N("proot"); N(" virtualization tool invoked by re-execute.sh to confine the"); N(" reproduced execution into the rootfs. It also emulates the"); N(" missing kernel features if needed."); N(""); N("concealed-accesses.txt"); N(" list of accessed paths that were concealed during the original"); N(" execution. Its main purpose is to know what are the paths that"); N(" should be revealed if the the original execution didn't go as"); N(" expected. It is absolutely useless for the reproduced execution."); N(""); return archive_close_file(care, file, "README.txt"); } #undef N #undef C #if !defined(CARE_BINARY_IS_PORTABLE) static int archive_myself(const Care *care) UNUSED; #endif /** * Archive the content pointed to by "/proc/self/exe" in * "@care->archive:@care->prefix/proot". Note: this function is * called in @care's destructor. */ static int archive_myself(const Care *care) { char path[PATH_MAX]; struct stat statl; char *location; int status; status = readlink("/proc/self/exe", path, PATH_MAX); if (status >= PATH_MAX) { status = -1; errno = ENAMETOOLONG; } if (status < 0) { notice(NULL, ERROR, SYSTEM, "can't readlink '/proc/self/exe'"); return status; } path[status] = '\0'; status = lstat(path, &statl); if (status < 0) { notice(NULL, ERROR, INTERNAL, "can't lstat '%s'", path); return status; } location = talloc_asprintf(care, "%s/proot", care->prefix); if (location == NULL) { notice(NULL, ERROR, INTERNAL, "can't allocate location for 'proot'"); return -1; } return archive(NULL, care->archive, path, location, &statl); } /** * Archive "re-execute.sh" & "proot" from @care. This function * always returns 0. Note: this is a Talloc destructor. */ int finalize_care(Care *care) { char *extractor; int status; /* Generate & archive the "re-execute.sh" script. */ status = archive_re_execute_sh(care); if (status < 0) notice(NULL, WARNING, INTERNAL, "can't archive 're-execute.sh'"); /* Generate & archive the "concealed-accesses.txt" file. */ status = archive_concealed_accesses_txt(care); if (status < 0) notice(NULL, WARNING, INTERNAL, "can't archive 'concealed-accesses.txt'"); /* Generate & archive the "README.txt" file. */ status = archive_readme_txt(care); if (status < 0) notice(NULL, WARNING, INTERNAL, "can't archive 'README.txt'"); #if defined(CARE_BINARY_IS_PORTABLE) /* Archive "care" as "proot", these are the same binary. */ status = archive_myself(care); if (status < 0) notice(NULL, WARNING, INTERNAL, "can't archive 'proot'"); #endif finalize_archive(care->archive); /* Append self/raw extracting information if needed. */ if (care->archive->fd >= 0 && care->archive->offset > 0) { AutoExtractInfo info; off_t position; strcpy(info.signature, AUTOEXTRACT_SIGNATURE); /* Compute the size of the archive. */ position = lseek(care->archive->fd, 0, SEEK_CUR); assert(position > care->archive->offset); info.size = htobe64(position - care->archive->offset); status = write(care->archive->fd, &info, sizeof(info)); if (status != sizeof(info)) notice(NULL, WARNING, SYSTEM, "can't write extracting information"); (void) close(care->archive->fd); care->archive->fd = -1; if (care->archive->offset == strlen("RAW")) extractor = talloc_asprintf(care, "`care -x %s`", care->output); else extractor = talloc_asprintf(care, "`./%1$s` or `care -x %1$s`", care->output); } else if (care->output[strlen(care->output) - 1] != '/') extractor = talloc_asprintf(care, "`care -x %s`", care->output); else extractor = NULL; notice(NULL, INFO, USER, "----------------------------------------------------------------------"); notice(NULL, INFO, USER, "Hints:"); notice(NULL, INFO, USER, " - search for \"conceal\" in `care -h` if the execution didn't go as expected."); if (extractor != NULL) notice(NULL, INFO, USER, " - run %s to extract the output archive correctly.", extractor); return 0; } care-2.2.1/src/extension/care/archive.h0000644000175000017500000000274312430615044017316 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef ARCHIVE_H #define ARCHIVE_H #include #include #include #include "tracee/tracee.h" typedef struct { struct archive *handle; struct archive_entry_linkresolver *hardlink_resolver; /* Information used to create an self-extracting archive. */ off_t offset; int fd; } Archive; extern Archive *new_archive(TALLOC_CTX *context, const Tracee* tracee, const char *output, size_t *prefix_length); extern int finalize_archive(Archive *archive); extern int archive(const Tracee* tracee, Archive *archive, const char *path, const char *alternate_path, const struct stat *statl); #endif /* ARCHIVE_H */ care-2.2.1/src/extension/extension.h0000644000175000017500000001454412430615044017001 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef EXTENSION_H #define EXTENSION_H #include /* LIST_, */ #include /* intptr_t, */ #include /* bool, */ #include "tracee/tracee.h" #include "syscall/seccomp.h" /* List of possible events. */ typedef enum { /* A guest path passed as an argument of the current syscall * is about to be translated: "(char *) data1" is the base for * "(char *) data2" -- the guest path -- if this latter is * relative. If the extension returns > 0, then PRoot skips * its own handling. If the extension returns < 0, then PRoot * reports this errno as-is. */ GUEST_PATH, /* A canonicalized host path is being accessed during the * translation of a guest path: "(char *) data1" is the * canonicalized host path and "(bool) data2" is true if it is * the final path. Note that several host paths are accessed * for a given guest path since PRoot has to walk along all * parent directories and symlinks in order to translate it. * If the extension returns < 0, then PRoot reports this errno * as-is. */ HOST_PATH, /* The tracee enters a syscall, and PRoot hasn't do anything * yet. If the extension returns > 0, then PRoot skips its * own handling. If the extension returns < 0, then PRoot * cancels the syscall and reports this errno to the * tracee. */ SYSCALL_ENTER_START, /* The tracee enters a syscall, and PRoot has already handled * it: "(int) data1" is the current status, it is < 0 when * something went wrong. If the extension returns < 0, then * PRoot cancels the syscall and reports this errno to the * tracee. */ SYSCALL_ENTER_END, /* The tracee exits a syscall, and PRoot hasn't do anything * yet. If the extension returns > 0, then PRoot skips its * own handling. If the extension returns < 0, then PRoot * reports this errno to the tracee. */ SYSCALL_EXIT_START, /* The tracee exits a syscall, and PRoot has already handled * it. If the extension returns < 0, then PRoot reports this * errno to the tracee. */ SYSCALL_EXIT_END, /* The tracee is stopped either because of a syscall or a * signal: "(int) data1" is its new status as reported by * waitpid(2). If the extension returns != 0, then PRoot * skips its own handling. */ NEW_STATUS, /* Ask how this extension is inheritable: "(Tracee *) data1" * is the child tracee and "(bool) data2" is the clone(2) * flags (CLONE_RECONF for sub-reconfiguration). The meaning * of the returned value is: * * < 0 : not inheritable * == 0 : inheritable + shared configuration. * > 0 : inheritable + call INHERIT_CHILD. */ INHERIT_PARENT, /* Control the inheritance: "(Extension *) data1" is the * extension of the parent and "(word_t) data2" is the clone(2) * flags (CLONE_RECONF for sub-reconfiguration). For instance * the extension for the child could use a configuration * different from the parent's configuration. */ INHERIT_CHILD, /* The tracee enters a "chained" syscall, that is, an * unrequested syscall inserted by PRoot after an actual * syscall. If the extension returns < 0, then PRoot cancels * the syscall and reports this errno to the tracee. */ SYSCALL_CHAINED_ENTER, /* The tracee exists a "chained" syscall, that is, an * unrequested syscall inserted by PRoot after an actual * syscall. */ SYSCALL_CHAINED_EXIT, /* Initialize the extension: "(const char *) data1" is its * argument that was passed to the command-line interface. If * the extension returns < 0, then PRoot removed it. */ INITIALIZATION, /* The extension is not attached to its tracee anymore * (destructor). */ REMOVED, /* Print the current configuration of the extension. See * print_config() as an example. */ PRINT_CONFIG, /* Print the usage of the extension: "(bool) data1" is true * for a detailed usage. See print_usage() as an example. */ PRINT_USAGE, } ExtensionEvent; #define CLONE_RECONF ((word_t) -1) struct extension; typedef int (*extension_callback_t)(struct extension *extension, ExtensionEvent event, intptr_t data1, intptr_t data2); typedef struct extension { /* Function to be called when any event occured. */ extension_callback_t callback; /* A chunk of memory allocated by any talloc functions. * Mainly useful to store a configuration. */ TALLOC_CTX *config; /* List of sysnum handled by this extension. */ const FilteredSysnum *filtered_sysnums; /* Link to the next and previous extensions. Note the order * is *never* garantee. */ LIST_ENTRY(extension) link; } Extension; typedef LIST_HEAD(extensions, extension) Extensions; extern int initialize_extension(Tracee *tracee, extension_callback_t callback, const char *cli); extern void inherit_extensions(Tracee *child, Tracee *parent, word_t clone_flags); /** * Notify all extensions of @tracee that the given @event occured. * See ExtensionEvent for the meaning of @data1 and @data2. */ static inline int notify_extensions(Tracee *tracee, ExtensionEvent event, intptr_t data1, intptr_t data2) { Extension *extension; if (tracee->extensions == NULL) return 0; LIST_FOREACH(extension, tracee->extensions, link) { int status = extension->callback(extension, event, data1, data2); if (status != 0) return status; } return 0; } /* Built-in extensions. */ extern int kompat_callback(Extension *extension, ExtensionEvent event, intptr_t d1, intptr_t d2); extern int fake_id0_callback(Extension *extension, ExtensionEvent event, intptr_t d1, intptr_t d2); extern int care_callback(Extension *extension, ExtensionEvent event, intptr_t d1, intptr_t d2); #endif /* EXTENSION_H */ care-2.2.1/src/compat.h0000644000175000017500000001270612430615044014232 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef COMPAT_H #define COMPAT_H /* Local definitions for compatibility with old and/or broken distros... */ # ifndef AT_RANDOM # define AT_RANDOM 25 # endif # ifndef AT_SYSINFO # define AT_SYSINFO 32 # endif # ifndef AT_SYSINFO_EHDR # define AT_SYSINFO_EHDR 33 # endif # ifndef AT_FDCWD # define AT_FDCWD -100 # endif # ifndef AT_SYMLINK_FOLLOW # define AT_SYMLINK_FOLLOW 0x400 # endif # ifndef AT_REMOVEDIR # define AT_REMOVEDIR 0x200 # endif # ifndef AT_SYMLINK_NOFOLLOW # define AT_SYMLINK_NOFOLLOW 0x100 # endif # ifndef IN_DONT_FOLLOW # define IN_DONT_FOLLOW 0x02000000 # endif # ifndef WIFCONTINUED # define WIFCONTINUED(status) ((status) == 0xffff) # endif # ifndef PTRACE_GETREGS # define PTRACE_GETREGS 12 # endif # ifndef PTRACE_SETREGS # define PTRACE_SETREGS 13 # endif # ifndef PTRACE_GETFPREGS # define PTRACE_GETFPREGS 14 # endif # ifndef PTRACE_SETFPREGS # define PTRACE_SETFPREGS 15 # endif # ifndef PTRACE_GETFPXREGS # define PTRACE_GETFPXREGS 18 # endif # ifndef PTRACE_SETFPXREGS # define PTRACE_SETFPXREGS 19 # endif # ifndef PTRACE_SETOPTIONS # define PTRACE_SETOPTIONS 0x4200 # endif # ifndef PTRACE_GETEVENTMSG # define PTRACE_GETEVENTMSG 0x4201 # endif # ifndef PTRACE_GETREGSET # define PTRACE_GETREGSET 0x4204 # endif # ifndef PTRACE_SETREGSET # define PTRACE_SETREGSET 0x4205 # endif # ifndef PTRACE_SEIZE # define PTRACE_SEIZE 0x4206 # endif # ifndef PTRACE_INTERRUPT # define PTRACE_INTERRUPT 0x4207 # endif # ifndef PTRACE_LISTEN # define PTRACE_LISTEN 0x4208 # endif # ifndef PTRACE_O_TRACESYSGOOD # define PTRACE_O_TRACESYSGOOD 0x00000001 # endif # ifndef PTRACE_O_TRACEFORK # define PTRACE_O_TRACEFORK 0x00000002 # endif # ifndef PTRACE_O_TRACEVFORK # define PTRACE_O_TRACEVFORK 0x00000004 # endif # ifndef PTRACE_O_TRACECLONE # define PTRACE_O_TRACECLONE 0x00000008 # endif # ifndef PTRACE_O_TRACEEXEC # define PTRACE_O_TRACEEXEC 0x00000010 # endif # ifndef PTRACE_O_TRACEVFORKDONE # define PTRACE_O_TRACEVFORKDONE 0x00000020 # endif # ifndef PTRACE_O_TRACEEXIT # define PTRACE_O_TRACEEXIT 0x00000040 # endif # ifndef PTRACE_O_TRACESECCOMP # define PTRACE_O_TRACESECCOMP 0x00000080 # endif # ifndef PTRACE_EVENT_FORK # define PTRACE_EVENT_FORK 1 # endif # ifndef PTRACE_EVENT_VFORK # define PTRACE_EVENT_VFORK 2 # endif # ifndef PTRACE_EVENT_CLONE # define PTRACE_EVENT_CLONE 3 # endif # ifndef PTRACE_EVENT_EXEC # define PTRACE_EVENT_EXEC 4 # endif # ifndef PTRACE_EVENT_VFORK_DONE # define PTRACE_EVENT_VFORK_DONE 5 # endif # ifndef PTRACE_EVENT_EXIT # define PTRACE_EVENT_EXIT 6 # endif # ifndef PTRACE_EVENT_SECCOMP # define PTRACE_EVENT_SECCOMP 7 # endif # ifndef PTRACE_EVENT_SECCOMP2 # if PTRACE_EVENT_SECCOMP == 7 # define PTRACE_EVENT_SECCOMP2 8 # elif PTRACE_EVENT_SECCOMP == 8 # define PTRACE_EVENT_SECCOMP2 7 # else # error "unknown PTRACE_EVENT_SECCOMP value" # endif # endif # ifndef PTRACE_SET_SYSCALL # define PTRACE_SET_SYSCALL 23 # endif # ifndef PTRACE_GET_THREAD_AREA # define PTRACE_GET_THREAD_AREA 25 # endif # ifndef PTRACE_SET_THREAD_AREA # define PTRACE_SET_THREAD_AREA 26 # endif # ifndef PTRACE_GETVFPREGS # define PTRACE_GETVFPREGS 27 # endif # ifndef PTRACE_SINGLEBLOCK # define PTRACE_SINGLEBLOCK 33 # endif # ifndef ADDR_NO_RANDOMIZE # define ADDR_NO_RANDOMIZE 0x0040000 # endif # ifndef SYS_ACCEPT4 # define SYS_ACCEPT4 18 # endif # ifndef TALLOC_FREE # define TALLOC_FREE(ctx) do { talloc_free(ctx); ctx = NULL; } while(0) # endif # ifndef PR_SET_NO_NEW_PRIVS # define PR_SET_NO_NEW_PRIVS 38 # endif # ifndef PR_SET_SECCOMP # define PR_SET_SECCOMP 22 # endif # ifndef SECCOMP_MODE_FILTER # define SECCOMP_MODE_FILTER 2 # endif # ifndef talloc_get_type_abort # define talloc_get_type_abort talloc_get_type # endif # ifndef FUTEX_PRIVATE_FLAG # define FUTEX_PRIVATE_FLAG 128 # endif # ifndef EFD_SEMAPHORE # define EFD_SEMAPHORE 1 # endif # ifndef F_DUPFD_CLOEXEC # define F_DUPFD_CLOEXEC 1030 # endif # ifndef O_CLOEXEC # define O_CLOEXEC 02000000 # endif #endif /* COMPAT_H */ care-2.2.1/src/syscall/0000755000175000017500000000000012430615044014242 5ustar ivoireivoirecare-2.2.1/src/syscall/sysnums-arm64.h0000644000175000017500000001445412430615044017073 0ustar ivoireivoire#include "syscall/sysnum.h" static const Sysnum sysnums_arm64[] = { [ 0 ] = PR_io_setup, [ 1 ] = PR_io_destroy, [ 2 ] = PR_io_submit, [ 3 ] = PR_io_cancel, [ 4 ] = PR_io_getevents, [ 5 ] = PR_setxattr, [ 6 ] = PR_lsetxattr, [ 7 ] = PR_fsetxattr, [ 8 ] = PR_getxattr, [ 9 ] = PR_lgetxattr, [ 10 ] = PR_fgetxattr, [ 11 ] = PR_listxattr, [ 12 ] = PR_llistxattr, [ 13 ] = PR_flistxattr, [ 14 ] = PR_removexattr, [ 15 ] = PR_lremovexattr, [ 16 ] = PR_fremovexattr, [ 17 ] = PR_getcwd, [ 18 ] = PR_lookup_dcookie, [ 19 ] = PR_eventfd2, [ 20 ] = PR_epoll_create1, [ 21 ] = PR_epoll_ctl, [ 22 ] = PR_epoll_pwait, [ 23 ] = PR_dup, [ 24 ] = PR_dup3, [ 25 ] = PR_fcntl, [ 26 ] = PR_inotify_init1, [ 27 ] = PR_inotify_add_watch, [ 28 ] = PR_inotify_rm_watch, [ 29 ] = PR_ioctl, [ 30 ] = PR_ioprio_set, [ 31 ] = PR_ioprio_get, [ 32 ] = PR_flock, [ 33 ] = PR_mknodat, [ 34 ] = PR_mkdirat, [ 35 ] = PR_unlinkat, [ 36 ] = PR_symlinkat, [ 37 ] = PR_linkat, [ 38 ] = PR_renameat, [ 39 ] = PR_umount2, [ 40 ] = PR_mount, [ 41 ] = PR_pivot_root, [ 42 ] = PR_nfsservctl, [ 43 ] = PR_statfs, [ 44 ] = PR_fstatfs, [ 45 ] = PR_truncate, [ 46 ] = PR_ftruncate, [ 47 ] = PR_fallocate, [ 48 ] = PR_faccessat, [ 49 ] = PR_chdir, [ 50 ] = PR_fchdir, [ 51 ] = PR_chroot, [ 52 ] = PR_fchmod, [ 53 ] = PR_fchmodat, [ 54 ] = PR_fchownat, [ 55 ] = PR_fchown, [ 56 ] = PR_openat, [ 57 ] = PR_close, [ 58 ] = PR_vhangup, [ 59 ] = PR_pipe2, [ 60 ] = PR_quotactl, [ 61 ] = PR_getdents64, [ 62 ] = PR_lseek, [ 63 ] = PR_read, [ 64 ] = PR_write, [ 65 ] = PR_readv, [ 66 ] = PR_writev, [ 67 ] = PR_pread64, [ 68 ] = PR_pwrite64, [ 69 ] = PR_preadv, [ 70 ] = PR_pwritev, [ 71 ] = PR_sendfile, [ 72 ] = PR_pselect6, [ 73 ] = PR_ppoll, [ 74 ] = PR_signalfd4, [ 75 ] = PR_vmsplice, [ 76 ] = PR_splice, [ 77 ] = PR_tee, [ 78 ] = PR_readlinkat, [ 79 ] = PR_fstatat64, [ 80 ] = PR_fstat, [ 81 ] = PR_sync, [ 82 ] = PR_fsync, [ 83 ] = PR_fdatasync, [ 84 ] = PR_sync_file_range, [ 85 ] = PR_timerfd_create, [ 86 ] = PR_timerfd_settime, [ 87 ] = PR_timerfd_gettime, [ 88 ] = PR_utimensat, [ 89 ] = PR_acct, [ 90 ] = PR_capget, [ 91 ] = PR_capset, [ 92 ] = PR_personality, [ 93 ] = PR_exit, [ 94 ] = PR_exit_group, [ 95 ] = PR_waitid, [ 96 ] = PR_set_tid_address, [ 97 ] = PR_unshare, [ 98 ] = PR_futex, [ 99 ] = PR_set_robust_list, [ 100 ] = PR_get_robust_list, [ 101 ] = PR_nanosleep, [ 102 ] = PR_getitimer, [ 103 ] = PR_setitimer, [ 104 ] = PR_kexec_load, [ 105 ] = PR_init_module, [ 106 ] = PR_delete_module, [ 107 ] = PR_timer_create, [ 108 ] = PR_timer_gettime, [ 109 ] = PR_timer_getoverrun, [ 110 ] = PR_timer_settime, [ 111 ] = PR_timer_delete, [ 112 ] = PR_clock_settime, [ 113 ] = PR_clock_gettime, [ 114 ] = PR_clock_getres, [ 115 ] = PR_clock_nanosleep, [ 116 ] = PR_syslog, [ 117 ] = PR_ptrace, [ 118 ] = PR_sched_setparam, [ 119 ] = PR_sched_setscheduler, [ 120 ] = PR_sched_getscheduler, [ 121 ] = PR_sched_getparam, [ 122 ] = PR_sched_setaffinity, [ 123 ] = PR_sched_getaffinity, [ 124 ] = PR_sched_yield, [ 125 ] = PR_sched_get_priority_max, [ 126 ] = PR_sched_get_priority_min, [ 127 ] = PR_sched_rr_get_interval, [ 128 ] = PR_restart_syscall, [ 129 ] = PR_kill, [ 130 ] = PR_tkill, [ 131 ] = PR_tgkill, [ 132 ] = PR_sigaltstack, [ 133 ] = PR_rt_sigsuspend, [ 134 ] = PR_rt_sigaction, [ 135 ] = PR_rt_sigprocmask, [ 136 ] = PR_rt_sigpending, [ 137 ] = PR_rt_sigtimedwait, [ 138 ] = PR_rt_sigqueueinfo, [ 139 ] = PR_rt_sigreturn, [ 140 ] = PR_setpriority, [ 141 ] = PR_getpriority, [ 142 ] = PR_reboot, [ 143 ] = PR_setregid, [ 144 ] = PR_setgid, [ 145 ] = PR_setreuid, [ 146 ] = PR_setuid, [ 147 ] = PR_setresuid, [ 148 ] = PR_getresuid, [ 149 ] = PR_setresgid, [ 150 ] = PR_getresgid, [ 151 ] = PR_setfsuid, [ 152 ] = PR_setfsgid, [ 153 ] = PR_times, [ 154 ] = PR_setpgid, [ 155 ] = PR_getpgid, [ 156 ] = PR_getsid, [ 157 ] = PR_setsid, [ 158 ] = PR_getgroups, [ 159 ] = PR_setgroups, [ 160 ] = PR_uname, [ 161 ] = PR_sethostname, [ 162 ] = PR_setdomainname, [ 163 ] = PR_getrlimit, [ 164 ] = PR_setrlimit, [ 165 ] = PR_getrusage, [ 166 ] = PR_umask, [ 167 ] = PR_prctl, [ 168 ] = PR_getcpu, [ 169 ] = PR_gettimeofday, [ 170 ] = PR_settimeofday, [ 171 ] = PR_adjtimex, [ 172 ] = PR_getpid, [ 173 ] = PR_getppid, [ 174 ] = PR_getuid, [ 175 ] = PR_geteuid, [ 176 ] = PR_getgid, [ 177 ] = PR_getegid, [ 178 ] = PR_gettid, [ 179 ] = PR_sysinfo, [ 180 ] = PR_mq_open, [ 181 ] = PR_mq_unlink, [ 182 ] = PR_mq_timedsend, [ 183 ] = PR_mq_timedreceive, [ 184 ] = PR_mq_notify, [ 185 ] = PR_mq_getsetattr, [ 186 ] = PR_msgget, [ 187 ] = PR_msgctl, [ 188 ] = PR_msgrcv, [ 189 ] = PR_msgsnd, [ 190 ] = PR_semget, [ 191 ] = PR_semctl, [ 192 ] = PR_semtimedop, [ 193 ] = PR_semop, [ 194 ] = PR_shmget, [ 195 ] = PR_shmctl, [ 196 ] = PR_shmat, [ 197 ] = PR_shmdt, [ 198 ] = PR_socket, [ 199 ] = PR_socketpair, [ 200 ] = PR_bind, [ 201 ] = PR_listen, [ 202 ] = PR_accept, [ 203 ] = PR_connect, [ 204 ] = PR_getsockname, [ 205 ] = PR_getpeername, [ 206 ] = PR_sendto, [ 207 ] = PR_recvfrom, [ 208 ] = PR_setsockopt, [ 209 ] = PR_getsockopt, [ 210 ] = PR_shutdown, [ 211 ] = PR_sendmsg, [ 212 ] = PR_recvmsg, [ 213 ] = PR_readahead, [ 214 ] = PR_brk, [ 215 ] = PR_munmap, [ 216 ] = PR_mremap, [ 217 ] = PR_add_key, [ 218 ] = PR_request_key, [ 219 ] = PR_keyctl, [ 220 ] = PR_clone, [ 221 ] = PR_execve, [ 222 ] = PR_mmap, [ 223 ] = PR_fadvise64, [ 224 ] = PR_swapon, [ 225 ] = PR_swapoff, [ 226 ] = PR_mprotect, [ 227 ] = PR_msync, [ 228 ] = PR_mlock, [ 229 ] = PR_munlock, [ 230 ] = PR_mlockall, [ 231 ] = PR_munlockall, [ 232 ] = PR_mincore, [ 233 ] = PR_madvise, [ 234 ] = PR_remap_file_pages, [ 235 ] = PR_mbind, [ 236 ] = PR_get_mempolicy, [ 237 ] = PR_set_mempolicy, [ 238 ] = PR_migrate_pages, [ 239 ] = PR_move_pages, [ 240 ] = PR_rt_tgsigqueueinfo, [ 241 ] = PR_perf_event_open, [ 242 ] = PR_accept4, [ 243 ] = PR_recvmmsg, [ 244 ] = PR_arch_specific_syscall, [ 260 ] = PR_wait4, [ 261 ] = PR_prlimit64, [ 262 ] = PR_fanotify_init, [ 263 ] = PR_fanotify_mark, [ 264 ] = PR_name_to_handle_at, [ 265 ] = PR_open_by_handle_at, [ 266 ] = PR_clock_adjtime, [ 267 ] = PR_syncfs, [ 268 ] = PR_setns, [ 269 ] = PR_sendmmsg, [ 270 ] = PR_process_vm_readv, [ 271 ] = PR_process_vm_writev, [ 272 ] = PR_kcmp, [ 273 ] = PR_syscalls, }; care-2.2.1/src/syscall/chain.h0000644000175000017500000000247512430615044015505 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef CHAIN_H #define CHAIN_H #include "tracee/tracee.h" #include "syscall/sysnum.h" #include "arch.h" extern int register_chained_syscall(Tracee *tracee, Sysnum sysnum, word_t sysarg_1, word_t sysarg_2, word_t sysarg_3, word_t sysarg_4, word_t sysarg_5, word_t sysarg_6); extern void force_chain_final_result(Tracee *tracee, word_t forced_result); extern int restart_original_syscall(Tracee *tracee); extern void chain_next_syscall(Tracee *tracee); #endif /* CHAIN_H */ care-2.2.1/src/syscall/seccomp.h0000644000175000017500000000243212430615044016045 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef SECCOMP_H #define SECCOMP_H #include "syscall/sysnum.h" #include "tracee/tracee.h" #include "attribute.h" #include "arch.h" typedef struct { Sysnum value; word_t flags; } FilteredSysnum; typedef struct { unsigned int value; size_t nb_abis; Abi abis[NB_MAX_ABIS]; } SeccompArch; #define FILTERED_SYSNUM_END { PR_void, 0 } #define FILTER_SYSEXIT 0x1 extern int enable_syscall_filtering(const Tracee *tracee); #endif /* SECCOMP_H */ care-2.2.1/src/syscall/sysnums-x86_64.h0000644000175000017500000001701112430615044017070 0ustar ivoireivoire#include "syscall/sysnum.h" static const Sysnum sysnums_x86_64[] = { [ 0 ] = PR_read, [ 1 ] = PR_write, [ 2 ] = PR_open, [ 3 ] = PR_close, [ 4 ] = PR_stat, [ 5 ] = PR_fstat, [ 6 ] = PR_lstat, [ 7 ] = PR_poll, [ 8 ] = PR_lseek, [ 9 ] = PR_mmap, [ 10 ] = PR_mprotect, [ 11 ] = PR_munmap, [ 12 ] = PR_brk, [ 13 ] = PR_rt_sigaction, [ 14 ] = PR_rt_sigprocmask, [ 15 ] = PR_rt_sigreturn, [ 16 ] = PR_ioctl, [ 17 ] = PR_pread64, [ 18 ] = PR_pwrite64, [ 19 ] = PR_readv, [ 20 ] = PR_writev, [ 21 ] = PR_access, [ 22 ] = PR_pipe, [ 23 ] = PR_select, [ 24 ] = PR_sched_yield, [ 25 ] = PR_mremap, [ 26 ] = PR_msync, [ 27 ] = PR_mincore, [ 28 ] = PR_madvise, [ 29 ] = PR_shmget, [ 30 ] = PR_shmat, [ 31 ] = PR_shmctl, [ 32 ] = PR_dup, [ 33 ] = PR_dup2, [ 34 ] = PR_pause, [ 35 ] = PR_nanosleep, [ 36 ] = PR_getitimer, [ 37 ] = PR_alarm, [ 38 ] = PR_setitimer, [ 39 ] = PR_getpid, [ 40 ] = PR_sendfile, [ 41 ] = PR_socket, [ 42 ] = PR_connect, [ 43 ] = PR_accept, [ 44 ] = PR_sendto, [ 45 ] = PR_recvfrom, [ 46 ] = PR_sendmsg, [ 47 ] = PR_recvmsg, [ 48 ] = PR_shutdown, [ 49 ] = PR_bind, [ 50 ] = PR_listen, [ 51 ] = PR_getsockname, [ 52 ] = PR_getpeername, [ 53 ] = PR_socketpair, [ 54 ] = PR_setsockopt, [ 55 ] = PR_getsockopt, [ 56 ] = PR_clone, [ 57 ] = PR_fork, [ 58 ] = PR_vfork, [ 59 ] = PR_execve, [ 60 ] = PR_exit, [ 61 ] = PR_wait4, [ 62 ] = PR_kill, [ 63 ] = PR_uname, [ 64 ] = PR_semget, [ 65 ] = PR_semop, [ 66 ] = PR_semctl, [ 67 ] = PR_shmdt, [ 68 ] = PR_msgget, [ 69 ] = PR_msgsnd, [ 70 ] = PR_msgrcv, [ 71 ] = PR_msgctl, [ 72 ] = PR_fcntl, [ 73 ] = PR_flock, [ 74 ] = PR_fsync, [ 75 ] = PR_fdatasync, [ 76 ] = PR_truncate, [ 77 ] = PR_ftruncate, [ 78 ] = PR_getdents, [ 79 ] = PR_getcwd, [ 80 ] = PR_chdir, [ 81 ] = PR_fchdir, [ 82 ] = PR_rename, [ 83 ] = PR_mkdir, [ 84 ] = PR_rmdir, [ 85 ] = PR_creat, [ 86 ] = PR_link, [ 87 ] = PR_unlink, [ 88 ] = PR_symlink, [ 89 ] = PR_readlink, [ 90 ] = PR_chmod, [ 91 ] = PR_fchmod, [ 92 ] = PR_chown, [ 93 ] = PR_fchown, [ 94 ] = PR_lchown, [ 95 ] = PR_umask, [ 96 ] = PR_gettimeofday, [ 97 ] = PR_getrlimit, [ 98 ] = PR_getrusage, [ 99 ] = PR_sysinfo, [ 100 ] = PR_times, [ 101 ] = PR_ptrace, [ 102 ] = PR_getuid, [ 103 ] = PR_syslog, [ 104 ] = PR_getgid, [ 105 ] = PR_setuid, [ 106 ] = PR_setgid, [ 107 ] = PR_geteuid, [ 108 ] = PR_getegid, [ 109 ] = PR_setpgid, [ 110 ] = PR_getppid, [ 111 ] = PR_getpgrp, [ 112 ] = PR_setsid, [ 113 ] = PR_setreuid, [ 114 ] = PR_setregid, [ 115 ] = PR_getgroups, [ 116 ] = PR_setgroups, [ 117 ] = PR_setresuid, [ 118 ] = PR_getresuid, [ 119 ] = PR_setresgid, [ 120 ] = PR_getresgid, [ 121 ] = PR_getpgid, [ 122 ] = PR_setfsuid, [ 123 ] = PR_setfsgid, [ 124 ] = PR_getsid, [ 125 ] = PR_capget, [ 126 ] = PR_capset, [ 127 ] = PR_rt_sigpending, [ 128 ] = PR_rt_sigtimedwait, [ 129 ] = PR_rt_sigqueueinfo, [ 130 ] = PR_rt_sigsuspend, [ 131 ] = PR_sigaltstack, [ 132 ] = PR_utime, [ 133 ] = PR_mknod, [ 134 ] = PR_uselib, [ 135 ] = PR_personality, [ 136 ] = PR_ustat, [ 137 ] = PR_statfs, [ 138 ] = PR_fstatfs, [ 139 ] = PR_sysfs, [ 140 ] = PR_getpriority, [ 141 ] = PR_setpriority, [ 142 ] = PR_sched_setparam, [ 143 ] = PR_sched_getparam, [ 144 ] = PR_sched_setscheduler, [ 145 ] = PR_sched_getscheduler, [ 146 ] = PR_sched_get_priority_max, [ 147 ] = PR_sched_get_priority_min, [ 148 ] = PR_sched_rr_get_interval, [ 149 ] = PR_mlock, [ 150 ] = PR_munlock, [ 151 ] = PR_mlockall, [ 152 ] = PR_munlockall, [ 153 ] = PR_vhangup, [ 154 ] = PR_modify_ldt, [ 155 ] = PR_pivot_root, [ 156 ] = PR__sysctl, [ 157 ] = PR_prctl, [ 158 ] = PR_arch_prctl, [ 159 ] = PR_adjtimex, [ 160 ] = PR_setrlimit, [ 161 ] = PR_chroot, [ 162 ] = PR_sync, [ 163 ] = PR_acct, [ 164 ] = PR_settimeofday, [ 165 ] = PR_mount, [ 166 ] = PR_umount2, [ 167 ] = PR_swapon, [ 168 ] = PR_swapoff, [ 169 ] = PR_reboot, [ 170 ] = PR_sethostname, [ 171 ] = PR_setdomainname, [ 172 ] = PR_iopl, [ 173 ] = PR_ioperm, [ 174 ] = PR_create_module, [ 175 ] = PR_init_module, [ 176 ] = PR_delete_module, [ 177 ] = PR_get_kernel_syms, [ 178 ] = PR_query_module, [ 179 ] = PR_quotactl, [ 180 ] = PR_nfsservctl, [ 181 ] = PR_getpmsg, [ 182 ] = PR_putpmsg, [ 183 ] = PR_afs_syscall, [ 184 ] = PR_tuxcall, [ 185 ] = PR_security, [ 186 ] = PR_gettid, [ 187 ] = PR_readahead, [ 188 ] = PR_setxattr, [ 189 ] = PR_lsetxattr, [ 190 ] = PR_fsetxattr, [ 191 ] = PR_getxattr, [ 192 ] = PR_lgetxattr, [ 193 ] = PR_fgetxattr, [ 194 ] = PR_listxattr, [ 195 ] = PR_llistxattr, [ 196 ] = PR_flistxattr, [ 197 ] = PR_removexattr, [ 198 ] = PR_lremovexattr, [ 199 ] = PR_fremovexattr, [ 200 ] = PR_tkill, [ 201 ] = PR_time, [ 202 ] = PR_futex, [ 203 ] = PR_sched_setaffinity, [ 204 ] = PR_sched_getaffinity, [ 205 ] = PR_set_thread_area, [ 206 ] = PR_io_setup, [ 207 ] = PR_io_destroy, [ 208 ] = PR_io_getevents, [ 209 ] = PR_io_submit, [ 210 ] = PR_io_cancel, [ 211 ] = PR_get_thread_area, [ 212 ] = PR_lookup_dcookie, [ 213 ] = PR_epoll_create, [ 214 ] = PR_epoll_ctl_old, [ 215 ] = PR_epoll_wait_old, [ 216 ] = PR_remap_file_pages, [ 217 ] = PR_getdents64, [ 218 ] = PR_set_tid_address, [ 219 ] = PR_restart_syscall, [ 220 ] = PR_semtimedop, [ 221 ] = PR_fadvise64, [ 222 ] = PR_timer_create, [ 223 ] = PR_timer_settime, [ 224 ] = PR_timer_gettime, [ 225 ] = PR_timer_getoverrun, [ 226 ] = PR_timer_delete, [ 227 ] = PR_clock_settime, [ 228 ] = PR_clock_gettime, [ 229 ] = PR_clock_getres, [ 230 ] = PR_clock_nanosleep, [ 231 ] = PR_exit_group, [ 232 ] = PR_epoll_wait, [ 233 ] = PR_epoll_ctl, [ 234 ] = PR_tgkill, [ 235 ] = PR_utimes, [ 236 ] = PR_vserver, [ 237 ] = PR_mbind, [ 238 ] = PR_set_mempolicy, [ 239 ] = PR_get_mempolicy, [ 240 ] = PR_mq_open, [ 241 ] = PR_mq_unlink, [ 242 ] = PR_mq_timedsend, [ 243 ] = PR_mq_timedreceive, [ 244 ] = PR_mq_notify, [ 245 ] = PR_mq_getsetattr, [ 246 ] = PR_kexec_load, [ 247 ] = PR_waitid, [ 248 ] = PR_add_key, [ 249 ] = PR_request_key, [ 250 ] = PR_keyctl, [ 251 ] = PR_ioprio_set, [ 252 ] = PR_ioprio_get, [ 253 ] = PR_inotify_init, [ 254 ] = PR_inotify_add_watch, [ 255 ] = PR_inotify_rm_watch, [ 256 ] = PR_migrate_pages, [ 257 ] = PR_openat, [ 258 ] = PR_mkdirat, [ 259 ] = PR_mknodat, [ 260 ] = PR_fchownat, [ 261 ] = PR_futimesat, [ 262 ] = PR_newfstatat, [ 263 ] = PR_unlinkat, [ 264 ] = PR_renameat, [ 265 ] = PR_linkat, [ 266 ] = PR_symlinkat, [ 267 ] = PR_readlinkat, [ 268 ] = PR_fchmodat, [ 269 ] = PR_faccessat, [ 270 ] = PR_pselect6, [ 271 ] = PR_ppoll, [ 272 ] = PR_unshare, [ 273 ] = PR_set_robust_list, [ 274 ] = PR_get_robust_list, [ 275 ] = PR_splice, [ 276 ] = PR_tee, [ 277 ] = PR_sync_file_range, [ 278 ] = PR_vmsplice, [ 279 ] = PR_move_pages, [ 280 ] = PR_utimensat, [ 281 ] = PR_epoll_pwait, [ 282 ] = PR_signalfd, [ 283 ] = PR_timerfd_create, [ 284 ] = PR_eventfd, [ 285 ] = PR_fallocate, [ 286 ] = PR_timerfd_settime, [ 287 ] = PR_timerfd_gettime, [ 288 ] = PR_accept4, [ 289 ] = PR_signalfd4, [ 290 ] = PR_eventfd2, [ 291 ] = PR_epoll_create1, [ 292 ] = PR_dup3, [ 293 ] = PR_pipe2, [ 294 ] = PR_inotify_init1, [ 295 ] = PR_preadv, [ 296 ] = PR_pwritev, [ 297 ] = PR_rt_tgsigqueueinfo, [ 298 ] = PR_perf_event_open, [ 299 ] = PR_recvmmsg, [ 300 ] = PR_fanotify_init, [ 301 ] = PR_fanotify_mark, [ 302 ] = PR_prlimit64, [ 303 ] = PR_name_to_handle_at, [ 304 ] = PR_open_by_handle_at, [ 305 ] = PR_clock_adjtime, [ 306 ] = PR_syncfs, [ 307 ] = PR_sendmmsg, [ 308 ] = PR_setns, [ 309 ] = PR_getcpu, [ 310 ] = PR_process_vm_readv, [ 311 ] = PR_process_vm_writev, [ 312 ] = PR_kcmp, }; care-2.2.1/src/syscall/exit.c0000644000175000017500000002654012430615044015366 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* errno(3), E* */ #include /* struct utsname, */ #include /* SYS_*, */ #include /* strlen(3), */ #include "syscall/syscall.h" #include "syscall/sysnum.h" #include "syscall/socket.h" #include "syscall/chain.h" #include "syscall/heap.h" #include "tracee/tracee.h" #include "tracee/reg.h" #include "tracee/mem.h" #include "tracee/abi.h" #include "path/path.h" #include "ptrace/ptrace.h" #include "ptrace/wait.h" #include "extension/extension.h" #include "arch.h" /** * Translate the output arguments of the current @tracee's syscall in * the @tracee->pid process area. This function sets the result of * this syscall to @tracee->status if an error occured previously * during the translation, that is, if @tracee->status is less than 0. */ void translate_syscall_exit(Tracee *tracee) { word_t syscall_number; word_t syscall_result; int status; status = notify_extensions(tracee, SYSCALL_EXIT_START, 0, 0); if (status < 0) { poke_reg(tracee, SYSARG_RESULT, (word_t) status); goto end; } if (status > 0) return; /* Set the tracee's errno if an error occured previously during * the translation. */ if (tracee->status < 0) { poke_reg(tracee, SYSARG_RESULT, (word_t) tracee->status); goto end; } /* Translate output arguments: * - break: update the syscall result register with "status" * - goto end: nothing else to do. */ syscall_number = get_sysnum(tracee, ORIGINAL); syscall_result = peek_reg(tracee, CURRENT, SYSARG_RESULT); switch (syscall_number) { case PR_brk: translate_brk_exit(tracee); goto end; case PR_getcwd: { char path[PATH_MAX]; size_t new_size; size_t size; word_t output; size = (size_t) peek_reg(tracee, ORIGINAL, SYSARG_2); if (size == 0) { status = -EINVAL; break; } /* Ensure cwd still exists. */ status = translate_path(tracee, path, AT_FDCWD, ".", false); if (status < 0) break; new_size = strlen(tracee->fs->cwd) + 1; if (size < new_size) { status = -ERANGE; break; } /* Overwrite the path. */ output = peek_reg(tracee, ORIGINAL, SYSARG_1); status = write_data(tracee, output, tracee->fs->cwd, new_size); if (status < 0) break; /* The value of "status" is used to update the returned value * in translate_syscall_exit(). */ status = new_size; break; } case PR_accept: case PR_accept4: /* Nothing special to do if no sockaddr was specified. */ if (peek_reg(tracee, ORIGINAL, SYSARG_2) == 0) goto end; /* Fall through. */ case PR_getsockname: case PR_getpeername: { word_t sock_addr; word_t size_addr; word_t max_size; /* Error reported by the kernel. */ if ((int) syscall_result < 0) goto end; sock_addr = peek_reg(tracee, ORIGINAL, SYSARG_2); size_addr = peek_reg(tracee, MODIFIED, SYSARG_3); max_size = peek_reg(tracee, MODIFIED, SYSARG_6); status = translate_socketcall_exit(tracee, sock_addr, size_addr, max_size); if (status < 0) break; /* Don't overwrite the syscall result. */ goto end; } #define SYSARG_ADDR(n) (args_addr + ((n) - 1) * sizeof_word(tracee)) #define POKE_WORD(addr, value) \ poke_word(tracee, addr, value); \ if (errno != 0) { \ status = -errno; \ break; \ } #define PEEK_WORD(addr) \ peek_word(tracee, addr); \ if (errno != 0) { \ status = -errno; \ break; \ } case PR_socketcall: { word_t args_addr; word_t sock_addr; word_t size_addr; word_t max_size; args_addr = peek_reg(tracee, ORIGINAL, SYSARG_2); switch (peek_reg(tracee, ORIGINAL, SYSARG_1)) { case SYS_ACCEPT: case SYS_ACCEPT4: /* Nothing special to do if no sockaddr was specified. */ sock_addr = PEEK_WORD(SYSARG_ADDR(2)); if (sock_addr == 0) goto end; /* Fall through. */ case SYS_GETSOCKNAME: case SYS_GETPEERNAME: /* Handle these cases below. */ status = 1; break; case SYS_BIND: case SYS_CONNECT: /* Restore the initial parameters: this memory was * overwritten at the enter stage. Remember: POKE_WORD * puts -errno in status and breaks if an error * occured. */ POKE_WORD(SYSARG_ADDR(2), peek_reg(tracee, MODIFIED, SYSARG_5)); POKE_WORD(SYSARG_ADDR(3), peek_reg(tracee, MODIFIED, SYSARG_6)); status = 0; break; default: status = 0; break; } /* Error reported by the kernel or there's nothing else to do. */ if ((int) syscall_result < 0 || status == 0) goto end; /* An error occured in SYS_BIND or SYS_CONNECT. */ if (status < 0) break; /* Remember: PEEK_WORD puts -errno in status and breaks if an * error occured. */ sock_addr = PEEK_WORD(SYSARG_ADDR(2)); size_addr = PEEK_WORD(SYSARG_ADDR(3)); max_size = peek_reg(tracee, MODIFIED, SYSARG_6); status = translate_socketcall_exit(tracee, sock_addr, size_addr, max_size); if (status < 0) break; /* Don't overwrite the syscall result. */ goto end; } #undef SYSARG_ADDR #undef PEEK_WORD #undef POKE_WORD case PR_fchdir: case PR_chdir: /* These syscalls are fully emulated, see enter.c for details * (like errors). */ status = 0; break; case PR_rename: case PR_renameat: { char old_path[PATH_MAX]; char new_path[PATH_MAX]; ssize_t old_length; ssize_t new_length; Comparison comparison; Reg old_reg; Reg new_reg; char *tmp; /* Error reported by the kernel. */ if ((int) syscall_result < 0) goto end; if (syscall_number == PR_rename) { old_reg = SYSARG_1; new_reg = SYSARG_2; } else { old_reg = SYSARG_2; new_reg = SYSARG_4; } /* Get the old path, then convert it to the same * "point-of-view" as tracee->fs->cwd (guest). */ status = read_path(tracee, old_path, peek_reg(tracee, MODIFIED, old_reg)); if (status < 0) break; status = detranslate_path(tracee, old_path, NULL); if (status < 0) break; old_length = (status > 0 ? status - 1 : (ssize_t) strlen(old_path)); /* Nothing special to do if the moved path is not the * current working directory. */ comparison = compare_paths(old_path, tracee->fs->cwd); if (comparison != PATH1_IS_PREFIX && comparison != PATHS_ARE_EQUAL) { status = 0; break; } /* Get the new path, then convert it to the same * "point-of-view" as tracee->fs->cwd (guest). */ status = read_path(tracee, new_path, peek_reg(tracee, MODIFIED, new_reg)); if (status < 0) break; status = detranslate_path(tracee, new_path, NULL); if (status < 0) break; new_length = (status > 0 ? status - 1 : (ssize_t) strlen(new_path)); /* Sanity check. */ if (strlen(tracee->fs->cwd) >= PATH_MAX) { status = 0; break; } strcpy(old_path, tracee->fs->cwd); /* Update the virtual current working directory. */ substitute_path_prefix(old_path, old_length, new_path, new_length); tmp = talloc_strdup(tracee->fs, old_path); if (tmp == NULL) { status = -ENOMEM; break; } TALLOC_FREE(tracee->fs->cwd); tracee->fs->cwd = tmp; status = 0; break; } case PR_readlink: case PR_readlinkat: { char referee[PATH_MAX]; char referer[PATH_MAX]; size_t old_size; size_t new_size; size_t max_size; word_t input; word_t output; /* Error reported by the kernel. */ if ((int) syscall_result < 0) goto end; old_size = syscall_result; if (syscall_number == PR_readlink) { output = peek_reg(tracee, ORIGINAL, SYSARG_2); max_size = peek_reg(tracee, ORIGINAL, SYSARG_3); input = peek_reg(tracee, MODIFIED, SYSARG_1); } else { output = peek_reg(tracee, ORIGINAL, SYSARG_3); max_size = peek_reg(tracee, ORIGINAL, SYSARG_4); input = peek_reg(tracee, MODIFIED, SYSARG_2); } if (max_size > PATH_MAX) max_size = PATH_MAX; if (max_size == 0) { status = -EINVAL; break; } /* The kernel does NOT put the NULL terminating byte for * readlink(2). */ status = read_data(tracee, referee, output, old_size); if (status < 0) break; referee[old_size] = '\0'; /* Not optimal but safe (path is fully translated). */ status = read_path(tracee, referer, input); if (status < 0) break; if (status >= PATH_MAX) { status = -ENAMETOOLONG; break; } status = detranslate_path(tracee, referee, referer); if (status < 0) break; /* The original path doesn't require any transformation, i.e * it is a symetric binding. */ if (status == 0) goto end; /* Overwrite the path. Note: the output buffer might be * initialized with zeros but it was updated with the kernel * result, and then with the detranslated result. This later * might be shorter than the former, so it's safier to add a * NULL terminating byte when possible. This problem was * exposed by IDA Demo 6.3. */ if ((size_t) status < max_size) { new_size = status - 1; status = write_data(tracee, output, referee, status); } else { new_size = max_size; status = write_data(tracee, output, referee, max_size); } if (status < 0) break; /* The value of "status" is used to update the returned value * in translate_syscall_exit(). */ status = new_size; break; } #if defined(ARCH_X86_64) case PR_uname: { struct utsname utsname; word_t address; size_t size; if (get_abi(tracee) != ABI_2) goto end; /* Error reported by the kernel. */ if ((int) syscall_result < 0) goto end; address = peek_reg(tracee, ORIGINAL, SYSARG_1); status = read_data(tracee, &utsname, address, sizeof(utsname)); if (status < 0) break; /* Some 32-bit programs like package managers can be * confused when the kernel reports "x86_64". */ size = sizeof(utsname.machine); strncpy(utsname.machine, "i686", size); utsname.machine[size - 1] = '\0'; status = write_data(tracee, address, &utsname, sizeof(utsname)); if (status < 0) break; status = 0; break; } #endif case PR_execve: if ((int) syscall_result >= 0) { /* New processes have no heap. */ bzero(tracee->heap, sizeof(Heap)); case PR_rt_sigreturn: case PR_sigreturn: tracee->restore_original_regs = false; } goto end; case PR_ptrace: status = translate_ptrace_exit(tracee); break; case PR_wait4: case PR_waitpid: if (tracee->as_ptracer.waits_in != WAITS_IN_PROOT) { pid_t pid; /* See ptrace/wait.c for explanation. */ pid = (pid_t) syscall_result; if (pid > 0 && is_exited_direct_ptracee(tracee, pid)) { remove_exited_direct_ptracee(tracee, pid); restart_original_syscall(tracee); } goto end; } status = translate_wait_exit(tracee); break; default: goto end; } poke_reg(tracee, SYSARG_RESULT, (word_t) status); end: status = notify_extensions(tracee, SYSCALL_EXIT_END, 0, 0); if (status < 0) poke_reg(tracee, SYSARG_RESULT, (word_t) status); } care-2.2.1/src/syscall/sysnums-arm.h0000644000175000017500000002006512430615044016714 0ustar ivoireivoire#include "syscall/sysnum.h" static const Sysnum sysnums_arm[] = { [ 0 ] = PR_restart_syscall, [ 1 ] = PR_exit, [ 2 ] = PR_fork, [ 3 ] = PR_read, [ 4 ] = PR_write, [ 5 ] = PR_open, [ 6 ] = PR_close, [ 8 ] = PR_creat, [ 9 ] = PR_link, [ 10 ] = PR_unlink, [ 11 ] = PR_execve, [ 12 ] = PR_chdir, [ 14 ] = PR_mknod, [ 15 ] = PR_chmod, [ 16 ] = PR_lchown, [ 19 ] = PR_lseek, [ 20 ] = PR_getpid, [ 21 ] = PR_mount, [ 23 ] = PR_setuid, [ 24 ] = PR_getuid, [ 26 ] = PR_ptrace, [ 29 ] = PR_pause, [ 33 ] = PR_access, [ 34 ] = PR_nice, [ 36 ] = PR_sync, [ 37 ] = PR_kill, [ 38 ] = PR_rename, [ 39 ] = PR_mkdir, [ 40 ] = PR_rmdir, [ 41 ] = PR_dup, [ 42 ] = PR_pipe, [ 43 ] = PR_times, [ 45 ] = PR_brk, [ 46 ] = PR_setgid, [ 47 ] = PR_getgid, [ 49 ] = PR_geteuid, [ 50 ] = PR_getegid, [ 51 ] = PR_acct, [ 52 ] = PR_umount2, [ 54 ] = PR_ioctl, [ 55 ] = PR_fcntl, [ 57 ] = PR_setpgid, [ 60 ] = PR_umask, [ 61 ] = PR_chroot, [ 62 ] = PR_ustat, [ 63 ] = PR_dup2, [ 64 ] = PR_getppid, [ 65 ] = PR_getpgrp, [ 66 ] = PR_setsid, [ 67 ] = PR_sigaction, [ 70 ] = PR_setreuid, [ 71 ] = PR_setregid, [ 72 ] = PR_sigsuspend, [ 73 ] = PR_sigpending, [ 74 ] = PR_sethostname, [ 75 ] = PR_setrlimit, [ 77 ] = PR_getrusage, [ 78 ] = PR_gettimeofday, [ 79 ] = PR_settimeofday, [ 80 ] = PR_getgroups, [ 81 ] = PR_setgroups, [ 83 ] = PR_symlink, [ 85 ] = PR_readlink, [ 86 ] = PR_uselib, [ 87 ] = PR_swapon, [ 88 ] = PR_reboot, [ 91 ] = PR_munmap, [ 92 ] = PR_truncate, [ 93 ] = PR_ftruncate, [ 94 ] = PR_fchmod, [ 95 ] = PR_fchown, [ 96 ] = PR_getpriority, [ 97 ] = PR_setpriority, [ 99 ] = PR_statfs, [ 100 ] = PR_fstatfs, [ 103 ] = PR_syslog, [ 104 ] = PR_setitimer, [ 105 ] = PR_getitimer, [ 106 ] = PR_stat, [ 107 ] = PR_lstat, [ 108 ] = PR_fstat, [ 111 ] = PR_vhangup, [ 114 ] = PR_wait4, [ 115 ] = PR_swapoff, [ 116 ] = PR_sysinfo, [ 118 ] = PR_fsync, [ 119 ] = PR_sigreturn, [ 120 ] = PR_clone, [ 121 ] = PR_setdomainname, [ 122 ] = PR_uname, [ 124 ] = PR_adjtimex, [ 125 ] = PR_mprotect, [ 126 ] = PR_sigprocmask, [ 128 ] = PR_init_module, [ 129 ] = PR_delete_module, [ 131 ] = PR_quotactl, [ 132 ] = PR_getpgid, [ 133 ] = PR_fchdir, [ 134 ] = PR_bdflush, [ 135 ] = PR_sysfs, [ 136 ] = PR_personality, [ 138 ] = PR_setfsuid, [ 139 ] = PR_setfsgid, [ 140 ] = PR__llseek, [ 141 ] = PR_getdents, [ 142 ] = PR__newselect, [ 143 ] = PR_flock, [ 144 ] = PR_msync, [ 145 ] = PR_readv, [ 146 ] = PR_writev, [ 147 ] = PR_getsid, [ 148 ] = PR_fdatasync, [ 149 ] = PR__sysctl, [ 150 ] = PR_mlock, [ 151 ] = PR_munlock, [ 152 ] = PR_mlockall, [ 153 ] = PR_munlockall, [ 154 ] = PR_sched_setparam, [ 155 ] = PR_sched_getparam, [ 156 ] = PR_sched_setscheduler, [ 157 ] = PR_sched_getscheduler, [ 158 ] = PR_sched_yield, [ 159 ] = PR_sched_get_priority_max, [ 160 ] = PR_sched_get_priority_min, [ 161 ] = PR_sched_rr_get_interval, [ 162 ] = PR_nanosleep, [ 163 ] = PR_mremap, [ 164 ] = PR_setresuid, [ 165 ] = PR_getresuid, [ 168 ] = PR_poll, [ 169 ] = PR_nfsservctl, [ 170 ] = PR_setresgid, [ 171 ] = PR_getresgid, [ 172 ] = PR_prctl, [ 173 ] = PR_rt_sigreturn, [ 174 ] = PR_rt_sigaction, [ 175 ] = PR_rt_sigprocmask, [ 176 ] = PR_rt_sigpending, [ 177 ] = PR_rt_sigtimedwait, [ 178 ] = PR_rt_sigqueueinfo, [ 179 ] = PR_rt_sigsuspend, [ 180 ] = PR_pread64, [ 181 ] = PR_pwrite64, [ 182 ] = PR_chown, [ 183 ] = PR_getcwd, [ 184 ] = PR_capget, [ 185 ] = PR_capset, [ 186 ] = PR_sigaltstack, [ 187 ] = PR_sendfile, [ 190 ] = PR_vfork, [ 191 ] = PR_ugetrlimit, [ 192 ] = PR_mmap2, [ 193 ] = PR_truncate64, [ 194 ] = PR_ftruncate64, [ 195 ] = PR_stat64, [ 196 ] = PR_lstat64, [ 197 ] = PR_fstat64, [ 198 ] = PR_lchown32, [ 199 ] = PR_getuid32, [ 200 ] = PR_getgid32, [ 201 ] = PR_geteuid32, [ 202 ] = PR_getegid32, [ 203 ] = PR_setreuid32, [ 204 ] = PR_setregid32, [ 205 ] = PR_getgroups32, [ 206 ] = PR_setgroups32, [ 207 ] = PR_fchown32, [ 208 ] = PR_setresuid32, [ 209 ] = PR_getresuid32, [ 210 ] = PR_setresgid32, [ 211 ] = PR_getresgid32, [ 212 ] = PR_chown32, [ 213 ] = PR_setuid32, [ 214 ] = PR_setgid32, [ 215 ] = PR_setfsuid32, [ 216 ] = PR_setfsgid32, [ 217 ] = PR_getdents64, [ 218 ] = PR_pivot_root, [ 219 ] = PR_mincore, [ 220 ] = PR_madvise, [ 221 ] = PR_fcntl64, [ 222 ] = PR_void, [ 224 ] = PR_gettid, [ 225 ] = PR_readahead, [ 226 ] = PR_setxattr, [ 227 ] = PR_lsetxattr, [ 228 ] = PR_fsetxattr, [ 229 ] = PR_getxattr, [ 230 ] = PR_lgetxattr, [ 231 ] = PR_fgetxattr, [ 232 ] = PR_listxattr, [ 233 ] = PR_llistxattr, [ 234 ] = PR_flistxattr, [ 235 ] = PR_removexattr, [ 236 ] = PR_lremovexattr, [ 237 ] = PR_fremovexattr, [ 238 ] = PR_tkill, [ 239 ] = PR_sendfile64, [ 240 ] = PR_futex, [ 241 ] = PR_sched_setaffinity, [ 242 ] = PR_sched_getaffinity, [ 243 ] = PR_io_setup, [ 244 ] = PR_io_destroy, [ 245 ] = PR_io_getevents, [ 246 ] = PR_io_submit, [ 247 ] = PR_io_cancel, [ 248 ] = PR_exit_group, [ 249 ] = PR_lookup_dcookie, [ 250 ] = PR_epoll_create, [ 251 ] = PR_epoll_ctl, [ 252 ] = PR_epoll_wait, [ 253 ] = PR_remap_file_pages, [ 256 ] = PR_set_tid_address, [ 257 ] = PR_timer_create, [ 258 ] = PR_timer_settime, [ 259 ] = PR_timer_gettime, [ 260 ] = PR_timer_getoverrun, [ 261 ] = PR_timer_delete, [ 262 ] = PR_clock_settime, [ 263 ] = PR_clock_gettime, [ 264 ] = PR_clock_getres, [ 265 ] = PR_clock_nanosleep, [ 266 ] = PR_statfs64, [ 267 ] = PR_fstatfs64, [ 268 ] = PR_tgkill, [ 269 ] = PR_utimes, [ 270 ] = PR_arm_fadvise64_64, [ 271 ] = PR_pciconfig_iobase, [ 272 ] = PR_pciconfig_read, [ 273 ] = PR_pciconfig_write, [ 274 ] = PR_mq_open, [ 275 ] = PR_mq_unlink, [ 276 ] = PR_mq_timedsend, [ 277 ] = PR_mq_timedreceive, [ 278 ] = PR_mq_notify, [ 279 ] = PR_mq_getsetattr, [ 280 ] = PR_waitid, [ 281 ] = PR_socket, [ 282 ] = PR_bind, [ 283 ] = PR_connect, [ 284 ] = PR_listen, [ 285 ] = PR_accept, [ 286 ] = PR_getsockname, [ 287 ] = PR_getpeername, [ 288 ] = PR_socketpair, [ 289 ] = PR_send, [ 290 ] = PR_sendto, [ 291 ] = PR_recv, [ 292 ] = PR_recvfrom, [ 293 ] = PR_shutdown, [ 294 ] = PR_setsockopt, [ 295 ] = PR_getsockopt, [ 296 ] = PR_sendmsg, [ 297 ] = PR_recvmsg, [ 298 ] = PR_semop, [ 299 ] = PR_semget, [ 300 ] = PR_semctl, [ 301 ] = PR_msgsnd, [ 302 ] = PR_msgrcv, [ 303 ] = PR_msgget, [ 304 ] = PR_msgctl, [ 305 ] = PR_shmat, [ 306 ] = PR_shmdt, [ 307 ] = PR_shmget, [ 308 ] = PR_shmctl, [ 309 ] = PR_add_key, [ 310 ] = PR_request_key, [ 311 ] = PR_keyctl, [ 312 ] = PR_semtimedop, [ 313 ] = PR_vserver, [ 314 ] = PR_ioprio_set, [ 315 ] = PR_ioprio_get, [ 316 ] = PR_inotify_init, [ 317 ] = PR_inotify_add_watch, [ 318 ] = PR_inotify_rm_watch, [ 319 ] = PR_mbind, [ 320 ] = PR_get_mempolicy, [ 321 ] = PR_set_mempolicy, [ 322 ] = PR_openat, [ 323 ] = PR_mkdirat, [ 324 ] = PR_mknodat, [ 325 ] = PR_fchownat, [ 326 ] = PR_futimesat, [ 327 ] = PR_fstatat64, [ 328 ] = PR_unlinkat, [ 329 ] = PR_renameat, [ 330 ] = PR_linkat, [ 331 ] = PR_symlinkat, [ 332 ] = PR_readlinkat, [ 333 ] = PR_fchmodat, [ 334 ] = PR_faccessat, [ 335 ] = PR_pselect6, [ 336 ] = PR_ppoll, [ 337 ] = PR_unshare, [ 338 ] = PR_set_robust_list, [ 339 ] = PR_get_robust_list, [ 340 ] = PR_splice, [ 341 ] = PR_arm_sync_file_range, [ 342 ] = PR_tee, [ 343 ] = PR_vmsplice, [ 344 ] = PR_move_pages, [ 345 ] = PR_getcpu, [ 346 ] = PR_epoll_pwait, [ 347 ] = PR_kexec_load, [ 348 ] = PR_utimensat, [ 349 ] = PR_signalfd, [ 350 ] = PR_timerfd_create, [ 351 ] = PR_eventfd, [ 352 ] = PR_fallocate, [ 353 ] = PR_timerfd_settime, [ 354 ] = PR_timerfd_gettime, [ 355 ] = PR_signalfd4, [ 356 ] = PR_eventfd2, [ 357 ] = PR_epoll_create1, [ 358 ] = PR_dup3, [ 359 ] = PR_pipe2, [ 360 ] = PR_inotify_init1, [ 361 ] = PR_preadv, [ 362 ] = PR_pwritev, [ 363 ] = PR_rt_tgsigqueueinfo, [ 364 ] = PR_perf_event_open, [ 365 ] = PR_recvmmsg, [ 366 ] = PR_accept4, [ 367 ] = PR_fanotify_init, [ 368 ] = PR_fanotify_mark, [ 369 ] = PR_prlimit64, [ 370 ] = PR_name_to_handle_at, [ 371 ] = PR_open_by_handle_at, [ 372 ] = PR_clock_adjtime, [ 373 ] = PR_syncfs, [ 374 ] = PR_sendmmsg, [ 375 ] = PR_setns, [ 376 ] = PR_process_vm_readv, [ 377 ] = PR_process_vm_writev, }; care-2.2.1/src/syscall/heap.h0000644000175000017500000000261712430615044015336 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef HEAP_H #define HEAP_H #include "tracee/tracee.h" extern int translate_brk_enter(Tracee *tracee); extern void translate_brk_exit(Tracee *tracee); /** * Check if the @address is in the @tracee's preallocated heap space. * This space is not supposed to be accessible. */ static inline bool belongs_to_heap_prealloc(const Tracee *tracee, word_t address) { return (tracee->heap != NULL && !tracee->heap->disabled && address >= tracee->heap->base + tracee->heap->size && address < tracee->heap->base + tracee->heap->prealloc_size); } #endif /* HEAP_H */ care-2.2.1/src/syscall/socket.c0000644000175000017500000001521512430615044015702 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* offsetof(3), */ #include /* bzero(3), */ #include /* strncpy(3), strlen(3), */ #include /* assert(3), */ #include /* E*, */ #include /* struct sockaddr_un, AF_UNIX, */ #include /* struct sockaddr_un, */ #include /* MIN(), MAX(), */ #include "syscall/socket.h" #include "tracee/tracee.h" #include "tracee/mem.h" #include "path/binding.h" #include "path/temp.h" #include "path/path.h" #include "arch.h" #include "compat.h" /* The sockaddr_un structure has exactly the same layout on all * architectures. */ static const off_t offsetof_path = offsetof(struct sockaddr_un, sun_path); extern struct sockaddr_un sockaddr_un__; static const size_t sizeof_path = sizeof(sockaddr_un__.sun_path); /** * Copy in @sockaddr the struct sockaddr_un stored in the @tracee * memory at the given @address. Also, its pathname is copied to the * null-terminated @path. Only @size bytes are read from the @tracee * memory (should be <= @max_size <= sizeof(struct sockaddr_un)). * This function returns -errno if an error occurred, 0 if the * structure was not found (not a sockaddr_un or @size > @max_size), * otherwise 1. */ static int read_sockaddr_un(Tracee *tracee, struct sockaddr_un *sockaddr, word_t max_size, char path[PATH_MAX], word_t address, int size) { int status; assert(max_size <= sizeof(struct sockaddr_un)); /* Nothing to do if the sockaddr has an unexpected size. */ if (size <= offsetof_path || (word_t) size > max_size) return 0; bzero(sockaddr, sizeof(struct sockaddr_un)); status = read_data(tracee, sockaddr, address, size); if (status < 0) return status; /* Nothing to do if it's not a named Unix domain socket. */ if ((sockaddr->sun_family != AF_UNIX) || sockaddr->sun_path[0] == '\0') return 0; /* Be careful: sun_path doesn't have to be null-terminated. */ assert(sizeof_path < PATH_MAX - 1); strncpy(path, sockaddr->sun_path, sizeof_path); path[sizeof_path] = '\0'; return 1; } /** * Translate the pathname of the struct sockaddr_un currently stored * in the @tracee memory at the given @address. See the documentation * of read_sockaddr_un() for the meaning of the @size parameter. * Also, the new address of the translated sockaddr_un is put in the * @address parameter. This function returns -errno if an error * occurred, otherwise 0. */ int translate_socketcall_enter(Tracee *tracee, word_t *address, int size) { struct sockaddr_un sockaddr; char user_path[PATH_MAX]; char host_path[PATH_MAX]; int status; if (*address == 0) return 0; status = read_sockaddr_un(tracee, &sockaddr, sizeof(sockaddr), user_path, *address, size); if (status <= 0) return status; status = translate_path(tracee, host_path, AT_FDCWD, user_path, true); if (status < 0) return status; /* Be careful: sun_path doesn't have to be null-terminated. */ if (strlen(host_path) > sizeof_path) { char *shorter_host_path; Binding *binding; /* The translated path is too long to fit the sun_path * array, so let's bind it to a shorter path. */ shorter_host_path = create_temp_name(tracee, "proot"); if (shorter_host_path == NULL || strlen(shorter_host_path) > sizeof_path) return -EINVAL; (void) mktemp(shorter_host_path); if (strlen(shorter_host_path) > sizeof_path) return -EINVAL; /* Ensure the guest path of this new binding is * canonicalized, as it is always assumed. */ strcpy(user_path, host_path); status = detranslate_path(tracee, user_path, NULL); if (status < 0) return -EINVAL; /* Bing the guest path to a shorter host path. */ binding = insort_binding3(tracee, tracee->ctx, shorter_host_path, user_path); if (binding == NULL) return -EINVAL; /* This temporary file (shorter_host_path) will be removed once the * binding is destroyed. */ talloc_reparent(tracee->ctx, binding, shorter_host_path); /* Let's use this shorter path now. */ strcpy(host_path, shorter_host_path); } strncpy(sockaddr.sun_path, host_path, sizeof_path); /* Push the updated sockaddr to a newly allocated space. */ *address = alloc_mem(tracee, sizeof(sockaddr)); if (*address == 0) return -EFAULT; status = write_data(tracee, *address, &sockaddr, sizeof(sockaddr)); if (status < 0) return status; return 1; } /** * Detranslate the pathname of the struct sockaddr_un currently stored * in the @tracee memory at the given @sock_addr. See the * documentation of read_sockaddr_un() for the meaning of the * @size_addr and @max_size parameters. This function returns -errno * if an error occurred, otherwise 0. */ int translate_socketcall_exit(Tracee *tracee, word_t sock_addr, word_t size_addr, word_t max_size) { struct sockaddr_un sockaddr; bool is_truncated = false; char path[PATH_MAX]; int status; int size; if (sock_addr == 0) return 0; size = peek_int32(tracee, size_addr); if (errno != 0) return -errno; max_size = MIN(max_size, sizeof(sockaddr)); status = read_sockaddr_un(tracee, &sockaddr, max_size, path, sock_addr, size); if (status <= 0) return status; status = detranslate_path(tracee, path, NULL); if (status < 0) return status; /* Be careful: sun_path doesn't have to be null-terminated. */ size = offsetof_path + strlen(path) + 1; if (size < 0 || (word_t) size > max_size) { size = max_size; is_truncated = true; } strncpy(sockaddr.sun_path, path, sizeof_path); /* Overwrite the sockaddr and socklen parameters. */ status = write_data(tracee, sock_addr, &sockaddr, size); if (status < 0) return status; /* If sockaddr is truncated (because the buffer provided is * too small), addrlen will return a value greater than was * supplied to the call. See man 2 accept. */ if (is_truncated) size = max_size + 1; poke_int32(tracee, size_addr, size); if (errno != 0) return -errno; return 0; } care-2.2.1/src/syscall/heap.c0000644000175000017500000001623612430615044015333 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* PROT_*, MAP_*, */ #include /* assert(3), */ #include /* strerror(3), */ #include /* sysconf(3), */ #include /* MIN(), MAX(), */ #include "tracee/tracee.h" #include "tracee/reg.h" #include "tracee/mem.h" #include "syscall/sysnum.h" #include "cli/notice.h" #include "compat.h" #define DEBUG_BRK(...) /* fprintf(stderr, __VA_ARGS__) */ /* The size of the heap can be zero, unlike the size of a memory * mapping. As a consequence, the first page of the "heap" memory * mapping is discarded in order to emulate an empty heap. */ static word_t heap_offset = 0; #define PREALLOCATED_HEAP_SIZE (16 * 1024 * 1024) /** * Emulate the brk(2) syscall with mmap(2) and/or mremap(2); this is * required to ensure a minimal heap size [1]. This function always * returns 0. * * [1] With some versions of the Linux kernel and some versions of the * GNU ELF interpreter (ld.so), the heap can't grow at all because * the kernel has put a memory mapping right after the heap. This * issue can be reproduced when using a Ubuntu 12.04 x86_64 rootfs * on RHEL 5 x86_64. */ word_t translate_brk_enter(Tracee *tracee) { word_t new_brk_address; size_t old_heap_size; size_t new_heap_size; if (tracee->heap->disabled) return 0; if (heap_offset == 0) { heap_offset = sysconf(_SC_PAGE_SIZE); if ((int) heap_offset <= 0) heap_offset = 0x1000; } /* Non-fixed mmap pages might be placed right after the * emulated heap on some architectures. A solution is to * preallocate some space to ensure a minimal heap size. */ if (tracee->heap->prealloc_size == 0) tracee->heap->prealloc_size = MAX(PREALLOCATED_HEAP_SIZE, heap_offset); new_brk_address = peek_reg(tracee, CURRENT, SYSARG_1); DEBUG_BRK("brk(0x%lx)\n", new_brk_address); /* Allocate a new mapping for the emulated heap. */ if (tracee->heap->base == 0) { Sysnum sysnum; /* From PRoot's point-of-view this is the first time this * tracee calls brk(2), although an address was specified. * This is not supposed to happen the first time. It is * likely because this tracee is the very first child of PRoot * but the first execve(2) didn't happen yet (so this is not * its first call to brk(2)). For instance, the installation * of seccomp filters is made after this very first process is * traced, and might call malloc(3) before the first * execve(2). */ if (new_brk_address != 0) { if (tracee->verbose > 0) notice(tracee, WARNING, INTERNAL, "process %d is doing suspicious brk()", tracee->pid); return 0; } /* I don't understand yet why mmap(2) fails (EFAULT) * on architectures that also have mmap2(2). Maybe * this former implies MAP_FIXED in such cases. */ sysnum = detranslate_sysnum(get_abi(tracee), PR_mmap2) != SYSCALL_AVOIDER ? PR_mmap2 : PR_mmap; set_sysnum(tracee, sysnum); poke_reg(tracee, SYSARG_1 /* address */, 0); poke_reg(tracee, SYSARG_2 /* length */, heap_offset + tracee->heap->prealloc_size); poke_reg(tracee, SYSARG_3 /* prot */, PROT_READ | PROT_WRITE); poke_reg(tracee, SYSARG_4 /* flags */, MAP_PRIVATE | MAP_ANONYMOUS); poke_reg(tracee, SYSARG_5 /* fd */, -1); poke_reg(tracee, SYSARG_6 /* offset */, 0); return 0; } /* The size of the heap can't be negative. */ if (new_brk_address < tracee->heap->base) { set_sysnum(tracee, PR_void); return 0; } new_heap_size = new_brk_address - tracee->heap->base; old_heap_size = tracee->heap->size; /* Clear the released memory in preallocated space, so it will be * in the expected state next time it will be reallocated. */ if (new_heap_size < old_heap_size && new_heap_size < tracee->heap->prealloc_size) { (void) clear_mem(tracee, tracee->heap->base + new_heap_size, MIN(old_heap_size, tracee->heap->prealloc_size) - new_heap_size); } /* No need to use mremap when both old size and new size are * in the preallocated space. */ if ( new_heap_size <= tracee->heap->prealloc_size && old_heap_size <= tracee->heap->prealloc_size) { tracee->heap->size = new_heap_size; set_sysnum(tracee, PR_void); return 0; } /* Ensure the preallocated space will never be released. */ new_heap_size = MAX(new_heap_size, tracee->heap->prealloc_size); old_heap_size = MAX(old_heap_size, tracee->heap->prealloc_size); /* Actually resizing. */ set_sysnum(tracee, PR_mremap); poke_reg(tracee, SYSARG_1 /* old_address */, tracee->heap->base - heap_offset); poke_reg(tracee, SYSARG_2 /* old_size */, old_heap_size + heap_offset); poke_reg(tracee, SYSARG_3 /* new_size */, new_heap_size + heap_offset); poke_reg(tracee, SYSARG_4 /* flags */, 0); poke_reg(tracee, SYSARG_5 /* new_address */, 0); return 0; } /** * c.f. function above. */ void translate_brk_exit(Tracee *tracee) { word_t result; word_t sysnum; int tracee_errno; if (tracee->heap->disabled) return; assert(heap_offset > 0); sysnum = get_sysnum(tracee, MODIFIED); result = peek_reg(tracee, CURRENT, SYSARG_RESULT); tracee_errno = (int) result; switch (sysnum) { case PR_void: poke_reg(tracee, SYSARG_RESULT, tracee->heap->base + tracee->heap->size); break; case PR_mmap: case PR_mmap2: /* On error, mmap(2) returns -errno (the last 4k is * reserved for this), whereas brk(2) returns the * previous value. */ if (tracee_errno < 0 && tracee_errno > -4096) { poke_reg(tracee, SYSARG_RESULT, 0); break; } tracee->heap->base = result + heap_offset; tracee->heap->size = 0; poke_reg(tracee, SYSARG_RESULT, tracee->heap->base + tracee->heap->size); break; case PR_mremap: /* On error, mremap(2) returns -errno (the last 4k is * reserved this), whereas brk(2) returns the previous * value. */ if ( (tracee_errno < 0 && tracee_errno > -4096) || (tracee->heap->base != result + heap_offset)) { poke_reg(tracee, SYSARG_RESULT, tracee->heap->base + tracee->heap->size); break; } tracee->heap->size = peek_reg(tracee, MODIFIED, SYSARG_3) - heap_offset; poke_reg(tracee, SYSARG_RESULT, tracee->heap->base + tracee->heap->size); break; case PR_brk: /* Is it confirmed that this suspicious call to brk(2) * is actually legit? */ if (result == peek_reg(tracee, ORIGINAL, SYSARG_1)) tracee->heap->disabled = true; break; default: assert(0); } DEBUG_BRK("brk() = 0x%lx\n", peek_reg(tracee, CURRENT, SYSARG_RESULT)); } care-2.2.1/src/syscall/syscall.h0000644000175000017500000000244412430615044016071 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef SYSCALL_H #define SYSCALL_H #include /* PATH_MAX, */ #include "tracee/tracee.h" #include "tracee/reg.h" extern int get_sysarg_path(const Tracee *tracee, char path[PATH_MAX], Reg reg); extern int set_sysarg_path(Tracee *tracee, char path[PATH_MAX], Reg reg); extern void translate_syscall(Tracee *tracee); extern int translate_syscall_enter(Tracee *tracee); extern void translate_syscall_exit(Tracee *tracee); #endif /* SYSCALL_H */ care-2.2.1/src/syscall/sysnums-sh4.h0000644000175000017500000002016212430615044016631 0ustar ivoireivoire#include "syscall/sysnum.h" static const Sysnum sysnums_sh4[] = { [ 0 ] = PR_restart_syscall, [ 1 ] = PR_exit, [ 2 ] = PR_fork, [ 3 ] = PR_read, [ 4 ] = PR_write, [ 5 ] = PR_open, [ 6 ] = PR_close, [ 7 ] = PR_waitpid, [ 8 ] = PR_creat, [ 9 ] = PR_link, [ 10 ] = PR_unlink, [ 11 ] = PR_execve, [ 12 ] = PR_chdir, [ 13 ] = PR_time, [ 14 ] = PR_mknod, [ 15 ] = PR_chmod, [ 16 ] = PR_lchown, [ 18 ] = PR_oldstat, [ 19 ] = PR_lseek, [ 20 ] = PR_getpid, [ 21 ] = PR_mount, [ 22 ] = PR_umount, [ 23 ] = PR_setuid, [ 24 ] = PR_getuid, [ 25 ] = PR_stime, [ 26 ] = PR_ptrace, [ 27 ] = PR_alarm, [ 28 ] = PR_oldfstat, [ 29 ] = PR_pause, [ 30 ] = PR_utime, [ 33 ] = PR_access, [ 34 ] = PR_nice, [ 36 ] = PR_sync, [ 37 ] = PR_kill, [ 38 ] = PR_rename, [ 39 ] = PR_mkdir, [ 40 ] = PR_rmdir, [ 41 ] = PR_dup, [ 42 ] = PR_pipe, [ 43 ] = PR_times, [ 45 ] = PR_brk, [ 46 ] = PR_setgid, [ 47 ] = PR_getgid, [ 48 ] = PR_signal, [ 49 ] = PR_geteuid, [ 50 ] = PR_getegid, [ 51 ] = PR_acct, [ 52 ] = PR_umount2, [ 54 ] = PR_ioctl, [ 55 ] = PR_fcntl, [ 57 ] = PR_setpgid, [ 60 ] = PR_umask, [ 61 ] = PR_chroot, [ 62 ] = PR_ustat, [ 63 ] = PR_dup2, [ 64 ] = PR_getppid, [ 65 ] = PR_getpgrp, [ 66 ] = PR_setsid, [ 67 ] = PR_sigaction, [ 68 ] = PR_sgetmask, [ 69 ] = PR_ssetmask, [ 70 ] = PR_setreuid, [ 71 ] = PR_setregid, [ 72 ] = PR_sigsuspend, [ 73 ] = PR_sigpending, [ 74 ] = PR_sethostname, [ 75 ] = PR_setrlimit, [ 76 ] = PR_getrlimit, [ 77 ] = PR_getrusage, [ 78 ] = PR_gettimeofday, [ 79 ] = PR_settimeofday, [ 80 ] = PR_getgroups, [ 81 ] = PR_setgroups, [ 83 ] = PR_symlink, [ 84 ] = PR_oldlstat, [ 85 ] = PR_readlink, [ 86 ] = PR_uselib, [ 87 ] = PR_swapon, [ 88 ] = PR_reboot, [ 89 ] = PR_readdir, [ 90 ] = PR_mmap, [ 91 ] = PR_munmap, [ 92 ] = PR_truncate, [ 93 ] = PR_ftruncate, [ 94 ] = PR_fchmod, [ 95 ] = PR_fchown, [ 96 ] = PR_getpriority, [ 97 ] = PR_setpriority, [ 99 ] = PR_statfs, [ 100 ] = PR_fstatfs, [ 102 ] = PR_socketcall, [ 103 ] = PR_syslog, [ 104 ] = PR_setitimer, [ 105 ] = PR_getitimer, [ 106 ] = PR_stat, [ 107 ] = PR_lstat, [ 108 ] = PR_fstat, [ 109 ] = PR_olduname, [ 111 ] = PR_vhangup, [ 114 ] = PR_wait4, [ 115 ] = PR_swapoff, [ 116 ] = PR_sysinfo, [ 117 ] = PR_ipc, [ 118 ] = PR_fsync, [ 119 ] = PR_sigreturn, [ 120 ] = PR_clone, [ 121 ] = PR_setdomainname, [ 122 ] = PR_uname, [ 123 ] = PR_cacheflush, [ 124 ] = PR_adjtimex, [ 125 ] = PR_mprotect, [ 126 ] = PR_sigprocmask, [ 128 ] = PR_init_module, [ 129 ] = PR_delete_module, [ 131 ] = PR_quotactl, [ 132 ] = PR_getpgid, [ 133 ] = PR_fchdir, [ 134 ] = PR_bdflush, [ 135 ] = PR_sysfs, [ 136 ] = PR_personality, [ 138 ] = PR_setfsuid, [ 139 ] = PR_setfsgid, [ 140 ] = PR__llseek, [ 141 ] = PR_getdents, [ 142 ] = PR__newselect, [ 143 ] = PR_flock, [ 144 ] = PR_msync, [ 145 ] = PR_readv, [ 146 ] = PR_writev, [ 147 ] = PR_getsid, [ 148 ] = PR_fdatasync, [ 149 ] = PR__sysctl, [ 150 ] = PR_mlock, [ 151 ] = PR_munlock, [ 152 ] = PR_mlockall, [ 153 ] = PR_munlockall, [ 154 ] = PR_sched_setparam, [ 155 ] = PR_sched_getparam, [ 156 ] = PR_sched_setscheduler, [ 157 ] = PR_sched_getscheduler, [ 158 ] = PR_sched_yield, [ 159 ] = PR_sched_get_priority_max, [ 160 ] = PR_sched_get_priority_min, [ 161 ] = PR_sched_rr_get_interval, [ 162 ] = PR_nanosleep, [ 163 ] = PR_mremap, [ 164 ] = PR_setresuid, [ 165 ] = PR_getresuid, [ 168 ] = PR_poll, [ 169 ] = PR_nfsservctl, [ 170 ] = PR_setresgid, [ 171 ] = PR_getresgid, [ 172 ] = PR_prctl, [ 173 ] = PR_rt_sigreturn, [ 174 ] = PR_rt_sigaction, [ 175 ] = PR_rt_sigprocmask, [ 176 ] = PR_rt_sigpending, [ 177 ] = PR_rt_sigtimedwait, [ 178 ] = PR_rt_sigqueueinfo, [ 179 ] = PR_rt_sigsuspend, [ 180 ] = PR_pread64, [ 181 ] = PR_pwrite64, [ 182 ] = PR_chown, [ 183 ] = PR_getcwd, [ 184 ] = PR_capget, [ 185 ] = PR_capset, [ 186 ] = PR_sigaltstack, [ 187 ] = PR_sendfile, [ 190 ] = PR_vfork, [ 191 ] = PR_ugetrlimit, [ 192 ] = PR_mmap2, [ 193 ] = PR_truncate64, [ 194 ] = PR_ftruncate64, [ 195 ] = PR_stat64, [ 196 ] = PR_lstat64, [ 197 ] = PR_fstat64, [ 198 ] = PR_lchown32, [ 199 ] = PR_getuid32, [ 200 ] = PR_getgid32, [ 201 ] = PR_geteuid32, [ 202 ] = PR_getegid32, [ 203 ] = PR_setreuid32, [ 204 ] = PR_setregid32, [ 205 ] = PR_getgroups32, [ 206 ] = PR_setgroups32, [ 207 ] = PR_fchown32, [ 208 ] = PR_setresuid32, [ 209 ] = PR_getresuid32, [ 210 ] = PR_setresgid32, [ 211 ] = PR_getresgid32, [ 212 ] = PR_chown32, [ 213 ] = PR_setuid32, [ 214 ] = PR_setgid32, [ 215 ] = PR_setfsuid32, [ 216 ] = PR_setfsgid32, [ 217 ] = PR_pivot_root, [ 218 ] = PR_mincore, [ 219 ] = PR_madvise, [ 220 ] = PR_getdents64, [ 221 ] = PR_fcntl64, [ 224 ] = PR_gettid, [ 225 ] = PR_readahead, [ 226 ] = PR_setxattr, [ 227 ] = PR_lsetxattr, [ 228 ] = PR_fsetxattr, [ 229 ] = PR_getxattr, [ 230 ] = PR_lgetxattr, [ 231 ] = PR_fgetxattr, [ 232 ] = PR_listxattr, [ 233 ] = PR_llistxattr, [ 234 ] = PR_flistxattr, [ 235 ] = PR_removexattr, [ 236 ] = PR_lremovexattr, [ 237 ] = PR_fremovexattr, [ 238 ] = PR_tkill, [ 239 ] = PR_sendfile64, [ 240 ] = PR_futex, [ 241 ] = PR_sched_setaffinity, [ 242 ] = PR_sched_getaffinity, [ 245 ] = PR_io_setup, [ 246 ] = PR_io_destroy, [ 247 ] = PR_io_getevents, [ 248 ] = PR_io_submit, [ 249 ] = PR_io_cancel, [ 250 ] = PR_fadvise64, [ 252 ] = PR_exit_group, [ 253 ] = PR_lookup_dcookie, [ 254 ] = PR_epoll_create, [ 255 ] = PR_epoll_ctl, [ 256 ] = PR_epoll_wait, [ 257 ] = PR_remap_file_pages, [ 258 ] = PR_set_tid_address, [ 259 ] = PR_timer_create, [ 260 ] = PR_timer_settime, [ 261 ] = PR_timer_gettime, [ 262 ] = PR_timer_getoverrun, [ 263 ] = PR_timer_delete, [ 264 ] = PR_clock_settime, [ 265 ] = PR_clock_gettime, [ 266 ] = PR_clock_getres, [ 267 ] = PR_clock_nanosleep, [ 268 ] = PR_statfs64, [ 269 ] = PR_fstatfs64, [ 270 ] = PR_tgkill, [ 271 ] = PR_utimes, [ 272 ] = PR_fadvise64_64, [ 274 ] = PR_mbind, [ 275 ] = PR_get_mempolicy, [ 276 ] = PR_set_mempolicy, [ 277 ] = PR_mq_open, [ 278 ] = PR_mq_unlink, [ 279 ] = PR_mq_timedsend, [ 280 ] = PR_mq_timedreceive, [ 281 ] = PR_mq_notify, [ 282 ] = PR_mq_getsetattr, [ 283 ] = PR_kexec_load, [ 284 ] = PR_waitid, [ 285 ] = PR_add_key, [ 286 ] = PR_request_key, [ 287 ] = PR_keyctl, [ 288 ] = PR_ioprio_set, [ 289 ] = PR_ioprio_get, [ 290 ] = PR_inotify_init, [ 291 ] = PR_inotify_add_watch, [ 292 ] = PR_inotify_rm_watch, [ 294 ] = PR_migrate_pages, [ 295 ] = PR_openat, [ 296 ] = PR_mkdirat, [ 297 ] = PR_mknodat, [ 298 ] = PR_fchownat, [ 299 ] = PR_futimesat, [ 300 ] = PR_fstatat64, [ 301 ] = PR_unlinkat, [ 302 ] = PR_renameat, [ 303 ] = PR_linkat, [ 304 ] = PR_symlinkat, [ 305 ] = PR_readlinkat, [ 306 ] = PR_fchmodat, [ 307 ] = PR_faccessat, [ 308 ] = PR_pselect6, [ 309 ] = PR_ppoll, [ 310 ] = PR_unshare, [ 311 ] = PR_set_robust_list, [ 312 ] = PR_get_robust_list, [ 313 ] = PR_splice, [ 314 ] = PR_sync_file_range, [ 315 ] = PR_tee, [ 316 ] = PR_vmsplice, [ 317 ] = PR_move_pages, [ 318 ] = PR_getcpu, [ 319 ] = PR_epoll_pwait, [ 320 ] = PR_utimensat, [ 321 ] = PR_signalfd, [ 322 ] = PR_timerfd_create, [ 323 ] = PR_eventfd, [ 324 ] = PR_fallocate, [ 325 ] = PR_timerfd_settime, [ 326 ] = PR_timerfd_gettime, [ 327 ] = PR_signalfd4, [ 328 ] = PR_eventfd2, [ 329 ] = PR_epoll_create1, [ 330 ] = PR_dup3, [ 331 ] = PR_pipe2, [ 332 ] = PR_inotify_init1, [ 333 ] = PR_preadv, [ 334 ] = PR_pwritev, [ 335 ] = PR_rt_tgsigqueueinfo, [ 336 ] = PR_perf_event_open, [ 337 ] = PR_fanotify_init, [ 338 ] = PR_fanotify_mark, [ 339 ] = PR_prlimit64, [ 340 ] = PR_socket, [ 341 ] = PR_bind, [ 342 ] = PR_connect, [ 343 ] = PR_listen, [ 344 ] = PR_accept, [ 345 ] = PR_getsockname, [ 346 ] = PR_getpeername, [ 347 ] = PR_socketpair, [ 348 ] = PR_send, [ 349 ] = PR_sendto, [ 350 ] = PR_recv, [ 351 ] = PR_recvfrom, [ 352 ] = PR_shutdown, [ 353 ] = PR_setsockopt, [ 354 ] = PR_getsockopt, [ 355 ] = PR_sendmsg, [ 356 ] = PR_recvmsg, [ 357 ] = PR_recvmmsg, [ 358 ] = PR_accept4, [ 359 ] = PR_name_to_handle_at, [ 360 ] = PR_open_by_handle_at, [ 361 ] = PR_clock_adjtime, [ 362 ] = PR_syncfs, [ 363 ] = PR_sendmmsg, [ 364 ] = PR_setns, [ 365 ] = PR_process_vm_readv, [ 366 ] = PR_process_vm_writev, }; care-2.2.1/src/syscall/seccomp.c0000644000175000017500000003326112430615044016044 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include "build.h" #include "arch.h" #if defined(HAVE_SECCOMP_FILTER) #include /* prctl(2), PR_* */ #include /* struct sock_*, */ #include /* SECCOMP_MODE_FILTER, */ #include /* struct sock_*, */ #include /* AUDIT_, */ #include /* LIST_FOREACH, */ #include /* size_t, */ #include /* talloc_*, */ #include /* E*, */ #include /* memcpy(3), */ #include /* offsetof(3), */ #include /* uint*_t, UINT*_MAX, */ #include /* assert(3), */ #include "syscall/seccomp.h" #include "tracee/tracee.h" #include "syscall/syscall.h" #include "syscall/sysnum.h" #include "extension/extension.h" #include "cli/notice.h" #include "compat.h" #include "attribute.h" #define DEBUG_FILTER(...) /* fprintf(stderr, __VA_ARGS__) */ /** * Allocate an empty @program->filter. This function returns -errno * if an error occurred, otherwise 0. */ static int new_program_filter(struct sock_fprog *program) { program->filter = talloc_array(NULL, struct sock_filter, 0); if (program->filter == NULL) return -ENOMEM; program->len = 0; return 0; } /** * Append to @program->filter the given @statements (@nb_statements * items). This function returns -errno if an error occurred, * otherwise 0. */ static int add_statements(struct sock_fprog *program, size_t nb_statements, struct sock_filter *statements) { size_t length; void *tmp; size_t i; length = talloc_array_length(program->filter); tmp = talloc_realloc(NULL, program->filter, struct sock_filter, length + nb_statements); if (tmp == NULL) return -ENOMEM; program->filter = tmp; for (i = 0; i < nb_statements; i++, length++) memcpy(&program->filter[length], &statements[i], sizeof(struct sock_filter)); return 0; } /** * Append to @program->filter the statements required to notify PRoot * about the given @syscall made by a tracee, with the given @flag. * This function returns -errno if an error occurred, otherwise 0. */ static int add_trace_syscall(struct sock_fprog *program, word_t syscall, int flag) { int status; /* Sanity check. */ if (syscall > UINT32_MAX) return -ERANGE; #define LENGTH_TRACE_SYSCALL 2 struct sock_filter statements[LENGTH_TRACE_SYSCALL] = { /* Compare the accumulator with the expected syscall: * skip the next statement if not equal. */ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, syscall, 0, 1), /* Notify the tracer. */ BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_TRACE + flag) }; DEBUG_FILTER("FILTER: trace if syscall == %ld\n", syscall); status = add_statements(program, LENGTH_TRACE_SYSCALL, statements); if (status < 0) return status; return 0; } /** * Append to @program->filter the statements that allow anything (if * unfiltered). Note that @nb_traced_syscalls is used to make a * sanity check. This function returns -errno if an error occurred, * otherwise 0. */ static int end_arch_section(struct sock_fprog *program, size_t nb_traced_syscalls) { int status; #define LENGTH_END_SECTION 1 struct sock_filter statements[LENGTH_END_SECTION] = { BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW) }; DEBUG_FILTER("FILTER: allow\n"); status = add_statements(program, LENGTH_END_SECTION, statements); if (status < 0) return status; /* Sanity check, see start_arch_section(). */ if ( talloc_array_length(program->filter) - program->len != LENGTH_END_SECTION + nb_traced_syscalls * LENGTH_TRACE_SYSCALL) return -ERANGE; return 0; } /** * Append to @program->filter the statements that check the current * @architecture. Note that @nb_traced_syscalls is used to make a * sanity check. This function returns -errno if an error occurred, * otherwise 0. */ static int start_arch_section(struct sock_fprog *program, uint32_t arch, size_t nb_traced_syscalls) { const size_t arch_offset = offsetof(struct seccomp_data, arch); const size_t syscall_offset = offsetof(struct seccomp_data, nr); const size_t section_length = LENGTH_END_SECTION + nb_traced_syscalls * LENGTH_TRACE_SYSCALL; int status; /* Sanity checks. */ if ( arch_offset > UINT32_MAX || syscall_offset > UINT32_MAX || section_length > UINT32_MAX - 1) return -ERANGE; #define LENGTH_START_SECTION 4 struct sock_filter statements[LENGTH_START_SECTION] = { /* Load the current architecture into the * accumulator. */ BPF_STMT(BPF_LD + BPF_W + BPF_ABS, arch_offset), /* Compare the accumulator with the expected * architecture: skip the following statement if * equal. */ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, arch, 1, 0), /* This is not the expected architecture, so jump * unconditionally to the end of this section. */ BPF_STMT(BPF_JMP + BPF_JA + BPF_K, section_length + 1), /* This is the expected architecture, so load the * current syscall into the accumulator. */ BPF_STMT(BPF_LD + BPF_W + BPF_ABS, syscall_offset) }; DEBUG_FILTER("FILTER: if arch == %ld, up to %zdth statement\n", arch, nb_traced_syscalls); status = add_statements(program, LENGTH_START_SECTION, statements); if (status < 0) return status; /* See the sanity check in end_arch_section(). */ program->len = talloc_array_length(program->filter); return 0; } /** * Append to @program->filter the statements that forbid anything (if * unfiltered) and update @program->len. This function returns -errno * if an error occurred, otherwise 0. */ static int finalize_program_filter(struct sock_fprog *program) { int status; #define LENGTH_FINALIZE 1 struct sock_filter statements[LENGTH_FINALIZE] = { BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_KILL) }; DEBUG_FILTER("FILTER: kill\n"); status = add_statements(program, LENGTH_FINALIZE, statements); if (status < 0) return status; program->len = talloc_array_length(program->filter); return 0; } /** * Free @program->filter and set @program->len to 0. */ static void free_program_filter(struct sock_fprog *program) { TALLOC_FREE(program->filter); program->len = 0; } /** * Convert the given @sysnums into BPF filters according to the * following pseudo-code, then enabled them for the given @tracee and * all of its future children: * * for each handled architectures * for each filtered syscall * trace * allow * kill * * This function returns -errno if an error occurred, otherwise 0. */ static int set_seccomp_filters(const FilteredSysnum *sysnums) { SeccompArch seccomp_archs[] = SECCOMP_ARCHS; size_t nb_archs = sizeof(seccomp_archs) / sizeof(SeccompArch); struct sock_fprog program = { .len = 0, .filter = NULL }; size_t nb_traced_syscalls; size_t i, j, k; int status; status = new_program_filter(&program); if (status < 0) goto end; /* For each handled architectures */ for (i = 0; i < nb_archs; i++) { word_t syscall; nb_traced_syscalls = 0; /* Pre-compute the number of traced syscalls for this architecture. */ for (j = 0; j < seccomp_archs[i].nb_abis; j++) { for (k = 0; sysnums[k].value != PR_void; k++) { syscall = detranslate_sysnum(seccomp_archs[i].abis[j], sysnums[k].value); if (syscall != SYSCALL_AVOIDER) nb_traced_syscalls++; } } /* Filter: if handled architecture */ status = start_arch_section(&program, seccomp_archs[i].value, nb_traced_syscalls); if (status < 0) goto end; for (j = 0; j < seccomp_archs[i].nb_abis; j++) { for (k = 0; sysnums[k].value != PR_void; k++) { /* Get the architecture specific syscall number. */ syscall = detranslate_sysnum(seccomp_archs[i].abis[j], sysnums[k].value); if (syscall == SYSCALL_AVOIDER) continue; /* Filter: trace if handled syscall */ status = add_trace_syscall(&program, syscall, sysnums[k].flags); if (status < 0) goto end; } } /* Filter: allow untraced syscalls for this architecture */ status = end_arch_section(&program, nb_traced_syscalls); if (status < 0) goto end; } status = finalize_program_filter(&program); if (status < 0) goto end; status = prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); if (status < 0) goto end; /* To output this BPF program for debug purpose: * * write(2, program.filter, program.len * sizeof(struct sock_filter)); */ status = prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &program); if (status < 0) goto end; status = 0; end: free_program_filter(&program); return status; } /* List of sysnums handled by PRoot. */ static FilteredSysnum proot_sysnums[] = { { PR_accept, FILTER_SYSEXIT }, { PR_accept4, FILTER_SYSEXIT }, { PR_access, 0 }, { PR_acct, 0 }, { PR_bind, 0 }, { PR_brk, FILTER_SYSEXIT }, { PR_chdir, FILTER_SYSEXIT }, { PR_chmod, 0 }, { PR_chown, 0 }, { PR_chown32, 0 }, { PR_chroot, 0 }, { PR_connect, 0 }, { PR_creat, 0 }, { PR_execve, FILTER_SYSEXIT }, { PR_faccessat, 0 }, { PR_fchdir, FILTER_SYSEXIT }, { PR_fchmodat, 0 }, { PR_fchownat, 0 }, { PR_fstatat64, 0 }, { PR_futimesat, 0 }, { PR_getcwd, FILTER_SYSEXIT }, { PR_getpeername, FILTER_SYSEXIT }, { PR_getsockname, FILTER_SYSEXIT }, { PR_getxattr, 0 }, { PR_inotify_add_watch, 0 }, { PR_lchown, 0 }, { PR_lchown32, 0 }, { PR_lgetxattr, 0 }, { PR_link, 0 }, { PR_linkat, 0 }, { PR_listxattr, 0 }, { PR_llistxattr, 0 }, { PR_lremovexattr, 0 }, { PR_lsetxattr, 0 }, { PR_lstat, 0 }, { PR_lstat64, 0 }, { PR_mkdir, 0 }, { PR_mkdirat, 0 }, { PR_mknod, 0 }, { PR_mknodat, 0 }, { PR_mount, 0 }, { PR_name_to_handle_at, 0 }, { PR_newfstatat, 0 }, { PR_oldlstat, 0 }, { PR_oldstat, 0 }, { PR_open, 0 }, { PR_openat, 0 }, { PR_pivot_root, 0 }, { PR_ptrace, FILTER_SYSEXIT }, { PR_readlink, FILTER_SYSEXIT }, { PR_readlinkat, FILTER_SYSEXIT }, { PR_removexattr, 0 }, { PR_rename, FILTER_SYSEXIT }, { PR_renameat, FILTER_SYSEXIT }, { PR_rmdir, 0 }, { PR_rt_sigreturn, FILTER_SYSEXIT }, { PR_setxattr, 0 }, { PR_sigreturn, FILTER_SYSEXIT }, { PR_socketcall, FILTER_SYSEXIT }, { PR_stat, 0 }, { PR_stat64, 0 }, { PR_statfs, 0 }, { PR_statfs64, 0 }, { PR_swapoff, 0 }, { PR_swapon, 0 }, { PR_symlink, 0 }, { PR_symlinkat, 0 }, { PR_truncate, 0 }, { PR_truncate64, 0 }, { PR_umount, 0 }, { PR_umount2, 0 }, { PR_uname, FILTER_SYSEXIT }, { PR_unlink, 0 }, { PR_unlinkat, 0 }, { PR_uselib, 0 }, { PR_utime, 0 }, { PR_utimensat, 0 }, { PR_utimes, 0 }, { PR_wait4, FILTER_SYSEXIT }, { PR_waitpid, FILTER_SYSEXIT }, FILTERED_SYSNUM_END, }; /** * Add the @new_sysnums to the list of filtered @sysnums, using the * given Talloc @context. This function returns -errno if an error * occurred, otherwise 0. */ static int merge_filtered_sysnums(TALLOC_CTX *context, FilteredSysnum **sysnums, const FilteredSysnum *new_sysnums) { size_t i, j; assert(sysnums != NULL); if (*sysnums == NULL) { /* Start with no sysnums but the terminator. */ *sysnums = talloc_array(context, FilteredSysnum, 1); if (*sysnums == NULL) return -ENOMEM; (*sysnums)[0].value = PR_void; } for (i = 0; new_sysnums[i].value != PR_void; i++) { /* Search for the given sysnum. */ for (j = 0; (*sysnums)[j].value != PR_void && (*sysnums)[j].value != new_sysnums[i].value; j++) ; if ((*sysnums)[j].value == PR_void) { /* No such sysnum, allocate a new entry. */ (*sysnums) = talloc_realloc(context, (*sysnums), FilteredSysnum, j + 2); if ((*sysnums) == NULL) return -ENOMEM; (*sysnums)[j] = new_sysnums[i]; /* The last item is the terminator. */ (*sysnums)[j + 1].value = PR_void; } else /* The sysnum is already filtered, merge the * flags. */ (*sysnums)[j].flags |= new_sysnums[i].flags; } return 0; } /** * Tell the kernel to trace only syscalls handled by PRoot and its * extensions. This filter will be enabled for the given @tracee and * all of its future children. This function returns -errno if an * error occurred, otherwise 0. */ int enable_syscall_filtering(const Tracee *tracee) { FilteredSysnum *filtered_sysnums = NULL; Extension *extension; int status; assert(tracee != NULL && tracee->ctx != NULL); /* Add the sysnums required by PRoot to the list of filtered * sysnums. TODO: only if path translation is required. */ status = merge_filtered_sysnums(tracee->ctx, &filtered_sysnums, proot_sysnums); if (status < 0) return status; /* Merge the sysnums required by the extensions to the list * of filtered sysnums. */ if (tracee->extensions != NULL) { LIST_FOREACH(extension, tracee->extensions, link) { if (extension->filtered_sysnums == NULL) continue; status = merge_filtered_sysnums(tracee->ctx, &filtered_sysnums, extension->filtered_sysnums); if (status < 0) return status; } } status = set_seccomp_filters(filtered_sysnums); if (status < 0) return status; return 0; } #else #include "tracee/tracee.h" #include "attribute.h" int enable_syscall_filtering(const Tracee *tracee UNUSED) { return 0; } #endif /* defined(HAVE_SECCOMP_FILTER) */ care-2.2.1/src/syscall/sysnum.h0000644000175000017500000000254312430615044015755 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef SYSNUM_H #define SYSNUM_H #include #include "tracee/tracee.h" #include "tracee/abi.h" #include "tracee/reg.h" #define SYSNUM(item) PR_ ## item, typedef enum { PR_void = 0, #include "syscall/sysnums.list" PR_NB_SYSNUM } Sysnum; #undef SYSNUM extern Sysnum get_sysnum(const Tracee *tracee, RegVersion version); extern void set_sysnum(Tracee *tracee, Sysnum sysnum); extern word_t detranslate_sysnum(Abi abi, Sysnum sysnum); extern const char *stringify_sysnum(Sysnum sysnum); #endif /* SYSNUM_H */ care-2.2.1/src/syscall/enter.c0000644000175000017500000003322612430615044015531 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* errno(3), E* */ #include /* talloc_*, */ #include /* struct sockaddr_un, */ #include /* SYS_*, */ #include /* AT_FDCWD, */ #include /* PATH_MAX, */ #include "syscall/syscall.h" #include "syscall/sysnum.h" #include "syscall/socket.h" #include "ptrace/ptrace.h" #include "ptrace/wait.h" #include "syscall/heap.h" #include "extension/extension.h" #include "execve/execve.h" #include "tracee/tracee.h" #include "tracee/reg.h" #include "tracee/mem.h" #include "tracee/abi.h" #include "path/path.h" #include "path/canon.h" #include "arch.h" /** * Translate @path and put the result in the @tracee's memory address * space pointed to by the @reg argument of the current syscall. See * the documentation of translate_path() about the meaning of * @type. This function returns -errno if an error occured, otherwise * 0. */ static int translate_path2(Tracee *tracee, int dir_fd, char path[PATH_MAX], Reg reg, Type type) { char new_path[PATH_MAX]; int status; /* Special case where the argument was NULL. */ if (path[0] == '\0') return 0; /* Translate the original path. */ status = translate_path(tracee, new_path, dir_fd, path, type != SYMLINK); if (status < 0) return status; return set_sysarg_path(tracee, new_path, reg); } /** * A helper, see the comment of the function above. */ static int translate_sysarg(Tracee *tracee, Reg reg, Type type) { char old_path[PATH_MAX]; int status; /* Extract the original path. */ status = get_sysarg_path(tracee, old_path, reg); if (status < 0) return status; return translate_path2(tracee, AT_FDCWD, old_path, reg, type); } /** * Translate the input arguments of the current @tracee's syscall in the * @tracee->pid process area. This function sets @tracee->status to * -errno if an error occured from the tracee's point-of-view (EFAULT * for instance), otherwise 0. */ int translate_syscall_enter(Tracee *tracee) { int flags; int dirfd; int olddirfd; int newdirfd; int status; int status2; char path[PATH_MAX]; char oldpath[PATH_MAX]; char newpath[PATH_MAX]; word_t syscall_number; bool special = false; status = notify_extensions(tracee, SYSCALL_ENTER_START, 0, 0); if (status < 0) goto end; if (status > 0) return 0; /* Translate input arguments. */ syscall_number = get_sysnum(tracee, ORIGINAL); switch (syscall_number) { default: /* Nothing to do. */ status = 0; break; case PR_execve: status = translate_execve(tracee); /* Something went wrong, restore ptracee's loading * state. */ if (status < 0) tracee->as_ptracee.is_load_pending = false; break; case PR_ptrace: status = translate_ptrace_enter(tracee); break; case PR_wait4: case PR_waitpid: status = translate_wait_enter(tracee); break; case PR_brk: status = translate_brk_enter(tracee); break; case PR_getcwd: set_sysnum(tracee, PR_void); status = 0; break; case PR_fchdir: case PR_chdir: { struct stat statl; char *tmp; /* The ending "." ensures an error will be reported if * path does not exist or if it is not a directory. */ if (syscall_number == PR_chdir) { status = get_sysarg_path(tracee, path, SYSARG_1); if (status < 0) break; status = join_paths(2, oldpath, path, "."); if (status < 0) break; dirfd = AT_FDCWD; } else { strcpy(oldpath, "."); dirfd = peek_reg(tracee, CURRENT, SYSARG_1); } status = translate_path(tracee, path, dirfd, oldpath, true); if (status < 0) break; status = lstat(path, &statl); if (status < 0) break; /* Check this directory is accessible. */ if ((statl.st_mode & S_IXUSR) == 0) return -EACCES; /* Sadly this method doesn't detranslate statefully, * this means that there's an ambiguity when several * bindings are from the same host path: * * $ proot -m /tmp:/a -m /tmp:/b fchdir_getcwd /a * /b * * $ proot -m /tmp:/b -m /tmp:/a fchdir_getcwd /a * /a * * A solution would be to follow each file descriptor * just like it is done for cwd. */ status = detranslate_path(tracee, path, NULL); if (status < 0) break; /* Remove the trailing "/" or "/.". */ chop_finality(path); tmp = talloc_strdup(tracee->fs, path); if (tmp == NULL) { status = -ENOMEM; break; } TALLOC_FREE(tracee->fs->cwd); tracee->fs->cwd = tmp; talloc_set_name_const(tracee->fs->cwd, "$cwd"); set_sysnum(tracee, PR_void); status = 0; break; } case PR_bind: case PR_connect: { word_t address; word_t size; address = peek_reg(tracee, CURRENT, SYSARG_2); size = peek_reg(tracee, CURRENT, SYSARG_3); status = translate_socketcall_enter(tracee, &address, size); if (status <= 0) break; poke_reg(tracee, SYSARG_2, address); poke_reg(tracee, SYSARG_3, sizeof(struct sockaddr_un)); status = 0; break; } #define SYSARG_ADDR(n) (args_addr + ((n) - 1) * sizeof_word(tracee)) #define PEEK_WORD(addr, forced_errno) \ peek_word(tracee, addr); \ if (errno != 0) { \ status = forced_errno ?: -errno; \ break; \ } #define POKE_WORD(addr, value) \ poke_word(tracee, addr, value); \ if (errno != 0) { \ status = -errno; \ break; \ } case PR_accept: case PR_accept4: /* Nothing special to do if no sockaddr was specified. */ if (peek_reg(tracee, ORIGINAL, SYSARG_2) == 0) { status = 0; break; } special = true; /* Fall through. */ case PR_getsockname: case PR_getpeername:{ int size; /* Remember: PEEK_WORD puts -errno in status and breaks if an * error occured. */ size = (int) PEEK_WORD(peek_reg(tracee, ORIGINAL, SYSARG_3), special ? -EINVAL : 0); /* The "size" argument is both used as an input parameter * (max. size) and as an output parameter (actual size). The * exit stage needs to know the max. size to not overwrite * anything, that's why it is copied in the 6th argument * (unused) before the kernel updates it. */ poke_reg(tracee, SYSARG_6, size); status = 0; break; } case PR_socketcall: { word_t args_addr; word_t sock_addr_saved; word_t sock_addr; word_t size_addr; word_t size; args_addr = peek_reg(tracee, CURRENT, SYSARG_2); switch (peek_reg(tracee, CURRENT, SYSARG_1)) { case SYS_BIND: case SYS_CONNECT: /* Handle these cases below. */ status = 1; break; case SYS_ACCEPT: case SYS_ACCEPT4: /* Nothing special to do if no sockaddr was specified. */ sock_addr = PEEK_WORD(SYSARG_ADDR(2), 0); if (sock_addr == 0) { status = 0; break; } special = true; /* Fall through. */ case SYS_GETSOCKNAME: case SYS_GETPEERNAME: /* Remember: PEEK_WORD puts -errno in status and breaks * if an error occured. */ size_addr = PEEK_WORD(SYSARG_ADDR(3), 0); size = (int) PEEK_WORD(size_addr, special ? -EINVAL : 0); /* See case PR_accept for explanation. */ poke_reg(tracee, SYSARG_6, size); status = 0; break; default: status = 0; break; } /* An error occured or there's nothing else to do. */ if (status <= 0) break; /* Remember: PEEK_WORD puts -errno in status and breaks if an * error occured. */ sock_addr = PEEK_WORD(SYSARG_ADDR(2), 0); size = PEEK_WORD(SYSARG_ADDR(3), 0); sock_addr_saved = sock_addr; status = translate_socketcall_enter(tracee, &sock_addr, size); if (status <= 0) break; /* These parameters are used/restored at the exit stage. */ poke_reg(tracee, SYSARG_5, sock_addr_saved); poke_reg(tracee, SYSARG_6, size); /* Remember: POKE_WORD puts -errno in status and breaks if an * error occured. */ POKE_WORD(SYSARG_ADDR(2), sock_addr); POKE_WORD(SYSARG_ADDR(3), sizeof(struct sockaddr_un)); status = 0; break; } #undef SYSARG_ADDR #undef PEEK_WORD #undef POKE_WORD case PR_access: case PR_acct: case PR_chmod: case PR_chown: case PR_chown32: case PR_chroot: case PR_getxattr: case PR_listxattr: case PR_mknod: case PR_oldstat: case PR_creat: case PR_removexattr: case PR_setxattr: case PR_stat: case PR_stat64: case PR_statfs: case PR_statfs64: case PR_swapoff: case PR_swapon: case PR_truncate: case PR_truncate64: case PR_umount: case PR_umount2: case PR_uselib: case PR_utime: case PR_utimes: status = translate_sysarg(tracee, SYSARG_1, REGULAR); break; case PR_open: flags = peek_reg(tracee, CURRENT, SYSARG_2); if ( ((flags & O_NOFOLLOW) != 0) || ((flags & O_EXCL) != 0 && (flags & O_CREAT) != 0)) status = translate_sysarg(tracee, SYSARG_1, SYMLINK); else status = translate_sysarg(tracee, SYSARG_1, REGULAR); break; case PR_fchownat: case PR_fstatat64: case PR_newfstatat: case PR_utimensat: case PR_name_to_handle_at: dirfd = peek_reg(tracee, CURRENT, SYSARG_1); status = get_sysarg_path(tracee, path, SYSARG_2); if (status < 0) break; flags = ( syscall_number == PR_fchownat || syscall_number == PR_name_to_handle_at) ? peek_reg(tracee, CURRENT, SYSARG_5) : peek_reg(tracee, CURRENT, SYSARG_4); if ((flags & AT_SYMLINK_NOFOLLOW) != 0) status = translate_path2(tracee, dirfd, path, SYSARG_2, SYMLINK); else status = translate_path2(tracee, dirfd, path, SYSARG_2, REGULAR); break; case PR_fchmodat: case PR_faccessat: case PR_futimesat: case PR_mknodat: dirfd = peek_reg(tracee, CURRENT, SYSARG_1); status = get_sysarg_path(tracee, path, SYSARG_2); if (status < 0) break; status = translate_path2(tracee, dirfd, path, SYSARG_2, REGULAR); break; case PR_inotify_add_watch: flags = peek_reg(tracee, CURRENT, SYSARG_3); if ((flags & IN_DONT_FOLLOW) != 0) status = translate_sysarg(tracee, SYSARG_2, SYMLINK); else status = translate_sysarg(tracee, SYSARG_2, REGULAR); break; case PR_readlink: case PR_lchown: case PR_lchown32: case PR_lgetxattr: case PR_llistxattr: case PR_lremovexattr: case PR_lsetxattr: case PR_lstat: case PR_lstat64: case PR_oldlstat: case PR_unlink: case PR_rmdir: case PR_mkdir: status = translate_sysarg(tracee, SYSARG_1, SYMLINK); break; case PR_pivot_root: status = translate_sysarg(tracee, SYSARG_1, REGULAR); if (status < 0) break; status = translate_sysarg(tracee, SYSARG_2, REGULAR); break; case PR_linkat: olddirfd = peek_reg(tracee, CURRENT, SYSARG_1); newdirfd = peek_reg(tracee, CURRENT, SYSARG_3); flags = peek_reg(tracee, CURRENT, SYSARG_5); status = get_sysarg_path(tracee, oldpath, SYSARG_2); if (status < 0) break; status = get_sysarg_path(tracee, newpath, SYSARG_4); if (status < 0) break; if ((flags & AT_SYMLINK_FOLLOW) != 0) status = translate_path2(tracee, olddirfd, oldpath, SYSARG_2, REGULAR); else status = translate_path2(tracee, olddirfd, oldpath, SYSARG_2, SYMLINK); if (status < 0) break; status = translate_path2(tracee, newdirfd, newpath, SYSARG_4, SYMLINK); break; case PR_mount: status = get_sysarg_path(tracee, path, SYSARG_1); if (status < 0) break; /* The following check covers only 90% of the cases. */ if (path[0] == '/' || path[0] == '.') { status = translate_path2(tracee, AT_FDCWD, path, SYSARG_1, REGULAR); if (status < 0) break; } status = translate_sysarg(tracee, SYSARG_2, REGULAR); break; case PR_openat: dirfd = peek_reg(tracee, CURRENT, SYSARG_1); flags = peek_reg(tracee, CURRENT, SYSARG_3); status = get_sysarg_path(tracee, path, SYSARG_2); if (status < 0) break; if ( ((flags & O_NOFOLLOW) != 0) || ((flags & O_EXCL) != 0 && (flags & O_CREAT) != 0)) status = translate_path2(tracee, dirfd, path, SYSARG_2, SYMLINK); else status = translate_path2(tracee, dirfd, path, SYSARG_2, REGULAR); break; case PR_readlinkat: case PR_unlinkat: case PR_mkdirat: dirfd = peek_reg(tracee, CURRENT, SYSARG_1); status = get_sysarg_path(tracee, path, SYSARG_2); if (status < 0) break; status = translate_path2(tracee, dirfd, path, SYSARG_2, SYMLINK); break; case PR_link: case PR_rename: status = translate_sysarg(tracee, SYSARG_1, SYMLINK); if (status < 0) break; status = translate_sysarg(tracee, SYSARG_2, SYMLINK); break; case PR_renameat: olddirfd = peek_reg(tracee, CURRENT, SYSARG_1); newdirfd = peek_reg(tracee, CURRENT, SYSARG_3); status = get_sysarg_path(tracee, oldpath, SYSARG_2); if (status < 0) break; status = get_sysarg_path(tracee, newpath, SYSARG_4); if (status < 0) break; status = translate_path2(tracee, olddirfd, oldpath, SYSARG_2, SYMLINK); if (status < 0) break; status = translate_path2(tracee, newdirfd, newpath, SYSARG_4, SYMLINK); break; case PR_symlink: status = translate_sysarg(tracee, SYSARG_2, SYMLINK); break; case PR_symlinkat: newdirfd = peek_reg(tracee, CURRENT, SYSARG_2); status = get_sysarg_path(tracee, newpath, SYSARG_3); if (status < 0) break; status = translate_path2(tracee, newdirfd, newpath, SYSARG_3, SYMLINK); break; } end: status2 = notify_extensions(tracee, SYSCALL_ENTER_END, status, 0); if (status2 < 0) status = status2; return status; } care-2.2.1/src/syscall/sysnums-x32.h0000644000175000017500000001634212430615044016554 0ustar ivoireivoire#include "syscall/sysnum.h" static const Sysnum sysnums_x32[] = { [ 0 ] = PR_read, [ 1 ] = PR_write, [ 2 ] = PR_open, [ 3 ] = PR_close, [ 4 ] = PR_stat, [ 5 ] = PR_fstat, [ 6 ] = PR_lstat, [ 7 ] = PR_poll, [ 8 ] = PR_lseek, [ 9 ] = PR_mmap, [ 10 ] = PR_mprotect, [ 11 ] = PR_munmap, [ 12 ] = PR_brk, [ 14 ] = PR_rt_sigprocmask, [ 17 ] = PR_pread64, [ 18 ] = PR_pwrite64, [ 21 ] = PR_access, [ 22 ] = PR_pipe, [ 23 ] = PR_select, [ 24 ] = PR_sched_yield, [ 25 ] = PR_mremap, [ 26 ] = PR_msync, [ 27 ] = PR_mincore, [ 28 ] = PR_madvise, [ 29 ] = PR_shmget, [ 30 ] = PR_shmat, [ 31 ] = PR_shmctl, [ 32 ] = PR_dup, [ 33 ] = PR_dup2, [ 34 ] = PR_pause, [ 35 ] = PR_nanosleep, [ 36 ] = PR_getitimer, [ 37 ] = PR_alarm, [ 38 ] = PR_setitimer, [ 39 ] = PR_getpid, [ 40 ] = PR_sendfile, [ 41 ] = PR_socket, [ 42 ] = PR_connect, [ 43 ] = PR_accept, [ 44 ] = PR_sendto, [ 48 ] = PR_shutdown, [ 49 ] = PR_bind, [ 50 ] = PR_listen, [ 51 ] = PR_getsockname, [ 52 ] = PR_getpeername, [ 53 ] = PR_socketpair, [ 56 ] = PR_clone, [ 57 ] = PR_fork, [ 58 ] = PR_vfork, [ 60 ] = PR_exit, [ 61 ] = PR_wait4, [ 62 ] = PR_kill, [ 63 ] = PR_uname, [ 64 ] = PR_semget, [ 65 ] = PR_semop, [ 66 ] = PR_semctl, [ 67 ] = PR_shmdt, [ 68 ] = PR_msgget, [ 69 ] = PR_msgsnd, [ 70 ] = PR_msgrcv, [ 71 ] = PR_msgctl, [ 72 ] = PR_fcntl, [ 73 ] = PR_flock, [ 74 ] = PR_fsync, [ 75 ] = PR_fdatasync, [ 76 ] = PR_truncate, [ 77 ] = PR_ftruncate, [ 78 ] = PR_getdents, [ 79 ] = PR_getcwd, [ 80 ] = PR_chdir, [ 81 ] = PR_fchdir, [ 82 ] = PR_rename, [ 83 ] = PR_mkdir, [ 84 ] = PR_rmdir, [ 85 ] = PR_creat, [ 86 ] = PR_link, [ 87 ] = PR_unlink, [ 88 ] = PR_symlink, [ 89 ] = PR_readlink, [ 90 ] = PR_chmod, [ 91 ] = PR_fchmod, [ 92 ] = PR_chown, [ 93 ] = PR_fchown, [ 94 ] = PR_lchown, [ 95 ] = PR_umask, [ 96 ] = PR_gettimeofday, [ 97 ] = PR_getrlimit, [ 98 ] = PR_getrusage, [ 99 ] = PR_sysinfo, [ 100 ] = PR_times, [ 102 ] = PR_getuid, [ 103 ] = PR_syslog, [ 104 ] = PR_getgid, [ 105 ] = PR_setuid, [ 106 ] = PR_setgid, [ 107 ] = PR_geteuid, [ 108 ] = PR_getegid, [ 109 ] = PR_setpgid, [ 110 ] = PR_getppid, [ 111 ] = PR_getpgrp, [ 112 ] = PR_setsid, [ 113 ] = PR_setreuid, [ 114 ] = PR_setregid, [ 115 ] = PR_getgroups, [ 116 ] = PR_setgroups, [ 117 ] = PR_setresuid, [ 118 ] = PR_getresuid, [ 119 ] = PR_setresgid, [ 120 ] = PR_getresgid, [ 121 ] = PR_getpgid, [ 122 ] = PR_setfsuid, [ 123 ] = PR_setfsgid, [ 124 ] = PR_getsid, [ 125 ] = PR_capget, [ 126 ] = PR_capset, [ 130 ] = PR_rt_sigsuspend, [ 132 ] = PR_utime, [ 133 ] = PR_mknod, [ 135 ] = PR_personality, [ 136 ] = PR_ustat, [ 137 ] = PR_statfs, [ 138 ] = PR_fstatfs, [ 139 ] = PR_sysfs, [ 140 ] = PR_getpriority, [ 141 ] = PR_setpriority, [ 142 ] = PR_sched_setparam, [ 143 ] = PR_sched_getparam, [ 144 ] = PR_sched_setscheduler, [ 145 ] = PR_sched_getscheduler, [ 146 ] = PR_sched_get_priority_max, [ 147 ] = PR_sched_get_priority_min, [ 148 ] = PR_sched_rr_get_interval, [ 149 ] = PR_mlock, [ 150 ] = PR_munlock, [ 151 ] = PR_mlockall, [ 152 ] = PR_munlockall, [ 153 ] = PR_vhangup, [ 154 ] = PR_modify_ldt, [ 155 ] = PR_pivot_root, [ 157 ] = PR_prctl, [ 158 ] = PR_arch_prctl, [ 159 ] = PR_adjtimex, [ 160 ] = PR_setrlimit, [ 161 ] = PR_chroot, [ 162 ] = PR_sync, [ 163 ] = PR_acct, [ 164 ] = PR_settimeofday, [ 165 ] = PR_mount, [ 166 ] = PR_umount2, [ 167 ] = PR_swapon, [ 168 ] = PR_swapoff, [ 169 ] = PR_reboot, [ 170 ] = PR_sethostname, [ 171 ] = PR_setdomainname, [ 172 ] = PR_iopl, [ 173 ] = PR_ioperm, [ 175 ] = PR_init_module, [ 176 ] = PR_delete_module, [ 179 ] = PR_quotactl, [ 181 ] = PR_getpmsg, [ 182 ] = PR_putpmsg, [ 183 ] = PR_afs_syscall, [ 184 ] = PR_tuxcall, [ 185 ] = PR_security, [ 186 ] = PR_gettid, [ 187 ] = PR_readahead, [ 188 ] = PR_setxattr, [ 189 ] = PR_lsetxattr, [ 190 ] = PR_fsetxattr, [ 191 ] = PR_getxattr, [ 192 ] = PR_lgetxattr, [ 193 ] = PR_fgetxattr, [ 194 ] = PR_listxattr, [ 195 ] = PR_llistxattr, [ 196 ] = PR_flistxattr, [ 197 ] = PR_removexattr, [ 198 ] = PR_lremovexattr, [ 199 ] = PR_fremovexattr, [ 200 ] = PR_tkill, [ 201 ] = PR_time, [ 202 ] = PR_futex, [ 203 ] = PR_sched_setaffinity, [ 204 ] = PR_sched_getaffinity, [ 206 ] = PR_io_setup, [ 207 ] = PR_io_destroy, [ 208 ] = PR_io_getevents, [ 209 ] = PR_io_submit, [ 210 ] = PR_io_cancel, [ 212 ] = PR_lookup_dcookie, [ 213 ] = PR_epoll_create, [ 216 ] = PR_remap_file_pages, [ 217 ] = PR_getdents64, [ 218 ] = PR_set_tid_address, [ 219 ] = PR_restart_syscall, [ 220 ] = PR_semtimedop, [ 221 ] = PR_fadvise64, [ 223 ] = PR_timer_settime, [ 224 ] = PR_timer_gettime, [ 225 ] = PR_timer_getoverrun, [ 226 ] = PR_timer_delete, [ 227 ] = PR_clock_settime, [ 228 ] = PR_clock_gettime, [ 229 ] = PR_clock_getres, [ 230 ] = PR_clock_nanosleep, [ 231 ] = PR_exit_group, [ 232 ] = PR_epoll_wait, [ 233 ] = PR_epoll_ctl, [ 234 ] = PR_tgkill, [ 235 ] = PR_utimes, [ 237 ] = PR_mbind, [ 238 ] = PR_set_mempolicy, [ 239 ] = PR_get_mempolicy, [ 240 ] = PR_mq_open, [ 241 ] = PR_mq_unlink, [ 242 ] = PR_mq_timedsend, [ 243 ] = PR_mq_timedreceive, [ 245 ] = PR_mq_getsetattr, [ 248 ] = PR_add_key, [ 249 ] = PR_request_key, [ 250 ] = PR_keyctl, [ 251 ] = PR_ioprio_set, [ 252 ] = PR_ioprio_get, [ 253 ] = PR_inotify_init, [ 254 ] = PR_inotify_add_watch, [ 255 ] = PR_inotify_rm_watch, [ 256 ] = PR_migrate_pages, [ 257 ] = PR_openat, [ 258 ] = PR_mkdirat, [ 259 ] = PR_mknodat, [ 260 ] = PR_fchownat, [ 261 ] = PR_futimesat, [ 262 ] = PR_newfstatat, [ 263 ] = PR_unlinkat, [ 264 ] = PR_renameat, [ 265 ] = PR_linkat, [ 266 ] = PR_symlinkat, [ 267 ] = PR_readlinkat, [ 268 ] = PR_fchmodat, [ 269 ] = PR_faccessat, [ 270 ] = PR_pselect6, [ 271 ] = PR_ppoll, [ 272 ] = PR_unshare, [ 275 ] = PR_splice, [ 276 ] = PR_tee, [ 277 ] = PR_sync_file_range, [ 280 ] = PR_utimensat, [ 281 ] = PR_epoll_pwait, [ 282 ] = PR_signalfd, [ 283 ] = PR_timerfd_create, [ 284 ] = PR_eventfd, [ 285 ] = PR_fallocate, [ 286 ] = PR_timerfd_settime, [ 287 ] = PR_timerfd_gettime, [ 288 ] = PR_accept4, [ 289 ] = PR_signalfd4, [ 290 ] = PR_eventfd2, [ 291 ] = PR_epoll_create1, [ 292 ] = PR_dup3, [ 293 ] = PR_pipe2, [ 294 ] = PR_inotify_init1, [ 298 ] = PR_perf_event_open, [ 300 ] = PR_fanotify_init, [ 301 ] = PR_fanotify_mark, [ 302 ] = PR_prlimit64, [ 303 ] = PR_name_to_handle_at, [ 304 ] = PR_open_by_handle_at, [ 305 ] = PR_clock_adjtime, [ 306 ] = PR_syncfs, [ 308 ] = PR_setns, [ 309 ] = PR_getcpu, [ 312 ] = PR_kcmp, [ 512 ] = PR_rt_sigaction, [ 513 ] = PR_rt_sigreturn, [ 514 ] = PR_ioctl, [ 515 ] = PR_readv, [ 516 ] = PR_writev, [ 517 ] = PR_recvfrom, [ 518 ] = PR_sendmsg, [ 519 ] = PR_recvmsg, [ 520 ] = PR_execve, [ 521 ] = PR_ptrace, [ 522 ] = PR_rt_sigpending, [ 523 ] = PR_rt_sigtimedwait, [ 524 ] = PR_rt_sigqueueinfo, [ 525 ] = PR_sigaltstack, [ 526 ] = PR_timer_create, [ 527 ] = PR_mq_notify, [ 528 ] = PR_kexec_load, [ 529 ] = PR_waitid, [ 530 ] = PR_set_robust_list, [ 531 ] = PR_get_robust_list, [ 532 ] = PR_vmsplice, [ 533 ] = PR_move_pages, [ 534 ] = PR_preadv, [ 535 ] = PR_pwritev, [ 536 ] = PR_rt_tgsigqueueinfo, [ 537 ] = PR_recvmmsg, [ 538 ] = PR_sendmmsg, [ 539 ] = PR_process_vm_readv, [ 540 ] = PR_process_vm_writev, [ 541 ] = PR_setsockopt, [ 542 ] = PR_getsockopt, }; care-2.2.1/src/syscall/sysnum.c0000644000175000017500000000676612430615044015763 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include #include "syscall/sysnum.h" #include "tracee/tracee.h" #include "tracee/abi.h" #include "tracee/reg.h" #include "arch.h" #include "cli/notice.h" #include SYSNUMS_HEADER1 #ifdef SYSNUMS_HEADER2 #include SYSNUMS_HEADER2 #endif #ifdef SYSNUMS_HEADER3 #include SYSNUMS_HEADER3 #endif typedef struct { const Sysnum *table; word_t offset; word_t length; } Sysnums; /** * Update @sysnums' fields with the sysnum table for the given @abi. */ static void get_sysnums(Abi abi, Sysnums *sysnums) { switch (abi) { case ABI_DEFAULT: sysnums->table = SYSNUMS_ABI1; sysnums->length = sizeof(SYSNUMS_ABI1) / sizeof(Sysnum); sysnums->offset = 0; return; #ifdef SYSNUMS_ABI2 case ABI_2: sysnums->table = SYSNUMS_ABI2; sysnums->length = sizeof(SYSNUMS_ABI2) / sizeof(Sysnum); sysnums->offset = 0; return; #endif #ifdef SYSNUMS_ABI3 case ABI_3: sysnums->table = SYSNUMS_ABI3; sysnums->length = sizeof(SYSNUMS_ABI3) / sizeof(Sysnum); sysnums->offset = 0x40000000; /* x32 */ return; #endif default: assert(0); } } /** * Return the neutral value of @sysnum from the given @abi. */ static Sysnum translate_sysnum(Abi abi, word_t sysnum) { Sysnums sysnums; word_t index; get_sysnums(abi, &sysnums); /* Sanity checks. */ if (sysnum < sysnums.offset) return PR_void; index = sysnum - sysnums.offset; /* Sanity checks. */ if (index > sysnums.length) return PR_void; return sysnums.table[index]; } /** * Return the architecture value of @sysnum for the given @abi. */ word_t detranslate_sysnum(Abi abi, Sysnum sysnum) { Sysnums sysnums; size_t i; /* Very special case. */ if (sysnum == PR_void) return SYSCALL_AVOIDER; get_sysnums(abi, &sysnums); for (i = 0; i < sysnums.length; i++) { if (sysnums.table[i] != sysnum) continue; return i + sysnums.offset; } return SYSCALL_AVOIDER; } /** * Return the neutral value of the @tracee's current syscall number. */ Sysnum get_sysnum(const Tracee *tracee, RegVersion version) { return translate_sysnum(get_abi(tracee), peek_reg(tracee, version, SYSARG_NUM)); } /** * Overwrite the @tracee's current syscall number with @sysnum. Note: * this neutral value is automatically converted into the architecture * value. */ void set_sysnum(Tracee *tracee, Sysnum sysnum) { poke_reg(tracee, SYSARG_NUM, detranslate_sysnum(get_abi(tracee), sysnum)); } /** * Return the human readable name of @sysnum. */ const char *stringify_sysnum(Sysnum sysnum) { #define SYSNUM(item) [ PR_ ## item ] = #item, static const char *names[] = { #include "syscall/sysnums.list" }; #undef SYSNUM if (sysnum == 0) return "void"; if (sysnum >= PR_NB_SYSNUM) return ""; return names[sysnum]; } care-2.2.1/src/syscall/syscall.c0000644000175000017500000001244712430615044016070 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* assert(3), */ #include /* PATH_MAX, */ #include /* strlen(3), */ #include /* errno(3), E* */ #include "syscall/syscall.h" #include "syscall/chain.h" #include "extension/extension.h" #include "tracee/tracee.h" #include "tracee/reg.h" #include "tracee/mem.h" /** * Copy in @path a C string (PATH_MAX bytes max.) from the @tracee's * memory address space pointed to by the @reg argument of the * current syscall. This function returns -errno if an error occured, * otherwise it returns the size in bytes put into the @path. */ int get_sysarg_path(const Tracee *tracee, char path[PATH_MAX], Reg reg) { int size; word_t src; src = peek_reg(tracee, CURRENT, reg); /* Check if the parameter is not NULL. Technically we should * not return an -EFAULT for this special value since it is * allowed for some syscall, utimensat(2) for instance. */ if (src == 0) { path[0] = '\0'; return 0; } /* Get the path from the tracee's memory space. */ size = read_path(tracee, path, src); if (size < 0) return size; path[size] = '\0'; return size; } /** * Copy @size bytes of the data pointed to by @tracer_ptr into a * @tracee's memory block and make the @reg argument of the current * syscall points to this new block. This function returns -errno if * an error occured, otherwise 0. */ static int set_sysarg_data(Tracee *tracee, void *tracer_ptr, word_t size, Reg reg) { word_t tracee_ptr; int status; /* Allocate space into the tracee's memory to host the new data. */ tracee_ptr = alloc_mem(tracee, size); if (tracee_ptr == 0) return -EFAULT; /* Copy the new data into the previously allocated space. */ status = write_data(tracee, tracee_ptr, tracer_ptr, size); if (status < 0) return status; /* Make this argument point to the new data. */ poke_reg(tracee, reg, tracee_ptr); return 0; } /** * Copy @path to a @tracee's memory block and make the @reg argument * of the current syscall points to this new block. This function * returns -errno if an error occured, otherwise 0. */ int set_sysarg_path(Tracee *tracee, char path[PATH_MAX], Reg reg) { return set_sysarg_data(tracee, path, strlen(path) + 1, reg); } void translate_syscall(Tracee *tracee) { const bool is_enter_stage = IS_IN_SYSENTER(tracee); int status; assert(tracee->exe != NULL); status = fetch_regs(tracee); if (status < 0) return; if (is_enter_stage) { /* Never restore original register values at the end * of this stage. */ tracee->restore_original_regs = false; print_current_regs(tracee, 3, "sysenter start"); /* Translate the syscall only if it was actually * requested by the tracee, it is not a syscall * chained by PRoot. */ if (tracee->chain.syscalls == NULL) { save_current_regs(tracee, ORIGINAL); status = translate_syscall_enter(tracee); save_current_regs(tracee, MODIFIED); } else { status = notify_extensions(tracee, SYSCALL_CHAINED_ENTER, 0, 0); tracee->restart_how = PTRACE_SYSCALL; } /* Remember the tracee status for the "exit" stage and * avoid the actual syscall if an error was reported * by the translation/extension. */ if (status < 0) { set_sysnum(tracee, PR_void); poke_reg(tracee, SYSARG_RESULT, (word_t) status); tracee->status = status; } else tracee->status = 1; /* Restore tracee's stack pointer now if it won't hit * the sysexit stage (i.e. when seccomp is enabled and * there's nothing else to do). */ if (tracee->restart_how == PTRACE_CONT) { tracee->status = 0; poke_reg(tracee, STACK_POINTER, peek_reg(tracee, ORIGINAL, STACK_POINTER)); } } else { /* By default, restore original register values at the * end of this stage. */ tracee->restore_original_regs = true; print_current_regs(tracee, 5, "sysexit start"); /* Translate the syscall only if it was actually * requested by the tracee, it is not a syscall * chained by PRoot. */ if (tracee->chain.syscalls == NULL) translate_syscall_exit(tracee); else (void) notify_extensions(tracee, SYSCALL_CHAINED_EXIT, 0, 0); /* Reset the tracee's status. */ tracee->status = 0; /* Insert the next chained syscall, if any. */ if (tracee->chain.syscalls != NULL) chain_next_syscall(tracee); } (void) push_regs(tracee); if (is_enter_stage) print_current_regs(tracee, 5, "sysenter end" ); else print_current_regs(tracee, 4, "sysexit end"); } care-2.2.1/src/syscall/socket.h0000644000175000017500000000217512430615044015710 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef SOCKET_H #define SOCKET_H #include "arch.h" /* word_t */ #include "tracee/tracee.h" int translate_socketcall_enter(Tracee *tracee, word_t *sock_addr, int size); int translate_socketcall_exit(Tracee *tracee, word_t sock_addr, word_t size_addr, word_t max_size); #endif /* SOCKET_H */ care-2.2.1/src/syscall/sysnums-i386.h0000644000175000017500000002044112430615044016624 0ustar ivoireivoire#include "syscall/sysnum.h" static const Sysnum sysnums_i386[] = { [ 0 ] = PR_restart_syscall, [ 1 ] = PR_exit, [ 2 ] = PR_fork, [ 3 ] = PR_read, [ 4 ] = PR_write, [ 5 ] = PR_open, [ 6 ] = PR_close, [ 7 ] = PR_waitpid, [ 8 ] = PR_creat, [ 9 ] = PR_link, [ 10 ] = PR_unlink, [ 11 ] = PR_execve, [ 12 ] = PR_chdir, [ 13 ] = PR_time, [ 14 ] = PR_mknod, [ 15 ] = PR_chmod, [ 16 ] = PR_lchown, [ 17 ] = PR_break, [ 18 ] = PR_oldstat, [ 19 ] = PR_lseek, [ 20 ] = PR_getpid, [ 21 ] = PR_mount, [ 22 ] = PR_umount, [ 23 ] = PR_setuid, [ 24 ] = PR_getuid, [ 25 ] = PR_stime, [ 26 ] = PR_ptrace, [ 27 ] = PR_alarm, [ 28 ] = PR_oldfstat, [ 29 ] = PR_pause, [ 30 ] = PR_utime, [ 31 ] = PR_stty, [ 32 ] = PR_gtty, [ 33 ] = PR_access, [ 34 ] = PR_nice, [ 35 ] = PR_ftime, [ 36 ] = PR_sync, [ 37 ] = PR_kill, [ 38 ] = PR_rename, [ 39 ] = PR_mkdir, [ 40 ] = PR_rmdir, [ 41 ] = PR_dup, [ 42 ] = PR_pipe, [ 43 ] = PR_times, [ 44 ] = PR_prof, [ 45 ] = PR_brk, [ 46 ] = PR_setgid, [ 47 ] = PR_getgid, [ 48 ] = PR_signal, [ 49 ] = PR_geteuid, [ 50 ] = PR_getegid, [ 51 ] = PR_acct, [ 52 ] = PR_umount2, [ 53 ] = PR_lock, [ 54 ] = PR_ioctl, [ 55 ] = PR_fcntl, [ 56 ] = PR_mpx, [ 57 ] = PR_setpgid, [ 58 ] = PR_ulimit, [ 59 ] = PR_oldolduname, [ 60 ] = PR_umask, [ 61 ] = PR_chroot, [ 62 ] = PR_ustat, [ 63 ] = PR_dup2, [ 64 ] = PR_getppid, [ 65 ] = PR_getpgrp, [ 66 ] = PR_setsid, [ 67 ] = PR_sigaction, [ 68 ] = PR_sgetmask, [ 69 ] = PR_ssetmask, [ 70 ] = PR_setreuid, [ 71 ] = PR_setregid, [ 72 ] = PR_sigsuspend, [ 73 ] = PR_sigpending, [ 74 ] = PR_sethostname, [ 75 ] = PR_setrlimit, [ 76 ] = PR_getrlimit, [ 77 ] = PR_getrusage, [ 78 ] = PR_gettimeofday, [ 79 ] = PR_settimeofday, [ 80 ] = PR_getgroups, [ 81 ] = PR_setgroups, [ 82 ] = PR_select, [ 83 ] = PR_symlink, [ 84 ] = PR_oldlstat, [ 85 ] = PR_readlink, [ 86 ] = PR_uselib, [ 87 ] = PR_swapon, [ 88 ] = PR_reboot, [ 89 ] = PR_readdir, [ 90 ] = PR_mmap, [ 91 ] = PR_munmap, [ 92 ] = PR_truncate, [ 93 ] = PR_ftruncate, [ 94 ] = PR_fchmod, [ 95 ] = PR_fchown, [ 96 ] = PR_getpriority, [ 97 ] = PR_setpriority, [ 98 ] = PR_profil, [ 99 ] = PR_statfs, [ 100 ] = PR_fstatfs, [ 101 ] = PR_ioperm, [ 102 ] = PR_socketcall, [ 103 ] = PR_syslog, [ 104 ] = PR_setitimer, [ 105 ] = PR_getitimer, [ 106 ] = PR_stat, [ 107 ] = PR_lstat, [ 108 ] = PR_fstat, [ 109 ] = PR_olduname, [ 110 ] = PR_iopl, [ 111 ] = PR_vhangup, [ 112 ] = PR_idle, [ 113 ] = PR_vm86old, [ 114 ] = PR_wait4, [ 115 ] = PR_swapoff, [ 116 ] = PR_sysinfo, [ 117 ] = PR_ipc, [ 118 ] = PR_fsync, [ 119 ] = PR_sigreturn, [ 120 ] = PR_clone, [ 121 ] = PR_setdomainname, [ 122 ] = PR_uname, [ 123 ] = PR_modify_ldt, [ 124 ] = PR_adjtimex, [ 125 ] = PR_mprotect, [ 126 ] = PR_sigprocmask, [ 127 ] = PR_create_module, [ 128 ] = PR_init_module, [ 129 ] = PR_delete_module, [ 130 ] = PR_get_kernel_syms, [ 131 ] = PR_quotactl, [ 132 ] = PR_getpgid, [ 133 ] = PR_fchdir, [ 134 ] = PR_bdflush, [ 135 ] = PR_sysfs, [ 136 ] = PR_personality, [ 137 ] = PR_afs_syscall, [ 138 ] = PR_setfsuid, [ 139 ] = PR_setfsgid, [ 140 ] = PR__llseek, [ 141 ] = PR_getdents, [ 142 ] = PR__newselect, [ 143 ] = PR_flock, [ 144 ] = PR_msync, [ 145 ] = PR_readv, [ 146 ] = PR_writev, [ 147 ] = PR_getsid, [ 148 ] = PR_fdatasync, [ 149 ] = PR__sysctl, [ 150 ] = PR_mlock, [ 151 ] = PR_munlock, [ 152 ] = PR_mlockall, [ 153 ] = PR_munlockall, [ 154 ] = PR_sched_setparam, [ 155 ] = PR_sched_getparam, [ 156 ] = PR_sched_setscheduler, [ 157 ] = PR_sched_getscheduler, [ 158 ] = PR_sched_yield, [ 159 ] = PR_sched_get_priority_max, [ 160 ] = PR_sched_get_priority_min, [ 161 ] = PR_sched_rr_get_interval, [ 162 ] = PR_nanosleep, [ 163 ] = PR_mremap, [ 164 ] = PR_setresuid, [ 165 ] = PR_getresuid, [ 166 ] = PR_vm86, [ 167 ] = PR_query_module, [ 168 ] = PR_poll, [ 169 ] = PR_nfsservctl, [ 170 ] = PR_setresgid, [ 171 ] = PR_getresgid, [ 172 ] = PR_prctl, [ 173 ] = PR_rt_sigreturn, [ 174 ] = PR_rt_sigaction, [ 175 ] = PR_rt_sigprocmask, [ 176 ] = PR_rt_sigpending, [ 177 ] = PR_rt_sigtimedwait, [ 178 ] = PR_rt_sigqueueinfo, [ 179 ] = PR_rt_sigsuspend, [ 180 ] = PR_pread64, [ 181 ] = PR_pwrite64, [ 182 ] = PR_chown, [ 183 ] = PR_getcwd, [ 184 ] = PR_capget, [ 185 ] = PR_capset, [ 186 ] = PR_sigaltstack, [ 187 ] = PR_sendfile, [ 188 ] = PR_getpmsg, [ 189 ] = PR_putpmsg, [ 190 ] = PR_vfork, [ 191 ] = PR_ugetrlimit, [ 192 ] = PR_mmap2, [ 193 ] = PR_truncate64, [ 194 ] = PR_ftruncate64, [ 195 ] = PR_stat64, [ 196 ] = PR_lstat64, [ 197 ] = PR_fstat64, [ 198 ] = PR_lchown32, [ 199 ] = PR_getuid32, [ 200 ] = PR_getgid32, [ 201 ] = PR_geteuid32, [ 202 ] = PR_getegid32, [ 203 ] = PR_setreuid32, [ 204 ] = PR_setregid32, [ 205 ] = PR_getgroups32, [ 206 ] = PR_setgroups32, [ 207 ] = PR_fchown32, [ 208 ] = PR_setresuid32, [ 209 ] = PR_getresuid32, [ 210 ] = PR_setresgid32, [ 211 ] = PR_getresgid32, [ 212 ] = PR_chown32, [ 213 ] = PR_setuid32, [ 214 ] = PR_setgid32, [ 215 ] = PR_setfsuid32, [ 216 ] = PR_setfsgid32, [ 217 ] = PR_pivot_root, [ 218 ] = PR_mincore, [ 219 ] = PR_madvise, [ 220 ] = PR_getdents64, [ 221 ] = PR_fcntl64, [ 224 ] = PR_gettid, [ 225 ] = PR_readahead, [ 226 ] = PR_setxattr, [ 227 ] = PR_lsetxattr, [ 228 ] = PR_fsetxattr, [ 229 ] = PR_getxattr, [ 230 ] = PR_lgetxattr, [ 231 ] = PR_fgetxattr, [ 232 ] = PR_listxattr, [ 233 ] = PR_llistxattr, [ 234 ] = PR_flistxattr, [ 235 ] = PR_removexattr, [ 236 ] = PR_lremovexattr, [ 237 ] = PR_fremovexattr, [ 238 ] = PR_tkill, [ 239 ] = PR_sendfile64, [ 240 ] = PR_futex, [ 241 ] = PR_sched_setaffinity, [ 242 ] = PR_sched_getaffinity, [ 243 ] = PR_set_thread_area, [ 244 ] = PR_get_thread_area, [ 245 ] = PR_io_setup, [ 246 ] = PR_io_destroy, [ 247 ] = PR_io_getevents, [ 248 ] = PR_io_submit, [ 249 ] = PR_io_cancel, [ 250 ] = PR_fadvise64, [ 252 ] = PR_exit_group, [ 253 ] = PR_lookup_dcookie, [ 254 ] = PR_epoll_create, [ 255 ] = PR_epoll_ctl, [ 256 ] = PR_epoll_wait, [ 257 ] = PR_remap_file_pages, [ 258 ] = PR_set_tid_address, [ 259 ] = PR_timer_create, [ 260 ] = PR_timer_settime, [ 261 ] = PR_timer_gettime, [ 262 ] = PR_timer_getoverrun, [ 263 ] = PR_timer_delete, [ 264 ] = PR_clock_settime, [ 265 ] = PR_clock_gettime, [ 266 ] = PR_clock_getres, [ 267 ] = PR_clock_nanosleep, [ 268 ] = PR_statfs64, [ 269 ] = PR_fstatfs64, [ 270 ] = PR_tgkill, [ 271 ] = PR_utimes, [ 272 ] = PR_fadvise64_64, [ 273 ] = PR_vserver, [ 274 ] = PR_mbind, [ 275 ] = PR_get_mempolicy, [ 276 ] = PR_set_mempolicy, [ 277 ] = PR_mq_open, [ 278 ] = PR_mq_unlink, [ 279 ] = PR_mq_timedsend, [ 280 ] = PR_mq_timedreceive, [ 281 ] = PR_mq_notify, [ 282 ] = PR_mq_getsetattr, [ 283 ] = PR_kexec_load, [ 284 ] = PR_waitid, [ 286 ] = PR_add_key, [ 287 ] = PR_request_key, [ 288 ] = PR_keyctl, [ 289 ] = PR_ioprio_set, [ 290 ] = PR_ioprio_get, [ 291 ] = PR_inotify_init, [ 292 ] = PR_inotify_add_watch, [ 293 ] = PR_inotify_rm_watch, [ 294 ] = PR_migrate_pages, [ 295 ] = PR_openat, [ 296 ] = PR_mkdirat, [ 297 ] = PR_mknodat, [ 298 ] = PR_fchownat, [ 299 ] = PR_futimesat, [ 300 ] = PR_fstatat64, [ 301 ] = PR_unlinkat, [ 302 ] = PR_renameat, [ 303 ] = PR_linkat, [ 304 ] = PR_symlinkat, [ 305 ] = PR_readlinkat, [ 306 ] = PR_fchmodat, [ 307 ] = PR_faccessat, [ 308 ] = PR_pselect6, [ 309 ] = PR_ppoll, [ 310 ] = PR_unshare, [ 311 ] = PR_set_robust_list, [ 312 ] = PR_get_robust_list, [ 313 ] = PR_splice, [ 314 ] = PR_sync_file_range, [ 315 ] = PR_tee, [ 316 ] = PR_vmsplice, [ 317 ] = PR_move_pages, [ 318 ] = PR_getcpu, [ 319 ] = PR_epoll_pwait, [ 320 ] = PR_utimensat, [ 321 ] = PR_signalfd, [ 322 ] = PR_timerfd_create, [ 323 ] = PR_eventfd, [ 324 ] = PR_fallocate, [ 325 ] = PR_timerfd_settime, [ 326 ] = PR_timerfd_gettime, [ 327 ] = PR_signalfd4, [ 328 ] = PR_eventfd2, [ 329 ] = PR_epoll_create1, [ 330 ] = PR_dup3, [ 331 ] = PR_pipe2, [ 332 ] = PR_inotify_init1, [ 333 ] = PR_preadv, [ 334 ] = PR_pwritev, [ 335 ] = PR_rt_tgsigqueueinfo, [ 336 ] = PR_perf_event_open, [ 337 ] = PR_recvmmsg, [ 338 ] = PR_fanotify_init, [ 339 ] = PR_fanotify_mark, [ 340 ] = PR_prlimit64, [ 341 ] = PR_name_to_handle_at, [ 342 ] = PR_open_by_handle_at, [ 343 ] = PR_clock_adjtime, [ 344 ] = PR_syncfs, [ 345 ] = PR_sendmmsg, [ 346 ] = PR_setns, [ 347 ] = PR_process_vm_readv, [ 348 ] = PR_process_vm_writev, [ 349 ] = PR_kcmp, }; care-2.2.1/src/syscall/chain.c0000644000175000017500000001152112430615044015470 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* talloc*, */ #include /* STAILQ_*, */ #include /* E*, */ #include /* assert(3), */ #include "syscall/chain.h" #include "syscall/sysnum.h" #include "tracee/tracee.h" #include "tracee/reg.h" #include "arch.h" struct chained_syscall { Sysnum sysnum; word_t sysargs[6]; STAILQ_ENTRY(chained_syscall) link; }; STAILQ_HEAD(chained_syscalls, chained_syscall); /** * Append a new syscall (@sysnum, @sysarg_*) to the list of * "unrequested" syscalls for the given @tracee. These new syscalls * will be triggered in order once the current syscall is done. The * caller is free to force the last result of this syscall chain in * @tracee->chain.final_result. This function returns -errno if an * error occurred, otherwise 0. */ int register_chained_syscall(Tracee *tracee, Sysnum sysnum, word_t sysarg_1, word_t sysarg_2, word_t sysarg_3, word_t sysarg_4, word_t sysarg_5, word_t sysarg_6) { struct chained_syscall *syscall; if (tracee->chain.syscalls == NULL) { tracee->chain.syscalls = talloc_zero(tracee, struct chained_syscalls); if (tracee->chain.syscalls == NULL) return -ENOMEM; STAILQ_INIT(tracee->chain.syscalls); } syscall = talloc_zero(tracee->chain.syscalls, struct chained_syscall); if (syscall == NULL) return -ENOMEM; syscall->sysnum = sysnum; syscall->sysargs[0] = sysarg_1; syscall->sysargs[1] = sysarg_2; syscall->sysargs[2] = sysarg_3; syscall->sysargs[3] = sysarg_4; syscall->sysargs[4] = sysarg_5; syscall->sysargs[5] = sysarg_6; STAILQ_INSERT_TAIL(tracee->chain.syscalls, syscall, link); return 0; } /** * Use/remove the first element of @tracee->chain.syscalls to forge a * new syscall. This function should be called only at the end of in * the sysexit stage. */ void chain_next_syscall(Tracee *tracee) { struct chained_syscall *syscall; word_t instr_pointer; word_t sysnum; assert(tracee->chain.syscalls != NULL); /* No more chained syscalls: force the result of the initial * syscall (the one explicitly requested by the tracee). */ if (STAILQ_EMPTY(tracee->chain.syscalls)) { TALLOC_FREE(tracee->chain.syscalls); if (tracee->chain.force_final_result) poke_reg(tracee, SYSARG_RESULT, tracee->chain.final_result); tracee->chain.force_final_result = false; tracee->chain.final_result = 0; return; } /* Original register values will be restored right after the * last chained syscall. */ tracee->restore_original_regs = false; /* The list of chained syscalls is a FIFO. */ syscall = STAILQ_FIRST(tracee->chain.syscalls); STAILQ_REMOVE_HEAD(tracee->chain.syscalls, link); poke_reg(tracee, SYSARG_1, syscall->sysargs[0]); poke_reg(tracee, SYSARG_2, syscall->sysargs[1]); poke_reg(tracee, SYSARG_3, syscall->sysargs[2]); poke_reg(tracee, SYSARG_4, syscall->sysargs[3]); poke_reg(tracee, SYSARG_5, syscall->sysargs[4]); poke_reg(tracee, SYSARG_6, syscall->sysargs[5]); sysnum = detranslate_sysnum(get_abi(tracee), syscall->sysnum); poke_reg(tracee, SYSTRAP_NUM, sysnum); /* Move the instruction pointer back to the original trap. */ instr_pointer = peek_reg(tracee, ORIGINAL, INSTR_POINTER); poke_reg(tracee, INSTR_POINTER, instr_pointer - SYSTRAP_SIZE); } /** * Force the last result of the @tracee's current syscall chain to be * @forced_result. */ void force_chain_final_result(Tracee *tracee, word_t forced_result) { tracee->chain.force_final_result = true; tracee->chain.final_result = forced_result; } /** * Restart the original syscall of the given @tracee. The result of * the current syscall will be overwritten. This function returns the * same status as register_chained_syscall(). */ int restart_original_syscall(Tracee *tracee) { return register_chained_syscall(tracee, get_sysnum(tracee, ORIGINAL), peek_reg(tracee, ORIGINAL, SYSARG_1), peek_reg(tracee, ORIGINAL, SYSARG_2), peek_reg(tracee, ORIGINAL, SYSARG_3), peek_reg(tracee, ORIGINAL, SYSARG_4), peek_reg(tracee, ORIGINAL, SYSARG_5), peek_reg(tracee, ORIGINAL, SYSARG_6)); } care-2.2.1/src/syscall/sysnums.list0000644000175000017500000001704512430615044016667 0ustar ivoireivoireSYSNUM(ARM_BASE) SYSNUM(ARM_breakpoint) SYSNUM(ARM_cacheflush) SYSNUM(ARM_set_tls) SYSNUM(ARM_usr26) SYSNUM(ARM_usr32) SYSNUM(X32_SYSCALL_BIT) SYSNUM(_llseek) SYSNUM(_newselect) SYSNUM(_sysctl) SYSNUM(accept) SYSNUM(accept4) SYSNUM(access) SYSNUM(acct) SYSNUM(add_key) SYSNUM(adjtimex) SYSNUM(afs_syscall) SYSNUM(alarm) SYSNUM(arch_prctl) SYSNUM(arch_specific_syscall) SYSNUM(arm_fadvise64_64) SYSNUM(arm_sync_file_range) SYSNUM(bdflush) SYSNUM(bind) SYSNUM(break) SYSNUM(brk) SYSNUM(cacheflush) SYSNUM(capget) SYSNUM(capset) SYSNUM(chdir) SYSNUM(chmod) SYSNUM(chown) SYSNUM(chown32) SYSNUM(chroot) SYSNUM(clock_adjtime) SYSNUM(clock_getres) SYSNUM(clock_gettime) SYSNUM(clock_nanosleep) SYSNUM(clock_settime) SYSNUM(clone) SYSNUM(close) SYSNUM(connect) SYSNUM(creat) SYSNUM(create_module) SYSNUM(delete_module) SYSNUM(dup) SYSNUM(dup2) SYSNUM(dup3) SYSNUM(epoll_create) SYSNUM(epoll_create1) SYSNUM(epoll_ctl) SYSNUM(epoll_ctl_old) SYSNUM(epoll_pwait) SYSNUM(epoll_wait) SYSNUM(epoll_wait_old) SYSNUM(eventfd) SYSNUM(eventfd2) SYSNUM(execve) SYSNUM(exit) SYSNUM(exit_group) SYSNUM(faccessat) SYSNUM(fadvise64) SYSNUM(fadvise64_64) SYSNUM(fallocate) SYSNUM(fanotify_init) SYSNUM(fanotify_mark) SYSNUM(fchdir) SYSNUM(fchmod) SYSNUM(fchmodat) SYSNUM(fchown) SYSNUM(fchown32) SYSNUM(fchownat) SYSNUM(fcntl) SYSNUM(fcntl64) SYSNUM(fdatasync) SYSNUM(fgetxattr) SYSNUM(flistxattr) SYSNUM(flock) SYSNUM(fork) SYSNUM(fremovexattr) SYSNUM(fsetxattr) SYSNUM(fstat) SYSNUM(fstat64) SYSNUM(fstatat64) SYSNUM(fstatfs) SYSNUM(fstatfs64) SYSNUM(fsync) SYSNUM(ftime) SYSNUM(ftruncate) SYSNUM(ftruncate64) SYSNUM(futex) SYSNUM(futimesat) SYSNUM(get_kernel_syms) SYSNUM(get_mempolicy) SYSNUM(get_robust_list) SYSNUM(get_thread_area) SYSNUM(getcpu) SYSNUM(getcwd) SYSNUM(getdents) SYSNUM(getdents64) SYSNUM(getegid) SYSNUM(getegid32) SYSNUM(geteuid) SYSNUM(geteuid32) SYSNUM(getgid) SYSNUM(getgid32) SYSNUM(getgroups) SYSNUM(getgroups32) SYSNUM(getitimer) SYSNUM(getpeername) SYSNUM(getpgid) SYSNUM(getpgrp) SYSNUM(getpid) SYSNUM(getpmsg) SYSNUM(getppid) SYSNUM(getpriority) SYSNUM(getresgid) SYSNUM(getresgid32) SYSNUM(getresuid) SYSNUM(getresuid32) SYSNUM(getrlimit) SYSNUM(getrusage) SYSNUM(getsid) SYSNUM(getsockname) SYSNUM(getsockopt) SYSNUM(gettid) SYSNUM(gettimeofday) SYSNUM(getuid) SYSNUM(getuid32) SYSNUM(getxattr) SYSNUM(gtty) SYSNUM(idle) SYSNUM(init_module) SYSNUM(inotify_add_watch) SYSNUM(inotify_init) SYSNUM(inotify_init1) SYSNUM(inotify_rm_watch) SYSNUM(io_cancel) SYSNUM(io_destroy) SYSNUM(io_getevents) SYSNUM(io_setup) SYSNUM(io_submit) SYSNUM(ioctl) SYSNUM(ioperm) SYSNUM(iopl) SYSNUM(ioprio_get) SYSNUM(ioprio_set) SYSNUM(ipc) SYSNUM(kcmp) SYSNUM(kexec_load) SYSNUM(keyctl) SYSNUM(kill) SYSNUM(lchown) SYSNUM(lchown32) SYSNUM(lgetxattr) SYSNUM(link) SYSNUM(linkat) SYSNUM(listen) SYSNUM(listxattr) SYSNUM(llistxattr) SYSNUM(lock) SYSNUM(lookup_dcookie) SYSNUM(lremovexattr) SYSNUM(lseek) SYSNUM(lsetxattr) SYSNUM(lstat) SYSNUM(lstat64) SYSNUM(madvise) SYSNUM(mbind) SYSNUM(migrate_pages) SYSNUM(mincore) SYSNUM(mkdir) SYSNUM(mkdirat) SYSNUM(mknod) SYSNUM(mknodat) SYSNUM(mlock) SYSNUM(mlockall) SYSNUM(mmap) SYSNUM(mmap2) SYSNUM(modify_ldt) SYSNUM(mount) SYSNUM(move_pages) SYSNUM(mprotect) SYSNUM(mpx) SYSNUM(mq_getsetattr) SYSNUM(mq_notify) SYSNUM(mq_open) SYSNUM(mq_timedreceive) SYSNUM(mq_timedsend) SYSNUM(mq_unlink) SYSNUM(mremap) SYSNUM(msgctl) SYSNUM(msgget) SYSNUM(msgrcv) SYSNUM(msgsnd) SYSNUM(msync) SYSNUM(munlock) SYSNUM(munlockall) SYSNUM(munmap) SYSNUM(name_to_handle_at) SYSNUM(nanosleep) SYSNUM(newfstatat) SYSNUM(nfsservctl) SYSNUM(nice) SYSNUM(oldfstat) SYSNUM(oldlstat) SYSNUM(oldolduname) SYSNUM(oldstat) SYSNUM(olduname) SYSNUM(open) SYSNUM(open_by_handle_at) SYSNUM(openat) SYSNUM(pause) SYSNUM(pciconfig_iobase) SYSNUM(pciconfig_read) SYSNUM(pciconfig_write) SYSNUM(perf_event_open) SYSNUM(personality) SYSNUM(pipe) SYSNUM(pipe2) SYSNUM(pivot_root) SYSNUM(poll) SYSNUM(ppoll) SYSNUM(prctl) SYSNUM(pread64) SYSNUM(preadv) SYSNUM(prlimit64) SYSNUM(process_vm_readv) SYSNUM(process_vm_writev) SYSNUM(prof) SYSNUM(profil) SYSNUM(pselect6) SYSNUM(ptrace) SYSNUM(putpmsg) SYSNUM(pwrite64) SYSNUM(pwritev) SYSNUM(query_module) SYSNUM(quotactl) SYSNUM(read) SYSNUM(readahead) SYSNUM(readdir) SYSNUM(readlink) SYSNUM(readlinkat) SYSNUM(readv) SYSNUM(reboot) SYSNUM(recv) SYSNUM(recvfrom) SYSNUM(recvmmsg) SYSNUM(recvmsg) SYSNUM(remap_file_pages) SYSNUM(removexattr) SYSNUM(rename) SYSNUM(renameat) SYSNUM(request_key) SYSNUM(restart_syscall) SYSNUM(rmdir) SYSNUM(rt_sigaction) SYSNUM(rt_sigpending) SYSNUM(rt_sigprocmask) SYSNUM(rt_sigqueueinfo) SYSNUM(rt_sigreturn) SYSNUM(rt_sigsuspend) SYSNUM(rt_sigtimedwait) SYSNUM(rt_tgsigqueueinfo) SYSNUM(sched_get_priority_max) SYSNUM(sched_get_priority_min) SYSNUM(sched_getaffinity) SYSNUM(sched_getparam) SYSNUM(sched_getscheduler) SYSNUM(sched_rr_get_interval) SYSNUM(sched_setaffinity) SYSNUM(sched_setparam) SYSNUM(sched_setscheduler) SYSNUM(sched_yield) SYSNUM(security) SYSNUM(select) SYSNUM(semctl) SYSNUM(semget) SYSNUM(semop) SYSNUM(semtimedop) SYSNUM(send) SYSNUM(sendfile) SYSNUM(sendfile64) SYSNUM(sendmmsg) SYSNUM(sendmsg) SYSNUM(sendto) SYSNUM(set_mempolicy) SYSNUM(set_robust_list) SYSNUM(set_thread_area) SYSNUM(set_tid_address) SYSNUM(setdomainname) SYSNUM(setfsgid) SYSNUM(setfsgid32) SYSNUM(setfsuid) SYSNUM(setfsuid32) SYSNUM(setgid) SYSNUM(setgid32) SYSNUM(setgroups) SYSNUM(setgroups32) SYSNUM(sethostname) SYSNUM(setitimer) SYSNUM(setns) SYSNUM(setpgid) SYSNUM(setpriority) SYSNUM(setregid) SYSNUM(setregid32) SYSNUM(setresgid) SYSNUM(setresgid32) SYSNUM(setresuid) SYSNUM(setresuid32) SYSNUM(setreuid) SYSNUM(setreuid32) SYSNUM(setrlimit) SYSNUM(setsid) SYSNUM(setsockopt) SYSNUM(settimeofday) SYSNUM(setuid) SYSNUM(setuid32) SYSNUM(setxattr) SYSNUM(sgetmask) SYSNUM(shmat) SYSNUM(shmctl) SYSNUM(shmdt) SYSNUM(shmget) SYSNUM(shutdown) SYSNUM(sigaction) SYSNUM(sigaltstack) SYSNUM(signal) SYSNUM(signalfd) SYSNUM(signalfd4) SYSNUM(sigpending) SYSNUM(sigprocmask) SYSNUM(sigreturn) SYSNUM(sigsuspend) SYSNUM(socket) SYSNUM(socketcall) SYSNUM(socketpair) SYSNUM(splice) SYSNUM(ssetmask) SYSNUM(stat) SYSNUM(stat64) SYSNUM(statfs) SYSNUM(statfs64) SYSNUM(stime) SYSNUM(stty) SYSNUM(swapoff) SYSNUM(swapon) SYSNUM(symlink) SYSNUM(symlinkat) SYSNUM(sync) SYSNUM(sync_file_range) SYSNUM(sync_file_range2) SYSNUM(syncfs) SYSNUM(syscalls) SYSNUM(sysfs) SYSNUM(sysinfo) SYSNUM(syslog) SYSNUM(tee) SYSNUM(tgkill) SYSNUM(time) SYSNUM(timer_create) SYSNUM(timer_delete) SYSNUM(timer_getoverrun) SYSNUM(timer_gettime) SYSNUM(timer_settime) SYSNUM(timerfd_create) SYSNUM(timerfd_gettime) SYSNUM(timerfd_settime) SYSNUM(times) SYSNUM(tkill) SYSNUM(truncate) SYSNUM(truncate64) SYSNUM(tuxcall) SYSNUM(ugetrlimit) SYSNUM(ulimit) SYSNUM(umask) SYSNUM(umount) SYSNUM(umount2) SYSNUM(uname) SYSNUM(unlink) SYSNUM(unlinkat) SYSNUM(unshare) SYSNUM(uselib) SYSNUM(ustat) SYSNUM(utime) SYSNUM(utimensat) SYSNUM(utimes) SYSNUM(vfork) SYSNUM(vhangup) SYSNUM(vm86) SYSNUM(vm86old) SYSNUM(vmsplice) SYSNUM(vserver) SYSNUM(wait4) SYSNUM(waitid) SYSNUM(waitpid) SYSNUM(write) SYSNUM(writev) SYSNUM(x32_execve) SYSNUM(x32_get_robust_list) SYSNUM(x32_ioctl) SYSNUM(x32_kexec_load) SYSNUM(x32_move_pages) SYSNUM(x32_mq_notify) SYSNUM(x32_preadv) SYSNUM(x32_process_vm_readv) SYSNUM(x32_process_vm_writev) SYSNUM(x32_ptrace) SYSNUM(x32_pwritev) SYSNUM(x32_readv) SYSNUM(x32_recvfrom) SYSNUM(x32_recvmmsg) SYSNUM(x32_recvmsg) SYSNUM(x32_rt_sigaction) SYSNUM(x32_rt_sigpending) SYSNUM(x32_rt_sigqueueinfo) SYSNUM(x32_rt_sigreturn) SYSNUM(x32_rt_sigtimedwait) SYSNUM(x32_rt_tgsigqueueinfo) SYSNUM(x32_sendmmsg) SYSNUM(x32_sendmsg) SYSNUM(x32_set_robust_list) SYSNUM(x32_sigaltstack) SYSNUM(x32_timer_create) SYSNUM(x32_vmsplice) SYSNUM(x32_waitid) SYSNUM(x32_writev) care-2.2.1/src/cli/0000755000175000017500000000000012430615044013337 5ustar ivoireivoirecare-2.2.1/src/cli/care.c0000644000175000017500000002563712430615044014432 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of CARE. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* str*(3), */ #include /* assert(3), */ #include /* bzero(3), */ #include /* STAILQ_*, */ #include /* INT_MIN, */ #include /* getpid(2), close(2), */ #include /* printf(3), fflush(3), */ #include /* getcwd(2), */ #include /* errno(3), */ #include "cli/cli.h" #include "cli/notice.h" #include "path/binding.h" #include "path/temp.h" #include "extension/extension.h" #include "extension/care/care.h" #include "extension/care/extract.h" #include "attribute.h" /* These should be included last. */ #include "build.h" #include "cli/care.h" static int handle_option_o(Tracee *tracee UNUSED, const Cli *cli, char *value) { Options *options = talloc_get_type_abort(cli->private, Options); options->output = value; return 0; } static int handle_option_c(Tracee *tracee UNUSED, const Cli *cli, char *value) { Options *options = talloc_get_type_abort(cli->private, Options); Item *item = queue_item(options, &options->concealed_paths, value); return (item != NULL ? 0 : -1); } static int handle_option_r(Tracee *tracee UNUSED, const Cli *cli, char *value) { Options *options = talloc_get_type_abort(cli->private, Options); Item *item = queue_item(options, &options->revealed_paths, value); return (item != NULL ? 0 : -1); } static int handle_option_p(Tracee *tracee UNUSED, const Cli *cli, char *value) { Options *options = talloc_get_type_abort(cli->private, Options); Item *item = queue_item(options, &options->volatile_paths, value); return (item != NULL ? 0 : -1); } static int handle_option_e(Tracee *tracee UNUSED, const Cli *cli, char *value) { Options *options = talloc_get_type_abort(cli->private, Options); Item *item = queue_item(options, &options->volatile_envars, value); return (item != NULL ? 0 : -1); } static int handle_option_m(Tracee *tracee, const Cli *cli, char *value) { Options *options = talloc_get_type_abort(cli->private, Options); return parse_integer_option(tracee, &options->max_size, value, "-m"); } static int handle_option_d(Tracee *tracee UNUSED, const Cli *cli, char *value UNUSED) { Options *options = talloc_get_type_abort(cli->private, Options); options->ignore_default_config = true; return 0; } static int handle_option_v(Tracee *tracee, const Cli *cli UNUSED, char *value) { int status; status = parse_integer_option(tracee, &tracee->verbose, value, "-v"); if (status < 0) return status; global_verbose_level = tracee->verbose; return 0; } extern char WEAK _binary_licenses_start; extern char WEAK _binary_licenses_end; static int handle_option_V(Tracee *tracee UNUSED, const Cli *cli, char *value UNUSED) { size_t size; print_version(cli); printf("suitable for self-extracting archives (.bin): %s\n", #if defined(CARE_BINARY_IS_PORTABLE) "yes" #else "no" #endif ); printf("\n%s\n", cli->colophon); fflush(stdout); size = &_binary_licenses_end - &_binary_licenses_start; if (size > 0) write(1, &_binary_licenses_start, size); exit_failure = false; return -1; } static int handle_option_x(Tracee *tracee UNUSED, const Cli *cli UNUSED, char *value) { int status = extract_archive_from_file(value); exit_failure = (status < 0); return -1; } extern char WEAK _binary_manual_start; extern char WEAK _binary_manual_end; static int handle_option_h(Tracee *tracee UNUSED, const Cli *cli UNUSED, char *value UNUSED) { size_t size; size = &_binary_manual_end - &_binary_manual_start; if (size != 0) write(1, &_binary_manual_start, size); else printf("No manual found, please visit http://reproducible.io instead.\n"); exit_failure = false; return -1; } /** * Allocate a new binding for the given @tracee that will conceal the * content of @path with an empty file or directory. This function * complains about missing @host path only if @must_exist is true. */ static Binding *new_concealing_binding(Tracee *tracee, const char *path, bool must_exist) { struct stat statl; Binding *binding; const char *temp; int status; status = stat(path, &statl); if (status < 0) { if (must_exist) notice(tracee, WARNING, SYSTEM, "can't conceal %s", path); return NULL; } if (S_ISDIR(statl.st_mode)) temp = create_temp_directory(NULL, tracee->tool_name); else temp = create_temp_file(NULL, tracee->tool_name); if (temp == NULL) { notice(tracee, WARNING, INTERNAL, "can't conceal %s", path); return NULL; } binding = new_binding(tracee, temp, path, must_exist); if (binding == NULL) return NULL; return binding; } /** * Initialize @tracee's fields that are mandatory for PRoot/CARE but * that are not specifiable on the command line. */ static int pre_initialize_bindings(Tracee *tracee, const Cli *cli, size_t argc, char *const *argv, size_t cursor) { Options *options = talloc_get_type_abort(cli->private, Options); char path[PATH_MAX]; Binding *binding; const char *home; const char *pwd; Item *item; size_t i; if (cursor >= argc) { notice(tracee, ERROR, USER, "no command specified"); return -1; } options->command = &argv[cursor]; home = getenv("HOME"); pwd = getenv("PWD"); /* Set these variables to their default values (ie. when not * set), this simplifies the binding setup to the related * files. */ setenv("XAUTHORITY", talloc_asprintf(tracee->ctx, "%s/.Xauthority", home), 0); setenv("ICEAUTHORITY", talloc_asprintf(tracee->ctx, "%s/.ICEauthority", home), 0); /* Enable default option first. */ if (!options->ignore_default_config) { const char *expanded; Binding *binding; int status; /* Bind an empty file/directory over default concealed * paths. */ for (i = 0; default_concealed_paths[i] != NULL; i++) { expanded = expand_front_variable(tracee->ctx, default_concealed_paths[i]); binding = new_concealing_binding(tracee, expanded, false); if (binding != NULL) VERBOSE(tracee, 0, "concealed path: %s %s", default_concealed_paths[i], default_concealed_paths[i] != expanded ? expanded : ""); } /* Bind default revealed paths over concealed * paths. */ for (i = 0; default_revealed_paths[i] != NULL; i++) { expanded = expand_front_variable(tracee->ctx, default_revealed_paths[i]); binding = new_binding(tracee, expanded, NULL, false); if (binding != NULL) VERBOSE(tracee, 0, "revealed path: %s %s", default_revealed_paths[i], default_revealed_paths[i] != expanded ? expanded : ""); } /* Ensure the initial command is accessible. */ status = which(NULL, NULL, path, options->command[0]); if (status < 0) return -1; /* This failure was already noticed by which(). */ binding = new_binding(tracee, path, NULL, false); if (binding != NULL) VERBOSE(tracee, 0, "revealed path: %s", path); /* Sanity check. Note: it is assumed $HOME and $PWD * are canonicalized. */ if (home != NULL && pwd != NULL && strcmp(home, pwd) == 0) notice(tracee, WARNING, USER, "$HOME is implicitely revealed since it is the same as $PWD, " "change your current working directory to be sure " "your personal data will be not archivable."); /* Add the default volatile paths to the list of user * volatile paths. */ for (i = 0; default_volatile_paths[i] != NULL; i++) { Item *item; expanded = expand_front_variable(tracee->ctx, default_volatile_paths[i]); item = queue_item(tracee, &options->volatile_paths, expanded); /* Remember the non expanded form, later used * by archive_re_execute_sh(). */ if (item != NULL && expanded != default_volatile_paths[i]) talloc_set_name_const(item, default_volatile_paths[i]); } for (i = 0; default_volatile_envars[i] != NULL; i++) queue_item(tracee, &options->volatile_envars, default_volatile_envars[i]); if (options->max_size == INT_MIN) options->max_size = CARE_MAX_SIZE; } else if (options->max_size == INT_MIN) options->max_size = -1; /* Unlimited. */ VERBOSE(tracee, 1, "max size: %d", options->max_size); /* Bind an empty file/directory over user concealed paths. */ if (options->concealed_paths != NULL) { STAILQ_FOREACH(item, options->concealed_paths, link) { binding = new_concealing_binding(tracee, item->load, true); if (binding != NULL) VERBOSE(tracee, 0, "concealed path: %s", (char *) item->load); } } /* Bind user revealed paths over concealed paths. */ if (options->revealed_paths != NULL) { STAILQ_FOREACH(item, options->revealed_paths, link) { binding = new_binding(tracee, item->load, NULL, true); if (binding != NULL) VERBOSE(tracee, 0, "revealed path: %s", (char *) item->load); } } /* Bind volatile paths over concealed paths. */ if (options->volatile_paths != NULL) { STAILQ_FOREACH(item, options->volatile_paths, link) { binding = new_binding(tracee, item->load, NULL, false); if (binding != NULL) VERBOSE(tracee, 1, "volatile path: %s", (char *) item->load); } } VERBOSE(tracee, 0, "----------------------------------------------------------------------"); /* Initialize @tracee->fs->cwd with a path already canonicalized * as required by care.c:handle_initialization(). */ if (getcwd(path, PATH_MAX) == NULL) { notice(tracee, ERROR, SYSTEM, "can't get current working directory"); return -1; } tracee->fs->cwd = talloc_strdup(tracee->fs, path); if (tracee->fs->cwd == NULL) return -1; talloc_set_name_const(tracee->fs->cwd, "$cwd"); /* Initialize @tracee's root (required by PRoot). */ binding = new_binding(tracee, "/", "/", true); if (binding == NULL) return -1; return cursor; } /** * Initialize CARE extensions. */ static int post_initialize_bindings(Tracee *tracee, const Cli *cli, size_t argc UNUSED, char *const *argv UNUSED, size_t cursor) { Options *options = talloc_get_type_abort(cli->private, Options); int status; status = initialize_extension(tracee, care_callback, (void *) options); if (status < 0) { notice(tracee, WARNING, INTERNAL, "can't initialize the care extension"); return -1; } return cursor; } const Cli *get_care_cli(TALLOC_CTX *context) { Options *options; global_tool_name = care_cli.name; options = talloc_zero(context, Options); if (options == NULL) return NULL; options->max_size = INT_MIN; care_cli.private = options; return &care_cli; } care-2.2.1/src/cli/care.h0000644000175000017500000001443712430615044014433 0ustar ivoireivoire/* This file is automatically generated from the documentation. EDIT AT YOUR OWN RISK. */ #ifndef CARE_CLI_H #define CARE_CLI_H #include "cli/cli.h" #ifndef VERSION #define VERSION "2.2.1" #endif #define CARE_MAX_SIZE 1024 static char const *default_concealed_paths[] = { "$HOME", "/tmp", NULL, }; static char const *default_revealed_paths[] = { "$PWD", NULL, }; static char const *default_volatile_paths[] = { "/dev", "/proc", "/sys", "/run/shm", "/tmp/.X11-unix", "/tmp/.ICE-unix", "$XAUTHORITY", "$ICEAUTHORITY", "/var/run/dbus/system_bus_socket", "/var/tmp/kdecache-$LOGNAME", NULL, }; static char const *default_volatile_envars[] = { "DISPLAY", "http_proxy", "https_proxy", "ftp_proxy", "all_proxy", "HTTP_PROXY", "HTTPS_PROXY", "FTP_PROXY", "ALL_PROXY", "DBUS_SESSION_BUS_ADDRESS", "SESSION_MANAGER", "XDG_SESSION_COOKIE", NULL, }; static int handle_option_o(Tracee *tracee, const Cli *cli, char *value); static int handle_option_c(Tracee *tracee, const Cli *cli, char *value); static int handle_option_r(Tracee *tracee, const Cli *cli, char *value); static int handle_option_p(Tracee *tracee, const Cli *cli, char *value); static int handle_option_e(Tracee *tracee, const Cli *cli, char *value); static int handle_option_m(Tracee *tracee, const Cli *cli, char *value); static int handle_option_d(Tracee *tracee, const Cli *cli, char *value); static int handle_option_v(Tracee *tracee, const Cli *cli, char *value); static int handle_option_V(Tracee *tracee, const Cli *cli, char *value); static int handle_option_x(Tracee *tracee, const Cli *cli, char *value); static int handle_option_h(Tracee *tracee, const Cli *cli, char *value); static int pre_initialize_bindings(Tracee *, const Cli *, size_t, char *const *, size_t); static int post_initialize_bindings(Tracee *, const Cli *, size_t, char *const *, size_t); static Cli care_cli = { .version = VERSION, .name = "care", .subtitle = "Comprehensive Archiver for Reproducible Execution", .synopsis = "care [option] ... command", .colophon = "Visit http://reproducible.io for help, bug reports, suggestions, patches, ...\n\ Copyright (C) 2014 STMicroelectronics, licensed under GPL v2 or later.", .logo = "\ _____ ____ _____ ____\n\ / __/ __ | __ \\ __|\n\ / /_/ | / __|\n\ \\_____|__|__|__|__\\____|", .pre_initialize_bindings = pre_initialize_bindings, .post_initialize_bindings = post_initialize_bindings, .options = { { .class = "Options", .arguments = { { .name = "-o", .separator = ' ', .value = "path" }, { .name = "--output", .separator = '=', .value = "path" }, { .name = NULL, .separator = '\0', .value = NULL } }, .handler = handle_option_o, .description = "Archive in *path*, its suffix specifies the format.", .detail = NULL, }, { .class = "Options", .arguments = { { .name = "-c", .separator = ' ', .value = "path" }, { .name = "--concealed-path", .separator = '=', .value = "path" }, { .name = NULL, .separator = '\0', .value = NULL } }, .handler = handle_option_c, .description = "Make *path* content appear empty during the original execution.", .detail = NULL, }, { .class = "Options", .arguments = { { .name = "-r", .separator = ' ', .value = "path" }, { .name = "--revealed-path", .separator = '=', .value = "path" }, { .name = NULL, .separator = '\0', .value = NULL } }, .handler = handle_option_r, .description = "Make *path* content accessible when nested in a concealed path.", .detail = NULL, }, { .class = "Options", .arguments = { { .name = "-p", .separator = ' ', .value = "path" }, { .name = "--volatile-path", .separator = '=', .value = "path" }, { .name = NULL, .separator = '\0', .value = NULL } }, .handler = handle_option_p, .description = "Don't archive *path* content, reuse actual *path* instead.", .detail = NULL, }, { .class = "Options", .arguments = { { .name = "-e", .separator = ' ', .value = "name" }, { .name = "--volatile-env", .separator = '=', .value = "name" }, { .name = NULL, .separator = '\0', .value = NULL } }, .handler = handle_option_e, .description = "Don't archive *name* env. variable, reuse actual value instead.", .detail = NULL, }, { .class = "Options", .arguments = { { .name = "-m", .separator = ' ', .value = "value" }, { .name = "--max-archivable-size", .separator = '=', .value = "value" }, { .name = NULL, .separator = '\0', .value = NULL } }, .handler = handle_option_m, .description = "Set the maximum size of archivable files to *value* megabytes.", .detail = NULL, }, { .class = "Options", .arguments = { { .name = "-d", .separator = '\0', .value = NULL }, { .name = "--ignore-default-config", .separator = '\0', .value = NULL }, { .name = NULL, .separator = '\0', .value = NULL } }, .handler = handle_option_d, .description = "Don't use the default options.", .detail = NULL, }, { .class = "Options", .arguments = { { .name = "-v", .separator = ' ', .value = "value" }, { .name = "--verbose", .separator = '=', .value = "value" }, { .name = NULL, .separator = '\0', .value = NULL } }, .handler = handle_option_v, .description = "Set the level of debug information to *value*.", .detail = NULL, }, { .class = "Options", .arguments = { { .name = "-V", .separator = '\0', .value = NULL }, { .name = "--version", .separator = '\0', .value = NULL }, { .name = "--about", .separator = '\0', .value = NULL }, { .name = NULL, .separator = '\0', .value = NULL } }, .handler = handle_option_V, .description = "Print version, copyright, license and contact, then exit.", .detail = NULL, }, { .class = "Options", .arguments = { { .name = "-x", .separator = ' ', .value = "file" }, { .name = "--extract", .separator = '=', .value = "file" }, { .name = NULL, .separator = '\0', .value = NULL } }, .handler = handle_option_x, .description = "Extract content of the archive *file*, then exit.", .detail = NULL, }, { .class = "Options", .arguments = { { .name = "-h", .separator = '\0', .value = NULL }, { .name = "--help", .separator = '\0', .value = NULL }, { .name = "--usage", .separator = '\0', .value = NULL }, { .name = NULL, .separator = '\0', .value = NULL } }, .handler = handle_option_h, .description = "Print the user manual, then exit.", .detail = NULL, }, END_OF_OPTIONS, }, }; #endif /* CARE_CLI_H */ care-2.2.1/src/cli/cli.c0000644000175000017500000003557312430615044014267 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* printf(3), */ #include /* bool, true, false, */ #include /* ARG_MAX, PATH_MAX, */ #include /* str*(3), basename(3), */ #include /* talloc*, */ #include /* exit(3), EXIT_*, strtol(3), {g,s}etenv(3), */ #include /* assert(3), */ #include /* getpid(2), */ #include /* getpid(2), */ #include /* errno(3), */ #include /* backtrace_symbols(3), */ #include /* INT_MAX, */ #include "cli/cli.h" #include "cli/notice.h" #include "extension/care/extract.h" #include "extension/extension.h" #include "tracee/tracee.h" #include "tracee/event.h" #include "path/binding.h" #include "path/canon.h" #include "path/path.h" #include "build.h" /** * Print a (@detailed) usage of PRoot. */ void print_usage(Tracee *tracee, const Cli *cli, bool detailed) { const char *current_class = "none"; const Option *options; size_t i, j; #define DETAIL(a) if (detailed) a DETAIL(printf("%s %s: %s.\n\n", cli->name, cli->version, cli->subtitle)); printf("Usage:\n %s\n", cli->synopsis); DETAIL(printf("\n")); options = cli->options; for (i = 0; options[i].class != NULL; i++) { for (j = 0; ; j++) { const Argument *argument = &(options[i].arguments[j]); if (!argument->name || (!detailed && j != 0)) { DETAIL(printf("\n")); printf("\t%s\n", options[i].description); if (detailed) { if (options[i].detail[0] != '\0') printf("\n%s\n\n", options[i].detail); else printf("\n"); } break; } if (strcmp(options[i].class, current_class) != 0) { current_class = options[i].class; printf("\n%s:\n", current_class); } if (j == 0) printf(" %s", argument->name); else printf(", %s", argument->name); if (argument->separator != '\0') printf("%c*%s*", argument->separator, argument->value); else if (!detailed) printf("\t"); } } notify_extensions(tracee, PRINT_USAGE, detailed, 0); if (detailed) printf("%s\n", cli->colophon); } /** * Print the version of PRoot. */ void print_version(const Cli *cli) { printf("%s %s\n\n", cli->logo, cli->version); printf("built-in accelerators: process_vm = %s, seccomp_filter = %s\n", #if defined(HAVE_PROCESS_VM) "yes", #else "no", #endif #if defined(HAVE_SECCOMP_FILTER) "yes" #else "no" #endif ); } static void print_execve_help(const Tracee *tracee, const char *argv0, int status) { notice(tracee, ERROR, SYSTEM, "execve(\"%s\")", argv0); /* Ubuntu kernel bug? */ if (status == -EPERM && getenv("PROOT_NO_SECCOMP") == NULL) { notice(tracee, INFO, USER, "It seems your kernel contains this bug: https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1202161\n" "To workaround it, set the env. variable PROOT_NO_SECCOMP to 1."); return; } notice(tracee, INFO, USER, "possible causes:\n" " * is a script but its interpreter (eg. /bin/sh) was not found;\n" " * is an ELF but its interpreter (eg. ld-linux.so) was not found;\n" " * is a foreign binary but no was specified;\n" " * does not work correctly (if specified)."); } static void print_error_separator(const Tracee *tracee, const Argument *argument) { if (argument->separator == '\0') notice(tracee, ERROR, USER, "option '%s' expects no value.", argument->name); else notice(tracee, ERROR, USER, "option '%s' and its value must be separated by '%c'.", argument->name, argument->separator); } static void print_argv(const Tracee *tracee, const char *prompt, char **argv) { char string[ARG_MAX] = ""; size_t i; if (!argv) return; #define APPEND(post) \ do { \ ssize_t length = sizeof(string) - (strlen(string) + strlen(post)); \ if (length <= 0) \ return; \ strncat(string, post, length); \ } while (0) APPEND(prompt); APPEND(" ="); for (i = 0; argv[i] != NULL; i++) { APPEND(" "); APPEND(argv[i]); } string[sizeof(string) - 1] = '\0'; #undef APPEND notice(tracee, INFO, USER, "%s", string); } static void print_config(Tracee *tracee) { assert(tracee != NULL); if (tracee->verbose <= 0) return; if (tracee->qemu) notice(tracee, INFO, USER, "host rootfs = %s", HOST_ROOTFS); if (tracee->glue) notice(tracee, INFO, USER, "glue rootfs = %s", tracee->glue); notice(tracee, INFO, USER, "exe = %s", tracee->exe); print_argv(tracee, "command", tracee->cmdline); print_argv(tracee, "qemu", tracee->qemu); notice(tracee, INFO, USER, "initial cwd = %s", tracee->fs->cwd); notice(tracee, INFO, USER, "verbose level = %d", tracee->verbose); notify_extensions(tracee, PRINT_CONFIG, 0, 0); } /** * Initialize @tracee's current working directory. This function * returns -1 if an error occurred, otherwise 0. */ static int initialize_cwd(Tracee *tracee) { char path2[PATH_MAX]; char path[PATH_MAX]; int status; /* Compute the base directory. */ if (tracee->fs->cwd[0] != '/') { status = getcwd2(tracee->reconf.tracee, path); if (status < 0) { notice(tracee, ERROR, INTERNAL, "getcwd: %s", strerror(-status)); return -1; } } else strcpy(path, "/"); /* The ending "." ensures canonicalize() will report an error * if tracee->fs->cwd does not exist or if it is not a * directory. */ status = join_paths(3, path2, path, tracee->fs->cwd, "."); if (status < 0) { notice(tracee, ERROR, INTERNAL, "getcwd: %s", strerror(-status)); return -1; } /* Initiale state for canonicalization. */ strcpy(path, "/"); status = canonicalize(tracee, path2, true, path, 0); if (status < 0) { notice(tracee, WARNING, USER, "can't chdir(\"%s\") in the guest rootfs: %s", path2, strerror(-status)); notice(tracee, INFO, USER, "default working directory is now \"/\""); strcpy(path, "/"); } chop_finality(path); /* Replace with the canonicalized working directory. */ TALLOC_FREE(tracee->fs->cwd); tracee->fs->cwd = talloc_strdup(tracee->fs, path); if (tracee->fs->cwd == NULL) return -1; talloc_set_name_const(tracee->fs->cwd, "$cwd"); /* Keep this special environment variable consistent. */ setenv("PWD", path, 1); return 0; } /** * Initialize @tracee->exe and @tracee->cmdline from @cmdline. */ static int initialize_command(Tracee *tracee, char *const *cmdline) { char path[PATH_MAX]; int status; int i; /* How many arguments? */ for (i = 0; cmdline[i] != NULL; i++) ; tracee->cmdline = talloc_zero_array(tracee, char *, i + 1); if (tracee->cmdline == NULL) { notice(tracee, ERROR, INTERNAL, "talloc_zero_array() failed"); return -1; } talloc_set_name_const(tracee->cmdline, "@cmdline"); for (i = 0; cmdline[i] != NULL; i++) tracee->cmdline[i] = cmdline[i]; /* Resolve the full guest path to tracee->cmdline[0]. */ status = which(tracee, tracee->reconf.paths, path, tracee->cmdline[0]); if (status < 0) return -1; status = detranslate_path(tracee, path, NULL); if (status < 0) return -1; tracee->exe = talloc_strdup(tracee, path); if (tracee->exe == NULL) return -1; talloc_set_name_const(tracee->exe, "$exe"); return 0; } /** * Configure @tracee according to the command-line arguments stored in * @argv[]. This function returns -1 if an error occured, otherwise * 0. */ int parse_config(Tracee *tracee, size_t argc, char *argv[]) { char *const default_command[] = { "/bin/sh", NULL }; option_handler_t handler = NULL; const Option *options; const Cli *cli = NULL; size_t i, j, k; int status; if (get_care_cli != NULL) { /* Check if it's an self-extracting CARE archive. */ status = extract_archive_from_file("/proc/self/exe"); if (status == 0) { /* Yes it is, nothing more to do. */ exit_failure = 0; return -1; } /* Check if it's a valid CARE tool name. */ if (strncasecmp(basename(argv[0]), "care", strlen("care")) == 0) cli = get_care_cli(tracee->ctx); } /* Unknown tool name? Default to PRoot. */ if (cli == NULL) cli = get_proot_cli(tracee->ctx); tracee->tool_name = cli->name; if (argc == 1) { print_usage(tracee, cli, false); return -1; } for (i = 1; i < argc; i++) { char *arg = argv[i]; /* The current argument is the value of a short option. */ if (handler != NULL) { status = handler(tracee, cli, arg); if (status < 0) return status; handler = NULL; continue; } if (arg[0] != '-') break; /* End of PRoot options. */ options = cli->options; for (j = 0; options[j].class != NULL; j++) { const Option *option = &options[j]; /* A given option has several aliases. */ for (k = 0; ; k++) { const Argument *argument; size_t length; argument = &option->arguments[k]; /* End of aliases for this option. */ if (!argument->name) break; length = strlen(argument->name); if (strncmp(arg, argument->name, length) != 0) continue; /* Avoid ambiguities. */ if (strlen(arg) > length && arg[length] != argument->separator) { print_error_separator(tracee, argument); return -1; } /* No option value. */ if (!argument->value) { status = option->handler(tracee, cli, NULL); if (status < 0) return -1; goto known_option; } /* Value coalesced with to its option. */ if (argument->separator == arg[length]) { assert(strlen(arg) >= length); status = option->handler(tracee, cli, &arg[length + 1]); if (status < 0) return -1; goto known_option; } /* Avoid ambiguities. */ if (argument->separator != ' ') { print_error_separator(tracee, argument); return -1; } /* Short option with a separated value. */ handler = option->handler; goto known_option; } } notice(tracee, ERROR, USER, "unknown option '%s'.", arg); return -1; known_option: if (handler != NULL && i == argc - 1) { notice(tracee, ERROR, USER, "missing value for option '%s'.", arg); return -1; } } #define HOOK_CONFIG(callback) \ do { \ if (cli->callback != NULL) { \ status = cli->callback(tracee, cli, argc, argv, i); \ if (status < 0) \ return -1; \ i = status; \ } \ } while (0) HOOK_CONFIG(pre_initialize_bindings); /* The guest rootfs is now known: bindings specified by the * user (tracee->bindings.user) can be canonicalized. */ status = initialize_bindings(tracee); if (status < 0) return -1; HOOK_CONFIG(post_initialize_bindings); HOOK_CONFIG(pre_initialize_cwd); /* Bindings are now installed (tracee->bindings.guest & * tracee->bindings.host): the current working directory can * be canonicalized. */ status = initialize_cwd(tracee); if (status < 0) return -1; HOOK_CONFIG(post_initialize_cwd); HOOK_CONFIG(pre_initialize_command); /* Bindings are now installed and the current working * directory is canonicalized: resolve path to @tracee->exe * and configure @tracee->cmdline. */ status = initialize_command(tracee, i < argc ? &argv[i] : default_command); if (status < 0) return -1; HOOK_CONFIG(post_initialize_command); #undef HOOK_CONFIG print_config(tracee); return 0; } bool exit_failure = true; int main(int argc, char *argv[]) { Tracee *tracee; int status; /* Configure the memory allocator. */ talloc_enable_leak_report(); #if defined(TALLOC_VERSION_MAJOR) && TALLOC_VERSION_MAJOR >= 2 talloc_set_log_stderr(); #endif /* Pre-create the first tracee (pid == 0). */ tracee = get_tracee(NULL, 0, true); if (tracee == NULL) goto error; tracee->pid = getpid(); /* Pre-configure the first tracee. */ status = parse_config(tracee, argc, argv); if (status < 0) goto error; /* Start the first tracee. */ status = launch_process(tracee); if (status < 0) { print_execve_help(tracee, tracee->exe, status); goto error; } /* Start tracing the first tracee and all its children. */ exit(event_loop()); error: TALLOC_FREE(tracee); if (exit_failure) { fprintf(stderr, "fatal error: see `%s --help`.\n", basename(argv[0])); exit(EXIT_FAILURE); } else exit(EXIT_SUCCESS); } /** * Convert @value into an integer, then put the result into * *@variable. This function prints a warning and returns -1 if a * conversion error occured, otherwise it returns 0. */ int parse_integer_option(const Tracee *tracee, int *variable, const char *value, const char *option) { char *end_ptr = NULL; errno = 0; *variable = strtol(value, &end_ptr, 10); if (errno != 0 || end_ptr == value) { notice(tracee, ERROR, USER, "option `%s` expects an integer value.", option); return -1; } return 0; } /** * Expand the environment variable in front of @string, if any. For * example, this function can expand "$HOME" or "$HOME/.ICEauthority". */ const char *expand_front_variable(TALLOC_CTX *context, const char *string) { const char *suffix; char *expanded; ptrdiff_t size; if (string[0] != '$') return string; suffix = strchr(string, '/'); if (suffix == NULL) return (getenv(&string[1]) ?: string); size = suffix - string; if (size <= 1) return string; expanded = talloc_strndup(context, &string[1], size - 1); if (expanded == NULL) return string; expanded = getenv(expanded); if (expanded == NULL) return string; expanded = talloc_asprintf(context, "%s%s", expanded, suffix); if (expanded == NULL) return string; return expanded; } /* Here follows the support for GCC function instrumentation. Build * with CFLAGS='-finstrument-functions -O0 -g' and LDFLAGS='-rdynamic' * to enable this mechanism. */ static int indent_level = 0; void __cyg_profile_func_enter(void *this_function, void *call_site) DONT_INSTRUMENT; void __cyg_profile_func_enter(void *this_function, void *call_site) { void *const pointers[] = { this_function, call_site }; char **symbols = NULL; symbols = backtrace_symbols(pointers, 2); if (symbols == NULL) goto end; fprintf(stderr, "%*s from %s\n", (int) strlen(symbols[0]) + indent_level, symbols[0], symbols[1]); end: if (symbols != NULL) free(symbols); if (indent_level < INT_MAX) indent_level++; } void __cyg_profile_func_exit(void *this_function UNUSED, void *call_site UNUSED) DONT_INSTRUMENT; void __cyg_profile_func_exit(void *this_function UNUSED, void *call_site UNUSED) { if (indent_level > 0) indent_level--; } care-2.2.1/src/cli/proot.c0000644000175000017500000001756312430615044014662 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* str*(3), */ #include /* assert(3), */ #include /* printf(3), fflush(3), */ #include /* write(2), */ #include "cli/cli.h" #include "cli/notice.h" #include "extension/extension.h" #include "path/binding.h" #include "attribute.h" /* These should be included last. */ #include "build.h" #include "cli/proot.h" static int handle_option_r(Tracee *tracee, const Cli *cli UNUSED, char *value) { Binding *binding; /* ``chroot $PATH`` is semantically equivalent to ``mount * --bind $PATH /``. */ binding = new_binding(tracee, value, "/", true); if (binding == NULL) return -1; return 0; } static int handle_option_b(Tracee *tracee, const Cli *cli UNUSED, char *value) { char *host; char *guest; host = talloc_strdup(tracee->ctx, value); if (host == NULL) { notice(tracee, ERROR, INTERNAL, "can't allocate memory"); return -1; } guest = strchr(host, ':'); if (guest != NULL) { *guest = '\0'; guest++; } new_binding(tracee, host, guest, true); return 0; } static int handle_option_q(Tracee *tracee, const Cli *cli UNUSED, char *value) { size_t nb_args; char *ptr; size_t i; nb_args = 0; ptr = value; while (1) { nb_args++; /* Keep consecutive non-space characters. */ while (*ptr != ' ' && *ptr != '\0') ptr++; /* End-of-string ? */ if (*ptr == '\0') break; /* Skip consecutive space separators. */ while (*ptr == ' ' && *ptr != '\0') ptr++; /* End-of-string ? */ if (*ptr == '\0') break; } tracee->qemu = talloc_zero_array(tracee, char *, nb_args + 1); if (tracee->qemu == NULL) return -1; talloc_set_name_const(tracee->qemu, "@qemu"); i = 0; ptr = value; while (1) { tracee->qemu[i] = ptr; i++; /* Keep consecutive non-space characters. */ while (*ptr != ' ' && *ptr != '\0') ptr++; /* End-of-string ? */ if (*ptr == '\0') break; /* Remove consecutive space separators. */ while (*ptr == ' ' && *ptr != '\0') *ptr++ = '\0'; /* End-of-string ? */ if (*ptr == '\0') break; } assert(i == nb_args); new_binding(tracee, "/", HOST_ROOTFS, true); new_binding(tracee, "/dev/null", "/etc/ld.so.preload", false); return 0; } static int handle_option_w(Tracee *tracee, const Cli *cli UNUSED, char *value) { tracee->fs->cwd = talloc_strdup(tracee->fs, value); if (tracee->fs->cwd == NULL) return -1; talloc_set_name_const(tracee->fs->cwd, "$cwd"); return 0; } static int handle_option_k(Tracee *tracee, const Cli *cli UNUSED, char *value) { int status; status = initialize_extension(tracee, kompat_callback, value); if (status < 0) notice(tracee, WARNING, INTERNAL, "option \"-k %s\" discarded", value); return 0; } static int handle_option_i(Tracee *tracee, const Cli *cli UNUSED, char *value) { (void) initialize_extension(tracee, fake_id0_callback, value); return 0; } static int handle_option_0(Tracee *tracee, const Cli *cli, char *value UNUSED) { return handle_option_i(tracee, cli, "0:0"); } static int handle_option_v(Tracee *tracee, const Cli *cli UNUSED, char *value) { int status; status = parse_integer_option(tracee, &tracee->verbose, value, "-v"); if (status < 0) return status; global_verbose_level = tracee->verbose; return 0; } extern char WEAK _binary_licenses_start; extern char WEAK _binary_licenses_end; static int handle_option_V(Tracee *tracee UNUSED, const Cli *cli, char *value UNUSED) { size_t size; print_version(cli); printf("\n%s\n", cli->colophon); fflush(stdout); size = &_binary_licenses_end - &_binary_licenses_start; if (size > 0) write(1, &_binary_licenses_start, size); exit_failure = false; return -1; } static int handle_option_h(Tracee *tracee, const Cli *cli, char *value UNUSED) { print_usage(tracee, cli, true); exit_failure = false; return -1; } static void new_bindings(Tracee *tracee, const char *bindings[], const char *value) { int i; for (i = 0; bindings[i] != NULL; i++) { const char *path; path = (strcmp(bindings[i], "*path*") != 0 ? expand_front_variable(tracee->ctx, bindings[i]) : value); new_binding(tracee, path, NULL, false); } } static int handle_option_R(Tracee *tracee, const Cli *cli, char *value) { int status; status = handle_option_r(tracee, cli, value); if (status < 0) return status; new_bindings(tracee, recommended_bindings, value); return 0; } static int handle_option_S(Tracee *tracee, const Cli *cli, char *value) { int status; status = handle_option_0(tracee, cli, value); if (status < 0) return status; status = handle_option_r(tracee, cli, value); if (status < 0) return status; new_bindings(tracee, recommended_su_bindings, value); return 0; } /** * Initialize @tracee->qemu. */ static int post_initialize_command(Tracee *tracee, const Cli *cli UNUSED, size_t argc UNUSED, char *const *argv UNUSED, size_t cursor UNUSED) { char path[PATH_MAX]; int status; /* Nothing else to do ? */ if (tracee->qemu == NULL) return 0; /* Resolve the full guest path to tracee->qemu[0]. */ status = which(tracee->reconf.tracee, tracee->reconf.paths, path, tracee->qemu[0]); if (status < 0) return -1; /* Actually tracee->qemu[0] has to be a host path from the tracee's * point-of-view, not from the PRoot's point-of-view. See * translate_execve() for details. */ if (tracee->reconf.tracee != NULL) { status = detranslate_path(tracee->reconf.tracee, path, NULL); if (status < 0) return -1; } tracee->qemu[0] = talloc_strdup(tracee->qemu, path); if (tracee->qemu[0] == NULL) return -1; /** * There's a bug when using the ELF interpreter as a loader (as PRoot * does) on PIE programs that uses constructors (typically QEMU v1.1+). * In this case, constructors are called twice as you can see on the * test below: * * $ cat test.c * static void __attribute__((constructor)) init(void) { puts("OK"); } * int main() { return 0; } * * $ gcc -fPIC -pie test.c -o test * $ ./test * OK * * $ /lib64/ld-linux-x86-64.so.2 ./test * OK * OK * * Actually, PRoot doesn't have to use the ELF interpreter as a loader * if QEMU isn't nested. When QEMU is nested (sub reconfiguration), the * user has to use either a version of QEMU prior v1.1 or a version of * QEMU compiled with the --disable-pie option. */ tracee->qemu_pie_workaround = (tracee->reconf.tracee == NULL); return 0; } /** * Initialize @tracee's fields that are mandatory for PRoot but that * are not required on the command line, i.e. "-w" and "-r". */ static int pre_initialize_bindings(Tracee *tracee, const Cli *cli, size_t argc UNUSED, char *const *argv UNUSED, size_t cursor) { int status; /* Default to "." if no CWD were specified. */ if (tracee->fs->cwd == NULL) { status = handle_option_w(tracee, cli, "."); if (status < 0) return -1; } /* The default guest rootfs is "/" if none was specified. */ if (get_root(tracee) == NULL) { status = handle_option_r(tracee, cli, "/"); if (status < 0) return -1; } return cursor; } const Cli *get_proot_cli(TALLOC_CTX *context UNUSED) { global_tool_name = proot_cli.name; return &proot_cli; } care-2.2.1/src/cli/notice.h0000644000175000017500000000300512430615044014767 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef NOTICE_H #define NOTICE_H #include "tracee/tracee.h" #include "attribute.h" /* Specify where a notice is coming from. */ typedef enum { SYSTEM, INTERNAL, USER, TALLOC, } Origin; /* Specify the severity of a notice. */ typedef enum { ERROR, WARNING, INFO, } Severity; #define VERBOSE(tracee, level, message, args...) do { \ if (tracee == NULL || tracee->verbose >= (level)) \ notice(tracee, INFO, INTERNAL, (message), ## args); \ } while (0) extern void notice(const Tracee *tracee, Severity severity, Origin origin, const char *message, ...) FORMAT(printf, 4, 5); extern int global_verbose_level; extern const char *global_tool_name; #endif /* NOTICE_H */ care-2.2.1/src/cli/proot.h0000644000175000017500000002542112430615044014657 0ustar ivoireivoire/* This file is automatically generated from the documentation. EDIT AT YOUR OWN RISK. */ #ifndef PROOT_CLI_H #define PROOT_CLI_H #include "cli/cli.h" #ifndef VERSION #define VERSION "4.0.3" #endif static const char *recommended_bindings[] = { "/etc/host.conf", "/etc/hosts", "/etc/hosts.equiv", "/etc/mtab", "/etc/netgroup", "/etc/networks", "/etc/passwd", "/etc/group", "/etc/nsswitch.conf", "/etc/resolv.conf", "/etc/localtime", "/dev/", "/sys/", "/proc/", "/tmp/", "$HOME", "*path*", NULL, }; static const char *recommended_su_bindings[] = { "/etc/host.conf", "/etc/hosts", "/etc/nsswitch.conf", "/etc/resolv.conf", "/dev/", "/sys/", "/proc/", "/tmp/", "$HOME", "*path*", NULL, }; static int handle_option_r(Tracee *tracee, const Cli *cli, char *value); static int handle_option_b(Tracee *tracee, const Cli *cli, char *value); static int handle_option_q(Tracee *tracee, const Cli *cli, char *value); static int handle_option_w(Tracee *tracee, const Cli *cli, char *value); static int handle_option_v(Tracee *tracee, const Cli *cli, char *value); static int handle_option_V(Tracee *tracee, const Cli *cli, char *value); static int handle_option_h(Tracee *tracee, const Cli *cli, char *value); static int handle_option_k(Tracee *tracee, const Cli *cli, char *value); static int handle_option_0(Tracee *tracee, const Cli *cli, char *value); static int handle_option_i(Tracee *tracee, const Cli *cli, char *value); static int handle_option_R(Tracee *tracee, const Cli *cli, char *value); static int handle_option_S(Tracee *tracee, const Cli *cli, char *value); static int pre_initialize_bindings(Tracee *, const Cli *, size_t, char *const *, size_t); static int post_initialize_command(Tracee *, const Cli *, size_t, char *const *, size_t); static Cli proot_cli = { .version = VERSION, .name = "proot", .subtitle = "chroot, mount --bind, and binfmt_misc without privilege/setup", .synopsis = "proot [option] ... [command]", .colophon = "Visit http://proot.me for help, bug reports, suggestions, patchs, ...\n\ Copyright (C) 2014 STMicroelectronics, licensed under GPL v2 or later.", .logo = "\ _____ _____ ___\n\ | __ \\ __ \\_____ _____| |_\n\ | __/ / _ \\/ _ \\ _|\n\ |__| |__|__\\_____/\\_____/\\____|", .pre_initialize_bindings = pre_initialize_bindings, .post_initialize_command = post_initialize_command, .options = { { .class = "Regular options", .arguments = { { .name = "-r", .separator = ' ', .value = "path" }, { .name = "--rootfs", .separator = '=', .value = "path" }, { .name = NULL, .separator = '\0', .value = NULL } }, .handler = handle_option_r, .description = "Use *path* as the new guest root file-system, default is /.", .detail = "\tThe specified path typically contains a Linux distribution where\n\ \tall new programs will be confined. The default rootfs is /\n\ \twhen none is specified, this makes sense when the bind mechanism\n\ \tis used to relocate host files and directories, see the -b\n\ \toption and the Examples section for details.\n\ \t\n\ \tIt is recommended to use the -R or -S options instead.", }, { .class = "Regular options", .arguments = { { .name = "-b", .separator = ' ', .value = "path" }, { .name = "--bind", .separator = '=', .value = "path" }, { .name = "-m", .separator = ' ', .value = "path" }, { .name = "--mount", .separator = '=', .value = "path" }, { .name = NULL, .separator = '\0', .value = NULL } }, .handler = handle_option_b, .description = "Make the content of *path* accessible in the guest rootfs.", .detail = "\tThis option makes any file or directory of the host rootfs\n\ \taccessible in the confined environment just as if it were part of\n\ \tthe guest rootfs. By default the host path is bound to the same\n\ \tpath in the guest rootfs but users can specify any other location\n\ \twith the syntax: -b *host_path*:*guest_location*. If the\n\ \tguest location is a symbolic link, it is dereferenced to ensure\n\ \tthe new content is accessible through all the symbolic links that\n\ \tpoint to the overlaid content. In most cases this default\n\ \tbehavior shouldn't be a problem, although it is possible to\n\ \texplicitly not dereference the guest location by appending it the\n\ \t! character: -b *host_path*:*guest_location!*.", }, { .class = "Regular options", .arguments = { { .name = "-q", .separator = ' ', .value = "command" }, { .name = "--qemu", .separator = '=', .value = "command" }, { .name = NULL, .separator = '\0', .value = NULL } }, .handler = handle_option_q, .description = "Execute guest programs through QEMU as specified by *command*.", .detail = "\tEach time a guest program is going to be executed, PRoot inserts\n\ \tthe QEMU user-mode command in front of the initial request.\n\ \tThat way, guest programs actually run on a virtual guest CPU\n\ \temulated by QEMU user-mode. The native execution of host programs\n\ \tis still effective and the whole host rootfs is bound to\n\ \t/host-rootfs in the guest environment.", }, { .class = "Regular options", .arguments = { { .name = "-w", .separator = ' ', .value = "path" }, { .name = "--pwd", .separator = '=', .value = "path" }, { .name = "--cwd", .separator = '=', .value = "path" }, { .name = NULL, .separator = '\0', .value = NULL } }, .handler = handle_option_w, .description = "Set the initial working directory to *path*.", .detail = "\tSome programs expect to be launched from a given directory but do\n\ \tnot perform any chdir by themselves. This option avoids the\n\ \tneed for running a shell and then entering the directory manually.", }, { .class = "Regular options", .arguments = { { .name = "-v", .separator = ' ', .value = "value" }, { .name = "--verbose", .separator = '=', .value = "value" }, { .name = NULL, .separator = '\0', .value = NULL } }, .handler = handle_option_v, .description = "Set the level of debug information to *value*.", .detail = "\tThe higher the integer value is, the more detailed debug\n\ \tinformation is printed to the standard error stream. A negative\n\ \tvalue makes PRoot quiet except on fatal errors.", }, { .class = "Regular options", .arguments = { { .name = "-V", .separator = '\0', .value = NULL }, { .name = "--version", .separator = '\0', .value = NULL }, { .name = "--about", .separator = '\0', .value = NULL }, { .name = NULL, .separator = '\0', .value = NULL } }, .handler = handle_option_V, .description = "Print version, copyright, license and contact, then exit.", .detail = "", }, { .class = "Regular options", .arguments = { { .name = "-h", .separator = '\0', .value = NULL }, { .name = "--help", .separator = '\0', .value = NULL }, { .name = "--usage", .separator = '\0', .value = NULL }, { .name = NULL, .separator = '\0', .value = NULL } }, .handler = handle_option_h, .description = "Print the version and the command-line usage, then exit.", .detail = "", }, { .class = "Extension options", .arguments = { { .name = "-k", .separator = ' ', .value = "string" }, { .name = "--kernel-release", .separator = '=', .value = "string" }, { .name = NULL, .separator = '\0', .value = NULL } }, .handler = handle_option_k, .description = "Make current kernel appear as kernel release *string*.", .detail = "\tIf a program is run on a kernel older than the one expected by its\n\ \tGNU C library, the following error is reported: \"FATAL: kernel too\n\ \told\". To be able to run such programs, PRoot can emulate some of\n\ \tthe features that are available in the kernel release specified by\n\ \t*string* but that are missing in the current kernel.", }, { .class = "Extension options", .arguments = { { .name = "-0", .separator = '\0', .value = NULL }, { .name = "--root-id", .separator = '\0', .value = NULL }, { .name = NULL, .separator = '\0', .value = NULL } }, .handler = handle_option_0, .description = "Make current user appear as \"root\" and fake its privileges.", .detail = "\tSome programs will refuse to work if they are not run with \"root\"\n\ \tprivileges, even if there is no technical reason for that. This\n\ \tis typically the case with package managers. This option allows\n\ \tusers to bypass this kind of limitation by faking the user/group\n\ \tidentity, and by faking the success of some operations like\n\ \tchanging the ownership of files, changing the root directory to\n\ \t/, ... Note that this option is quite limited compared to\n\ \tfakeroot.", }, { .class = "Extension options", .arguments = { { .name = "-i", .separator = ' ', .value = "string" }, { .name = "--change-id", .separator = '=', .value = "string" }, { .name = NULL, .separator = '\0', .value = NULL } }, .handler = handle_option_i, .description = "Make current user and group appear as *string* \"uid:gid\".", .detail = "\tThis option makes the current user and group appear as uid and\n\ \tgid. Likewise, files actually owned by the current user and\n\ \tgroup appear as if they were owned by uid and gid instead.\n\ \tNote that the -0 option is the same as -i 0:0.", }, { .class = "Alias options", .arguments = { { .name = "-R", .separator = ' ', .value = "path" }, { .name = NULL, .separator = '\0', .value = NULL } }, .handler = handle_option_R, .description = "Alias: -r *path* + a couple of recommended -b.", .detail = "\tPrograms isolated in *path*, a guest rootfs, might still need to\n\ \taccess information about the host system, as it is illustrated in\n\ \tthe Examples section of the manual. These host information\n\ \tare typically: user/group definition, network setup, run-time\n\ \tinformation, users' files, ... On all Linux distributions, they\n\ \tall lie in a couple of host files and directories that are\n\ \tautomatically bound by this option:\n\ \t\n\ \t * /etc/host.conf\n\ \t * /etc/hosts\n\ \t * /etc/hosts.equiv\n\ \t * /etc/mtab\n\ \t * /etc/netgroup\n\ \t * /etc/networks\n\ \t * /etc/passwd\n\ \t * /etc/group\n\ \t * /etc/nsswitch.conf\n\ \t * /etc/resolv.conf\n\ \t * /etc/localtime\n\ \t * /dev/\n\ \t * /sys/\n\ \t * /proc/\n\ \t * /tmp/\n\ \t * $HOME", }, { .class = "Alias options", .arguments = { { .name = "-S", .separator = ' ', .value = "path" }, { .name = NULL, .separator = '\0', .value = NULL } }, .handler = handle_option_S, .description = "Alias: -0 -r *path* + a couple of recommended -b.", .detail = "\tThis option is useful to safely create and install packages into\n\ \tthe guest rootfs. It is similar to the -R option expect it\n\ \tenables the -0 option and binds only the following minimal set\n\ \tof paths to avoid unexpected changes on host files:\n\ \t\n\ \t * /etc/host.conf\n\ \t * /etc/hosts\n\ \t * /etc/nsswitch.conf\n\ \t * /dev/\n\ \t * /sys/\n\ \t * /proc/\n\ \t * /tmp/\n\ \t * $HOME", }, END_OF_OPTIONS, }, }; #endif /* PROOT_CLI_H */ care-2.2.1/src/cli/cli.h0000644000175000017500000000342712430615044014265 0ustar ivoireivoire/* This file is automatically generated from the documentation. EDIT AT YOUR OWN RISK. */ #ifndef CLI_H #define CLI_H #include #include "tracee/tracee.h" #include "attribute.h" typedef struct { const char *name; char separator; const char *value; } Argument; struct Cli; typedef int (*option_handler_t)(Tracee *tracee, const struct Cli *cli, char *value); typedef struct { const char *class; option_handler_t handler; const char *description; const char *detail; Argument arguments[5]; } Option; #define END_OF_OPTIONS { .class = NULL, \ .arguments = {{ .name = NULL, .separator = '\0', .value = NULL }}, \ .handler = NULL, \ .description = NULL, \ .detail = NULL \ } typedef int (*initialization_hook_t)(Tracee *tracee, const struct Cli *cli, size_t argc, char *const *argv, size_t cursor); typedef struct Cli { const char *name; const char *version; const char *subtitle; const char *synopsis; const char *colophon; const char *logo; initialization_hook_t pre_initialize_bindings; initialization_hook_t post_initialize_bindings; initialization_hook_t pre_initialize_cwd; initialization_hook_t post_initialize_cwd; initialization_hook_t pre_initialize_command; initialization_hook_t post_initialize_command; void *private; const Option options[]; } Cli; extern const Cli *get_proot_cli(TALLOC_CTX *context); extern const Cli * WEAK get_care_cli(TALLOC_CTX *context); extern void print_usage(Tracee *tracee, const Cli *cli, bool detailed); extern void print_version(const Cli *cli); extern int parse_integer_option(const Tracee *tracee, int *variable, const char *value, const char *option); extern const char *expand_front_variable(TALLOC_CTX *context, const char *string); extern bool exit_failure; #endif /* CLI_H */ care-2.2.1/src/cli/notice.c0000644000175000017500000000430112430615044014762 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* errno, */ #include /* strerror(3), */ #include /* va_*, */ #include /* vfprintf(3), */ #include /* INT_MAX, */ #include "cli/notice.h" #include "tracee/tracee.h" int global_verbose_level; const char *global_tool_name; /** * Print @message to the standard error stream according to its * @severity and @origin. */ void notice(const Tracee *tracee, Severity severity, Origin origin, const char *message, ...) { const char *tool_name; va_list extra_params; int verbose_level; if (tracee == NULL) { verbose_level = global_verbose_level; tool_name = global_tool_name ?: ""; } else { verbose_level = tracee->verbose; tool_name = tracee->tool_name; } if (verbose_level < 0 && severity != ERROR) return; switch (severity) { case WARNING: fprintf(stderr, "%s warning: ", tool_name); break; case ERROR: fprintf(stderr, "%s error: ", tool_name); break; case INFO: default: fprintf(stderr, "%s info: ", tool_name); break; } if (origin == TALLOC) fprintf(stderr, "talloc: "); va_start(extra_params, message); vfprintf(stderr, message, extra_params); va_end(extra_params); switch (origin) { case SYSTEM: fprintf(stderr, ": "); perror(NULL); break; case TALLOC: break; case INTERNAL: case USER: default: fprintf(stderr, "\n"); break; } return; } care-2.2.1/src/tracee/0000755000175000017500000000000012430615044014033 5ustar ivoireivoirecare-2.2.1/src/tracee/reg.h0000644000175000017500000000272712430615044014771 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef TRACEE_REG_H #define TRACEE_REG_H #include "tracee/tracee.h" #include "arch.h" typedef enum { SYSARG_NUM = 0, SYSARG_1, SYSARG_2, SYSARG_3, SYSARG_4, SYSARG_5, SYSARG_6, SYSARG_RESULT, STACK_POINTER, INSTR_POINTER, } Reg; extern int fetch_regs(Tracee *tracee); extern int push_regs(Tracee *tracee); extern word_t peek_reg(const Tracee *tracee, RegVersion version, Reg reg); extern void poke_reg(Tracee *tracee, Reg reg, word_t value); extern void print_current_regs(Tracee *tracee, int verbose_level, const char *message); extern void save_current_regs(Tracee *tracee, RegVersion version); #endif /* TRACEE_REG_H */ care-2.2.1/src/tracee/event.h0000644000175000017500000000222412430615044015325 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef TRACEE_EVENT_H #define TRACEE_EVENT_H #include #include "tracee/tracee.h" extern int launch_process(Tracee *tracee); extern int event_loop(); extern int handle_tracee_event(Tracee *tracee, int tracee_status); extern bool restart_tracee(Tracee *tracee, int signal); #endif /* TRACEE_EVENT_H */ care-2.2.1/src/tracee/array.h0000644000175000017500000000521312430615044015323 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef ARRAY_H #define ARRAY_H #include #include "tracee/reg.h" #include "arch.h" typedef struct array Array; typedef int (*read_item_t)(Array *array, size_t index, void **value); typedef int (*write_item_t)(Array *array, size_t index, const void *value); typedef int (*compare_item_t)(Array *array, size_t index, const void *reference); typedef int (*sizeof_item_t)(Array *array, size_t index); typedef struct array_entry ArrayEntry; struct array { ArrayEntry *_cache; size_t length; read_item_t read_item; write_item_t write_item; compare_item_t compare_item; sizeof_item_t sizeof_item; }; static inline int read_item(Array *array, size_t index, void **value) { return array->read_item(array, index, value); } static inline int write_item(Array *array, size_t index, const void *value) { return array->write_item(array, index, value); } static inline int compare_item(Array *array, size_t index, const void *reference) { return array->compare_item(array, index, reference); } static inline int sizeof_item(Array *array, size_t index) { return array->sizeof_item(array, index); } extern int find_item(Array *array, const void *reference); extern int resize_array(Array *array, size_t index, ssize_t nb_delta_entries); extern int fetch_array(Tracee *tracee, Array **array, Reg reg, size_t nb_entries); extern int push_array(Array *array, Reg reg); extern int read_item_data(Array *array, size_t index, void **value); extern int read_item_string(Array *array, size_t index, char **value); extern int write_item_string(Array *array, size_t index, const char *value); extern int write_items(Array *array, size_t index, size_t nb_items, ...); extern int compare_item_generic(Array *array, size_t index, const void *reference); extern int sizeof_item_string(Array *array, size_t index); #endif /* ARRAY_H */ care-2.2.1/src/tracee/mem.c0000644000175000017500000003615212430615044014764 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* ptrace(2), PTRACE_*, */ #include /* pid_t, size_t, */ #include /* NULL, */ #include /* offsetof(), */ #include /* struct user*, */ #include /* errno, */ #include /* assert(3), */ #include /* waitpid(2), */ #include /* memcpy(3), */ #include /* uint*_t, */ #include /* process_vm_*, struct iovec, */ #include /* sysconf(3), */ #include /* mmap(2), munmap(2), MAP_*, */ #include "tracee/mem.h" #include "tracee/abi.h" #include "syscall/heap.h" #include "arch.h" /* word_t, NO_MISALIGNED_ACCESS */ #include "build.h" /* HAVE_PROCESS_VM, */ #include "cli/notice.h" /** * Load the word at the given @address, potentially *not* aligned. */ static inline word_t load_word(const void *address) { #ifdef NO_MISALIGNED_ACCESS if (((word_t)address) % sizeof(word_t) == 0) return *(word_t *)address; else { word_t value; memcpy(&value, address, sizeof(word_t)); return value; } #else return *(word_t *)address; #endif } /** * Store the word with the given @value to the given @address, * potentially *not* aligned. */ static inline void store_word(void *address, word_t value) { #ifdef NO_MISALIGNED_ACCESS if (((word_t)address) % sizeof(word_t) == 0) *((word_t *)address) = value; else memcpy(address, &value, sizeof(word_t)); #else *((word_t *)address) = value; #endif } /** * Copy @size bytes from the buffer @src_tracer to the address * @dest_tracee within the memory space of the @tracee process. It * returns -errno if an error occured, otherwise 0. */ int write_data(const Tracee *tracee, word_t dest_tracee, const void *src_tracer, word_t size) { word_t *src = (word_t *)src_tracer; word_t *dest = (word_t *)dest_tracee; long status; word_t word, i, j; word_t nb_trailing_bytes; word_t nb_full_words; uint8_t *last_dest_word; uint8_t *last_src_word; if (belongs_to_heap_prealloc(tracee, dest_tracee)) return -EFAULT; #if defined(HAVE_PROCESS_VM) struct iovec local; struct iovec remote; local.iov_base = src; local.iov_len = size; remote.iov_base = dest; remote.iov_len = size; status = process_vm_writev(tracee->pid, &local, 1, &remote, 1, 0); if ((size_t) status == size) return 0; /* Fallback to ptrace if something went wrong. */ #endif /* HAVE_PROCESS_VM */ nb_trailing_bytes = size % sizeof(word_t); nb_full_words = (size - nb_trailing_bytes) / sizeof(word_t); /* Copy one word by one word, except for the last one. */ for (i = 0; i < nb_full_words; i++) { status = ptrace(PTRACE_POKEDATA, tracee->pid, dest + i, load_word(&src[i])); if (status < 0) { notice(tracee, WARNING, SYSTEM, "ptrace(POKEDATA)"); return -EFAULT; } } if (nb_trailing_bytes == 0) return 0; /* Copy the bytes in the last word carefully since we have to * overwrite only the relevant ones. */ word = ptrace(PTRACE_PEEKDATA, tracee->pid, dest + i, NULL); if (errno != 0) { notice(tracee, WARNING, SYSTEM, "ptrace(PEEKDATA)"); return -EFAULT; } last_dest_word = (uint8_t *)&word; last_src_word = (uint8_t *)&src[i]; for (j = 0; j < nb_trailing_bytes; j++) last_dest_word[j] = last_src_word[j]; status = ptrace(PTRACE_POKEDATA, tracee->pid, dest + i, word); if (status < 0) { notice(tracee, WARNING, SYSTEM, "ptrace(POKEDATA)"); return -EFAULT; } return 0; } /** * Gather the @src_tracer_count buffers pointed to by @src_tracer to * the address @dest_tracee within the memory space of the @tracee * process. This function returns -errno if an error occured, * otherwise 0. */ int writev_data(const Tracee *tracee, word_t dest_tracee, const struct iovec *src_tracer, int src_tracer_count) { size_t size; int status; int i; if (belongs_to_heap_prealloc(tracee, dest_tracee)) return -EFAULT; #if defined(HAVE_PROCESS_VM) struct iovec remote; for (i = 0, size = 0; i < src_tracer_count; i++) size += src_tracer[i].iov_len; remote.iov_base = (word_t *)dest_tracee; remote.iov_len = size; status = process_vm_writev(tracee->pid, src_tracer, src_tracer_count, &remote, 1, 0); if ((size_t) status == size) return 0; /* Fallback to iterative-write if something went wrong. */ #endif /* HAVE_PROCESS_VM */ for (i = 0, size = 0; i < src_tracer_count; i++) { status = write_data(tracee, dest_tracee + size, src_tracer[i].iov_base, src_tracer[i].iov_len); if (status < 0) return status; size += src_tracer[i].iov_len; } return 0; } /** * Copy @size bytes to the buffer @dest_tracer from the address * @src_tracee within the memory space of the @tracee process. It * returns -errno if an error occured, otherwise 0. */ int read_data(const Tracee *tracee, void *dest_tracer, word_t src_tracee, word_t size) { word_t *src = (word_t *)src_tracee; word_t *dest = (word_t *)dest_tracer; word_t nb_trailing_bytes; word_t nb_full_words; word_t word, i, j; uint8_t *last_src_word; uint8_t *last_dest_word; if (belongs_to_heap_prealloc(tracee, src_tracee)) return -EFAULT; #if defined(HAVE_PROCESS_VM) long status; struct iovec local; struct iovec remote; local.iov_base = dest; local.iov_len = size; remote.iov_base = src; remote.iov_len = size; status = process_vm_readv(tracee->pid, &local, 1, &remote, 1, 0); if ((size_t) status == size) return 0; /* Fallback to ptrace if something went wrong. */ #endif /* HAVE_PROCESS_VM */ nb_trailing_bytes = size % sizeof(word_t); nb_full_words = (size - nb_trailing_bytes) / sizeof(word_t); /* Copy one word by one word, except for the last one. */ for (i = 0; i < nb_full_words; i++) { word = ptrace(PTRACE_PEEKDATA, tracee->pid, src + i, NULL); if (errno != 0) { notice(tracee, WARNING, SYSTEM, "ptrace(PEEKDATA)"); return -EFAULT; } store_word(&dest[i], word); } if (nb_trailing_bytes == 0) return 0; /* Copy the bytes from the last word carefully since we have * to not overwrite the bytes lying beyond @dest_tracer. */ word = ptrace(PTRACE_PEEKDATA, tracee->pid, src + i, NULL); if (errno != 0) { notice(tracee, WARNING, SYSTEM, "ptrace(PEEKDATA)"); return -EFAULT; } last_dest_word = (uint8_t *)&dest[i]; last_src_word = (uint8_t *)&word; for (j = 0; j < nb_trailing_bytes; j++) last_dest_word[j] = last_src_word[j]; return 0; } /** * Copy to @dest_tracer at most @max_size bytes from the string * pointed to by @src_tracee within the memory space of the @tracee * process. This function returns -errno on error, otherwise * it returns the number in bytes of the string, including the * end-of-string terminator. */ int read_string(const Tracee *tracee, char *dest_tracer, word_t src_tracee, word_t max_size) { word_t *src = (word_t *)src_tracee; word_t *dest = (word_t *)dest_tracer; word_t nb_trailing_bytes; word_t nb_full_words; word_t word, i, j; uint8_t *src_word; uint8_t *dest_word; if (belongs_to_heap_prealloc(tracee, src_tracee)) return -EFAULT; #if defined(HAVE_PROCESS_VM) /* [process_vm] system calls do not check the memory regions * in the remote process until just before doing the * read/write. Consequently, a partial read/write [1] may * result if one of the remote_iov elements points to an * invalid memory region in the remote process. No further * reads/writes will be attempted beyond that point. Keep * this in mind when attempting to read data of unknown length * (such as C strings that are null-terminated) from a remote * process, by avoiding spanning memory pages (typically 4KiB) * in a single remote iovec element. (Instead, split the * remote read into two remote_iov elements and have them * merge back into a single write local_iov entry. The first * read entry goes up to the page boundary, while the second * starts on the next page boundary.). * * [1] Partial transfers apply at the granularity of iovec * elements. These system calls won't perform a partial * transfer that splits a single iovec element. * * -- man 2 process_vm_readv */ long status; size_t size; size_t offset; struct iovec local; struct iovec remote; static size_t chunk_size = 0; static uintptr_t chunk_mask; /* A chunk shall not cross a page boundary. */ if (chunk_size == 0) { chunk_size = sysconf(_SC_PAGESIZE); chunk_size = (chunk_size > 0 && chunk_size < 1024 ? chunk_size : 1024); chunk_mask = ~(chunk_size - 1); } /* Read the string by chunk. */ offset = 0; do { uintptr_t current_chunk = (src_tracee + offset) & chunk_mask; uintptr_t next_chunk = current_chunk + chunk_size; /* Compute the number of bytes available up to the * next chunk or up to max_size. */ size = next_chunk - (src_tracee + offset); size = (size < max_size - offset ? size : max_size - offset); local.iov_base = (uint8_t *)dest + offset; local.iov_len = size; remote.iov_base = (uint8_t *)src + offset; remote.iov_len = size; status = process_vm_readv(tracee->pid, &local, 1, &remote, 1, 0); if ((size_t) status != size) goto fallback; status = strnlen(local.iov_base, size); if ((size_t) status < size) { size = offset + status + 1; assert(size <= max_size); return size; } offset += size; } while (offset < max_size); assert(offset == max_size); /* Fallback to ptrace if something went wrong. */ fallback: #endif /* HAVE_PROCESS_VM */ nb_trailing_bytes = max_size % sizeof(word_t); nb_full_words = (max_size - nb_trailing_bytes) / sizeof(word_t); /* Copy one word by one word, except for the last one. */ for (i = 0; i < nb_full_words; i++) { word = ptrace(PTRACE_PEEKDATA, tracee->pid, src + i, NULL); if (errno != 0) return -EFAULT; store_word(&dest[i], word); /* Stop once an end-of-string is detected. */ src_word = (uint8_t *)&word; for (j = 0; j < sizeof(word_t); j++) if (src_word[j] == '\0') return i * sizeof(word_t) + j + 1; } /* Copy the bytes from the last word carefully since we have * to not overwrite the bytes lying beyond @dest_tracer. */ word = ptrace(PTRACE_PEEKDATA, tracee->pid, src + i, NULL); if (errno != 0) return -EFAULT; dest_word = (uint8_t *)&dest[i]; src_word = (uint8_t *)&word; for (j = 0; j < nb_trailing_bytes; j++) { dest_word[j] = src_word[j]; if (src_word[j] == '\0') break; } return i * sizeof(word_t) + j + 1; } /** * Return the value of the word at the given @address in the @tracee's * memory space. The caller must test errno to check if an error * occured. */ word_t peek_word(const Tracee *tracee, word_t address) { word_t result = 0; if (belongs_to_heap_prealloc(tracee, address)) { errno = EFAULT; return 0; } #if defined(HAVE_PROCESS_VM) int status; struct iovec local; struct iovec remote; local.iov_base = &result; local.iov_len = sizeof_word(tracee); remote.iov_base = (void *)address; remote.iov_len = sizeof_word(tracee); errno = 0; status = process_vm_readv(tracee->pid, &local, 1, &remote, 1, 0); if (status > 0) return result; /* Fallback to ptrace if something went wrong. */ #endif errno = 0; result = (word_t) ptrace(PTRACE_PEEKDATA, tracee->pid, address, NULL); /* From ptrace(2) manual: "Unfortunately, under Linux, * different variations of this fault will return EIO or * EFAULT more or less arbitrarily." */ if (errno == EIO) errno = EFAULT; /* Use only the 32 LSB when running a 32-bit process on a * 64-bit kernel. */ if (is_32on64_mode(tracee)) result &= 0xFFFFFFFF; return result; } /** * Set the word at the given @address in the @tracee's memory space to * the given @value. The caller must test errno to check if an error * occured. */ void poke_word(const Tracee *tracee, word_t address, word_t value) { word_t tmp; if (belongs_to_heap_prealloc(tracee, address)) { errno = EFAULT; return; } #if defined(HAVE_PROCESS_VM) int status; struct iovec local; struct iovec remote; /* Note: &value points to the 32 LSB on 64-bit little-endian * architecture. */ local.iov_base = &value; local.iov_len = sizeof_word(tracee); remote.iov_base = (void *)address; remote.iov_len = sizeof_word(tracee); errno = 0; status = process_vm_writev(tracee->pid, &local, 1, &remote, 1, 0); if (status > 0) return; /* Fallback to ptrace if something went wrong. */ #endif /* Don't overwrite the 32 MSB when running a 32-bit process on * a 64-bit kernel. */ if (is_32on64_mode(tracee)) { errno = 0; tmp = (word_t) ptrace(PTRACE_PEEKDATA, tracee->pid, address, NULL); if (errno != 0) return; value |= (tmp & 0xFFFFFFFF00000000ULL); } errno = 0; (void) ptrace(PTRACE_POKEDATA, tracee->pid, address, value); /* From ptrace(2) manual: "Unfortunately, under Linux, * different variations of this fault will return EIO or * EFAULT more or less arbitrarily." */ if (errno == EIO) errno = EFAULT; return; } /** * Allocate @size bytes in the @tracee's memory space. This function * returns the address of the allocated memory in the @tracee's memory * space, otherwise 0 if an error occured. */ word_t alloc_mem(Tracee *tracee, ssize_t size) { word_t stack_pointer; /* Get the current value of the stack pointer from the tracee's * USER area. */ stack_pointer = peek_reg(tracee, CURRENT, STACK_POINTER); /* Some ABIs specify an amount of bytes after the stack * pointer that shall not be used by anything but the compiler * (for optimization purpose). */ if (stack_pointer == peek_reg(tracee, ORIGINAL, STACK_POINTER)) size += RED_ZONE_SIZE; /* Sanity check. */ if ( (size > 0 && stack_pointer <= (word_t) size) || (size < 0 && stack_pointer >= ULONG_MAX + size)) { notice(tracee, WARNING, INTERNAL, "integer under/overflow detected in %s", __FUNCTION__); return 0; } /* Remember the stack grows downward. */ stack_pointer -= size; /* Set the new value of the stack pointer in the tracee's USER * area. */ poke_reg(tracee, STACK_POINTER, stack_pointer); return stack_pointer; } /** * Clear @size bytes at the given @address in the @tracee's memory * space. This function returns -errno if an error occured, otherwise * 0. */ int clear_mem(const Tracee *tracee, word_t address, size_t size) { int status; void *zeros; if (belongs_to_heap_prealloc(tracee, address)) return -EFAULT; zeros = mmap(NULL, size, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (zeros == MAP_FAILED) return -errno; status = write_data(tracee, address, zeros, size); munmap(zeros, size); return status; } care-2.2.1/src/tracee/reg.c0000644000175000017500000002313312430615044014756 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* off_t */ #include /* struct user*, */ #include /* ptrace(2), PTRACE*, */ #include /* assert(3), */ #include /* errno(3), */ #include /* offsetof(), */ #include /* *int*_t(), */ #include /* ULONG_MAX, */ #include /* memcpy(3), */ #include /* struct iovec, */ #include "arch.h" #if defined(ARCH_ARM64) #include /* NT_PRSTATUS */ #endif #include "syscall/sysnum.h" #include "tracee/reg.h" #include "tracee/abi.h" #include "cli/notice.h" #include "compat.h" /** * Compute the offset of the register @reg_name in the USER area. */ #define USER_REGS_OFFSET(reg_name) \ (offsetof(struct user, regs) \ + offsetof(struct user_regs_struct, reg_name)) #define REG(tracee, version, index) \ (*(word_t*) (((uint8_t *) &tracee->_regs[version]) + reg_offset[index])) /* Specify the ABI registers (syscall argument passing, stack pointer). * See sysdeps/unix/sysv/linux/${ARCH}/syscall.S from the GNU C Library. */ #if defined(ARCH_X86_64) static off_t reg_offset[] = { [SYSARG_NUM] = USER_REGS_OFFSET(orig_rax), [SYSARG_1] = USER_REGS_OFFSET(rdi), [SYSARG_2] = USER_REGS_OFFSET(rsi), [SYSARG_3] = USER_REGS_OFFSET(rdx), [SYSARG_4] = USER_REGS_OFFSET(r10), [SYSARG_5] = USER_REGS_OFFSET(r8), [SYSARG_6] = USER_REGS_OFFSET(r9), [SYSARG_RESULT] = USER_REGS_OFFSET(rax), [STACK_POINTER] = USER_REGS_OFFSET(rsp), [INSTR_POINTER] = USER_REGS_OFFSET(rip), }; static off_t reg_offset_x86[] = { [SYSARG_NUM] = USER_REGS_OFFSET(orig_rax), [SYSARG_1] = USER_REGS_OFFSET(rbx), [SYSARG_2] = USER_REGS_OFFSET(rcx), [SYSARG_3] = USER_REGS_OFFSET(rdx), [SYSARG_4] = USER_REGS_OFFSET(rsi), [SYSARG_5] = USER_REGS_OFFSET(rdi), [SYSARG_6] = USER_REGS_OFFSET(rbp), [SYSARG_RESULT] = USER_REGS_OFFSET(rax), [STACK_POINTER] = USER_REGS_OFFSET(rsp), [INSTR_POINTER] = USER_REGS_OFFSET(rip), }; #undef REG #define REG(tracee, version, index) \ (*(word_t*) (tracee->_regs[version].cs == 0x23 \ ? (((uint8_t *) &tracee->_regs[version]) + reg_offset_x86[index]) \ : (((uint8_t *) &tracee->_regs[version]) + reg_offset[index]))) #elif defined(ARCH_ARM_EABI) static off_t reg_offset[] = { [SYSARG_NUM] = USER_REGS_OFFSET(uregs[7]), [SYSARG_1] = USER_REGS_OFFSET(uregs[0]), [SYSARG_2] = USER_REGS_OFFSET(uregs[1]), [SYSARG_3] = USER_REGS_OFFSET(uregs[2]), [SYSARG_4] = USER_REGS_OFFSET(uregs[3]), [SYSARG_5] = USER_REGS_OFFSET(uregs[4]), [SYSARG_6] = USER_REGS_OFFSET(uregs[5]), [SYSARG_RESULT] = USER_REGS_OFFSET(uregs[0]), [STACK_POINTER] = USER_REGS_OFFSET(uregs[13]), [INSTR_POINTER] = USER_REGS_OFFSET(uregs[15]), }; #elif defined(ARCH_ARM64) #undef USER_REGS_OFFSET #define USER_REGS_OFFSET(reg_name) offsetof(struct user_regs_struct, reg_name) static off_t reg_offset[] = { [SYSARG_NUM] = USER_REGS_OFFSET(regs[8]), [SYSARG_1] = USER_REGS_OFFSET(regs[0]), [SYSARG_2] = USER_REGS_OFFSET(regs[1]), [SYSARG_3] = USER_REGS_OFFSET(regs[2]), [SYSARG_4] = USER_REGS_OFFSET(regs[3]), [SYSARG_5] = USER_REGS_OFFSET(regs[4]), [SYSARG_6] = USER_REGS_OFFSET(regs[5]), [SYSARG_RESULT] = USER_REGS_OFFSET(regs[0]), [STACK_POINTER] = USER_REGS_OFFSET(sp), [INSTR_POINTER] = USER_REGS_OFFSET(pc), }; #elif defined(ARCH_X86) static off_t reg_offset[] = { [SYSARG_NUM] = USER_REGS_OFFSET(orig_eax), [SYSARG_1] = USER_REGS_OFFSET(ebx), [SYSARG_2] = USER_REGS_OFFSET(ecx), [SYSARG_3] = USER_REGS_OFFSET(edx), [SYSARG_4] = USER_REGS_OFFSET(esi), [SYSARG_5] = USER_REGS_OFFSET(edi), [SYSARG_6] = USER_REGS_OFFSET(ebp), [SYSARG_RESULT] = USER_REGS_OFFSET(eax), [STACK_POINTER] = USER_REGS_OFFSET(esp), [INSTR_POINTER] = USER_REGS_OFFSET(eip), }; #elif defined(ARCH_SH4) static off_t reg_offset[] = { [SYSARG_NUM] = USER_REGS_OFFSET(regs[3]), [SYSARG_1] = USER_REGS_OFFSET(regs[4]), [SYSARG_2] = USER_REGS_OFFSET(regs[5]), [SYSARG_3] = USER_REGS_OFFSET(regs[6]), [SYSARG_4] = USER_REGS_OFFSET(regs[7]), [SYSARG_5] = USER_REGS_OFFSET(regs[0]), [SYSARG_6] = USER_REGS_OFFSET(regs[1]), [SYSARG_RESULT] = USER_REGS_OFFSET(regs[0]), [STACK_POINTER] = USER_REGS_OFFSET(regs[15]), [INSTR_POINTER] = USER_REGS_OFFSET(pc), }; #else #error "Unsupported architecture" #endif /** * Return the *cached* value of the given @tracees' @reg. */ word_t peek_reg(const Tracee *tracee, RegVersion version, Reg reg) { word_t result; assert(version < NB_REG_VERSION); result = REG(tracee, version, reg); /* Use only the 32 least significant bits (LSB) when running * 32-bit processes on a 64-bit kernel. */ if (is_32on64_mode(tracee)) result &= 0xFFFFFFFF; return result; } /** * Set the *cached* value of the given @tracees' @reg. */ void poke_reg(Tracee *tracee, Reg reg, word_t value) { if (peek_reg(tracee, CURRENT, reg) == value) return; REG(tracee, CURRENT, reg) = value; tracee->_regs_were_changed = true; } /** * Print the value of the current @tracee's registers according * to the @verbose_level. Note: @message is mixed to the output. */ void print_current_regs(Tracee *tracee, int verbose_level, const char *message) { if (tracee->verbose < verbose_level) return; notice(tracee, INFO, INTERNAL, "pid %d: %s: %s(0x%lx, 0x%lx, 0x%lx, 0x%lx, 0x%lx, 0x%lx) = 0x%lx [0x%lx, %d]", tracee->pid, message, stringify_sysnum(get_sysnum(tracee, CURRENT)), peek_reg(tracee, CURRENT, SYSARG_1), peek_reg(tracee, CURRENT, SYSARG_2), peek_reg(tracee, CURRENT, SYSARG_3), peek_reg(tracee, CURRENT, SYSARG_4), peek_reg(tracee, CURRENT, SYSARG_5), peek_reg(tracee, CURRENT, SYSARG_6), peek_reg(tracee, CURRENT, SYSARG_RESULT), peek_reg(tracee, CURRENT, STACK_POINTER), get_abi(tracee)); } /** * Save the @tracee's current register bank into the @version register * bank. */ void save_current_regs(Tracee *tracee, RegVersion version) { /* Optimization: don't restore original register values if * they were never changed. */ if (version == ORIGINAL) tracee->_regs_were_changed = false; memcpy(&tracee->_regs[version], &tracee->_regs[CURRENT], sizeof(tracee->_regs[CURRENT])); } /** * Copy all @tracee's general purpose registers into a dedicated * cache. This function returns -errno if an error occured, 0 * otherwise. */ int fetch_regs(Tracee *tracee) { int status; #if defined(ARCH_ARM64) struct iovec regs; regs.iov_base = &tracee->_regs[CURRENT]; regs.iov_len = sizeof(tracee->_regs[CURRENT]); status = ptrace(PTRACE_GETREGSET, tracee->pid, NT_PRSTATUS, ®s); #else status = ptrace(PTRACE_GETREGS, tracee->pid, NULL, &tracee->_regs[CURRENT]); #endif if (status < 0) return status; return 0; } /** * Copy the cached values of all @tracee's general purpose registers * back to the process, if necessary. This function returns -errno if * an error occured, 0 otherwise. */ int push_regs(Tracee *tracee) { int status; if (tracee->_regs_were_changed) { /* At the very end of a syscall, with regard to the * entry, only the result register can be modified by * PRoot. */ if (tracee->restore_original_regs) { /* Restore the sysarg register only if it is * not the same as the result register. Note: * it's never the case on x86 architectures, * so don't make this check, otherwise it * would introduce useless complexity because * of the multiple ABI support. */ #if defined(ARCH_X86) || defined(ARCH_X86_64) # define RESTORE(sysarg) (REG(tracee, CURRENT, sysarg) = REG(tracee, ORIGINAL, sysarg)) #else # define RESTORE(sysarg) (void) (reg_offset[SYSARG_RESULT] != reg_offset[sysarg] && \ (REG(tracee, CURRENT, sysarg) = REG(tracee, ORIGINAL, sysarg))) #endif RESTORE(SYSARG_NUM); RESTORE(SYSARG_1); RESTORE(SYSARG_2); RESTORE(SYSARG_3); RESTORE(SYSARG_4); RESTORE(SYSARG_5); RESTORE(SYSARG_6); RESTORE(STACK_POINTER); } #if defined(ARCH_ARM64) struct iovec regs; regs.iov_base = &tracee->_regs[CURRENT]; regs.iov_len = sizeof(tracee->_regs[CURRENT]); status = ptrace(PTRACE_SETREGSET, tracee->pid, NT_PRSTATUS, ®s); #else # if defined(ARCH_ARM_EABI) /* On ARM, a special ptrace request is required to * change effectively the syscall number during a * ptrace-stop. */ word_t current_sysnum = REG(tracee, CURRENT, SYSARG_NUM); if (current_sysnum != REG(tracee, ORIGINAL, SYSARG_NUM)) { status = ptrace(PTRACE_SET_SYSCALL, tracee->pid, 0, current_sysnum); if (status < 0) notice(tracee, WARNING, SYSTEM, "can't set the syscall number"); } # endif status = ptrace(PTRACE_SETREGS, tracee->pid, NULL, &tracee->_regs[CURRENT]); #endif if (status < 0) return status; } return 0; } care-2.2.1/src/tracee/event.c0000644000175000017500000003732312430615044015330 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* CLONE_*, */ #include /* pid_t, */ #include /* ptrace(1), PTRACE_*, */ #include /* waitpid(2), */ #include /* waitpid(2), */ #include /* uname(2), */ #include /* fork(2), chdir(2), getpid(2), */ #include /* strcmp(3), */ #include /* errno(3), */ #include /* bool, true, false, */ #include /* assert(3), */ #include /* atexit(3), getenv(3), */ #include /* talloc_*, */ #include "tracee/event.h" #include "cli/notice.h" #include "path/path.h" #include "path/binding.h" #include "syscall/syscall.h" #include "syscall/seccomp.h" #include "ptrace/wait.h" #include "extension/extension.h" #include "execve/elf.h" #include "attribute.h" #include "compat.h" /** * Launch the first process as specified by @tracee->cmdline[]. This * function returns -errno if an error occurred, otherwise 0. */ int launch_process(Tracee *tracee) { long status; pid_t pid; /* Warn about open file descriptors. They won't be * translated until they are closed. */ if (tracee->verbose > 0) list_open_fd(tracee); pid = fork(); switch(pid) { case -1: notice(tracee, ERROR, SYSTEM, "fork()"); return -errno; case 0: /* child */ /* Declare myself as ptraceable before executing the * requested program. */ status = ptrace(PTRACE_TRACEME, 0, NULL, NULL); if (status < 0) { notice(tracee, ERROR, SYSTEM, "ptrace(TRACEME)"); return -errno; } /* Synchronize with the tracer's event loop. Without * this trick the tracer only sees the "return" from * the next execve(2) so PRoot wouldn't handle the * interpreter/runner. I also verified that strace * does the same thing. */ kill(getpid(), SIGSTOP); /* Improve performance by using seccomp mode 2, unless * this support is explicitly disabled. */ if (getenv("PROOT_NO_SECCOMP") == NULL) (void) enable_syscall_filtering(tracee); /* Now process is ptraced, so the current rootfs is already the * guest rootfs. Note: Valgrind can't handle execve(2) on * "foreign" binaries (ENOEXEC) but can handle execvp(3) on such * binaries. */ execvp(tracee->exe, tracee->cmdline); return -errno; default: /* parent */ /* We know the pid of the first tracee now. */ tracee->pid = pid; return 0; } /* Never reached. */ return -ENOSYS; } /* Send the KILL signal to all tracees when PRoot has received a fatal * signal. */ static void kill_all_tracees2(int signum, siginfo_t *siginfo UNUSED, void *ucontext UNUSED) { notice(NULL, WARNING, INTERNAL, "signal %d received from process %d", signum, siginfo->si_pid); kill_all_tracees(); /* Exit immediately for system signals (segmentation fault, * illegal instruction, ...), otherwise exit cleanly through * the event loop. */ if (signum != SIGQUIT) _exit(EXIT_FAILURE); } /** * Helper for print_talloc_hierarchy(). */ static void print_talloc_chunk(const void *ptr, int depth, int max_depth UNUSED, int is_ref, void *data UNUSED) { const char *name; size_t count; size_t size; name = talloc_get_name(ptr); size = talloc_get_size(ptr); count = talloc_reference_count(ptr); if (depth == 0) return; while (depth-- > 1) fprintf(stderr, "\t"); fprintf(stderr, "%-16s ", name); if (is_ref) fprintf(stderr, "-> %-8p", ptr); else { fprintf(stderr, "%-8p %zd bytes %zd ref'", ptr, size, count); if (name[0] == '$') { fprintf(stderr, "\t(\"%s\")", (char *)ptr); } if (name[0] == '@') { char **argv; int i; fprintf(stderr, "\t("); for (i = 0, argv = (char **)ptr; argv[i] != NULL; i++) fprintf(stderr, "\"%s\", ", argv[i]); fprintf(stderr, ")"); } else if (strcmp(name, "Tracee") == 0) { fprintf(stderr, "\t(pid = %d, parent = %p)", ((Tracee *)ptr)->pid, ((Tracee *)ptr)->parent); } else if (strcmp(name, "Bindings") == 0) { Tracee *tracee; tracee = TRACEE(ptr); if (ptr == tracee->fs->bindings.pending) fprintf(stderr, "\t(pending)"); else if (ptr == tracee->fs->bindings.guest) fprintf(stderr, "\t(guest)"); else if (ptr == tracee->fs->bindings.host) fprintf(stderr, "\t(host)"); } else if (strcmp(name, "Binding") == 0) { Binding *binding = (Binding *)ptr; fprintf(stderr, "\t(%s:%s)", binding->host.path, binding->guest.path); } } fprintf(stderr, "\n"); } /* Print on stderr the complete talloc hierarchy. */ static void print_talloc_hierarchy(int signum, siginfo_t *siginfo UNUSED, void *ucontext UNUSED) { switch (signum) { case SIGUSR1: talloc_report_depth_cb(NULL, 0, 100, print_talloc_chunk, NULL); break; case SIGUSR2: talloc_report_depth_file(NULL, 0, 100, stderr); break; default: break; } } static int last_exit_status = -1; /** * Check if this instance of PRoot can *technically* handle @tracee. */ static void check_architecture(Tracee *tracee) { struct utsname utsname; ElfHeader elf_header; char path[PATH_MAX]; int status; if (tracee->exe == NULL) return; status = translate_path(tracee, path, AT_FDCWD, tracee->exe, false); if (status < 0) return; status = open_elf(path, &elf_header); if (status < 0) return; close(status); if (!IS_CLASS64(elf_header) || sizeof(word_t) == sizeof(uint64_t)) return; notice(tracee, ERROR, USER, "'%s' is a 64-bit program whereas this version of " "%s handles 32-bit programs only", path, tracee->tool_name); status = uname(&utsname); if (status < 0) return; if (strcmp(utsname.machine, "x86_64") != 0) return; notice(tracee, INFO, USER, "use a 64-bit version of %s instead, it supports both 32 and 64-bit programs", tracee->tool_name); } /** * Wait then handle any event from any tracee. This function returns * the exit status of the last terminated program. */ int event_loop() { struct sigaction signal_action; long status; int signum; /* Kill all tracees when exiting. */ status = atexit(kill_all_tracees); if (status != 0) notice(NULL, WARNING, INTERNAL, "atexit() failed"); /* All signals are blocked when the signal handler is called. * SIGINFO is used to know which process has signaled us and * RESTART is used to restart waitpid(2) seamlessly. */ bzero(&signal_action, sizeof(signal_action)); signal_action.sa_flags = SA_SIGINFO | SA_RESTART; status = sigfillset(&signal_action.sa_mask); if (status < 0) notice(NULL, WARNING, SYSTEM, "sigfillset()"); /* Handle all signals. */ for (signum = 0; signum < SIGRTMAX; signum++) { switch (signum) { case SIGQUIT: case SIGILL: case SIGABRT: case SIGFPE: case SIGSEGV: /* Kill all tracees on abnormal termination * signals. This ensures no process is left * untraced. */ signal_action.sa_sigaction = kill_all_tracees2; break; case SIGUSR1: case SIGUSR2: /* Print on stderr the complete talloc * hierarchy, useful for debug purpose. */ signal_action.sa_sigaction = print_talloc_hierarchy; break; case SIGCHLD: case SIGCONT: case SIGSTOP: case SIGTSTP: case SIGTTIN: case SIGTTOU: /* The default action is OK for these signals, * they are related to tty and job control. */ continue; default: /* Ignore all other signals, including * terminating ones (^C for instance). */ signal_action.sa_sigaction = (void *)SIG_IGN; break; } status = sigaction(signum, &signal_action, NULL); if (status < 0 && errno != EINVAL) notice(NULL, WARNING, SYSTEM, "sigaction(%d)", signum); } while (1) { int tracee_status; Tracee *tracee; int signal; pid_t pid; /* Wait for the next tracee's stop. */ pid = waitpid(-1, &tracee_status, __WALL); if (pid < 0) { if (errno != ECHILD) { notice(NULL, ERROR, SYSTEM, "waitpid()"); return EXIT_FAILURE; } break; } /* Get information about this tracee. */ tracee = get_tracee(NULL, pid, true); assert(tracee != NULL); tracee->running = false; status = notify_extensions(tracee, NEW_STATUS, tracee_status, 0); if (status != 0) continue; if (tracee->as_ptracee.ptracer != NULL) { bool keep_stopped = handle_ptracee_event(tracee, tracee_status); if (keep_stopped) continue; } signal = handle_tracee_event(tracee, tracee_status); (void) restart_tracee(tracee, signal); } return last_exit_status; } /** * Handle the current event (@tracee_status) of the given @tracee. * This function returns the "computed" signal that should be used to * restart the given @tracee. */ int handle_tracee_event(Tracee *tracee, int tracee_status) { static bool seccomp_detected = false; pid_t pid = tracee->pid; long status; int signal; /* Don't overwrite restart_how if it is explicitly set * elsewhere, i.e in the ptrace emulation when single * stepping. */ if (tracee->restart_how == 0) { /* When seccomp is enabled, all events are restarted in * non-stop mode, but this default choice could be overwritten * later if necessary. The check against "sysexit_pending" * ensures PTRACE_SYSCALL (used to hit the exit stage under * seccomp) is not cleared due to an event that would happen * before the exit stage, eg. PTRACE_EVENT_EXEC for the exit * stage of execve(2). */ if (tracee->seccomp == ENABLED && !tracee->sysexit_pending) tracee->restart_how = PTRACE_CONT; else tracee->restart_how = PTRACE_SYSCALL; } /* Not a signal-stop by default. */ signal = 0; if (WIFEXITED(tracee_status)) { last_exit_status = WEXITSTATUS(tracee_status); VERBOSE(tracee, 1, "pid %d: exited with status %d", pid, last_exit_status); } else if (WIFSIGNALED(tracee_status)) { check_architecture(tracee); VERBOSE(tracee, (int) (last_exit_status != -1), "pid %d: terminated with signal %d", pid, WTERMSIG(tracee_status)); } else if (WIFSTOPPED(tracee_status)) { /* Don't use WSTOPSIG() to extract the signal * since it clears the PTRACE_EVENT_* bits. */ signal = (tracee_status & 0xfff00) >> 8; switch (signal) { static bool deliver_sigtrap = false; case SIGTRAP: { const unsigned long default_ptrace_options = ( PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK | PTRACE_O_TRACEVFORKDONE | PTRACE_O_TRACEEXEC | PTRACE_O_TRACECLONE | PTRACE_O_TRACEEXIT); /* Distinguish some events from others and * automatically trace each new process with * the same options. * * Note that only the first bare SIGTRAP is * related to the tracing loop, others SIGTRAP * carry tracing information because of * TRACE*FORK/CLONE/EXEC. */ if (deliver_sigtrap) break; /* Deliver this signal as-is. */ deliver_sigtrap = true; /* Try to enable seccomp mode 2... */ status = ptrace(PTRACE_SETOPTIONS, tracee->pid, NULL, default_ptrace_options | PTRACE_O_TRACESECCOMP); if (status < 0) { /* ... otherwise use default options only. */ status = ptrace(PTRACE_SETOPTIONS, tracee->pid, NULL, default_ptrace_options); if (status < 0) { notice(tracee, ERROR, SYSTEM, "ptrace(PTRACE_SETOPTIONS)"); exit(EXIT_FAILURE); } } } /* Fall through. */ case SIGTRAP | 0x80: signal = 0; /* This tracee got signaled then freed during the sysenter stage but the kernel reports the sysexit stage; just discard this spurious tracee/event. */ if (tracee->exe == NULL) { tracee->restart_how = PTRACE_CONT; return 0; } switch (tracee->seccomp) { case ENABLED: if (IS_IN_SYSENTER(tracee)) { /* sysenter: ensure the sysexit * stage will be hit under seccomp. */ tracee->restart_how = PTRACE_SYSCALL; tracee->sysexit_pending = true; } else { /* sysexit: the next sysenter * will be notified by seccomp. */ tracee->restart_how = PTRACE_CONT; tracee->sysexit_pending = false; } /* Fall through. */ case DISABLED: translate_syscall(tracee); /* This syscall has disabled seccomp. */ if (tracee->seccomp == DISABLING) { tracee->restart_how = PTRACE_SYSCALL; tracee->seccomp = DISABLED; } break; case DISABLING: /* Seccomp was disabled by the * previous syscall, but its sysenter * stage was already handled. */ tracee->seccomp = DISABLED; if (IS_IN_SYSENTER(tracee)) tracee->status = 1; break; } break; case SIGTRAP | PTRACE_EVENT_SECCOMP2 << 8: case SIGTRAP | PTRACE_EVENT_SECCOMP << 8: { unsigned long flags = 0; signal = 0; if (!seccomp_detected) { VERBOSE(tracee, 1, "ptrace acceleration (seccomp mode 2) enabled"); tracee->seccomp = ENABLED; seccomp_detected = true; } /* Use the common ptrace flow if seccomp was * explicitely disabled for this tracee. */ if (tracee->seccomp != ENABLED) break; status = ptrace(PTRACE_GETEVENTMSG, tracee->pid, NULL, &flags); if (status < 0) break; /* Use the common ptrace flow when * sysexit has to be handled. */ if ((flags & FILTER_SYSEXIT) != 0) { tracee->restart_how = PTRACE_SYSCALL; break; } /* Otherwise, handle the sysenter * stage right now. */ tracee->restart_how = PTRACE_CONT; translate_syscall(tracee); /* This syscall has disabled seccomp, so move * the ptrace flow back to the common path to * ensure its sysexit will be handled. */ if (tracee->seccomp == DISABLING) tracee->restart_how = PTRACE_SYSCALL; break; } case SIGTRAP | PTRACE_EVENT_VFORK << 8: signal = 0; (void) new_child(tracee, CLONE_VFORK); break; case SIGTRAP | PTRACE_EVENT_FORK << 8: case SIGTRAP | PTRACE_EVENT_CLONE << 8: signal = 0; (void) new_child(tracee, 0); break; case SIGTRAP | PTRACE_EVENT_VFORK_DONE << 8: case SIGTRAP | PTRACE_EVENT_EXEC << 8: case SIGTRAP | PTRACE_EVENT_EXIT << 8: signal = 0; break; case SIGSTOP: /* Stop this tracee until PRoot has received * the EVENT_*FORK|CLONE notification. */ if (tracee->exe == NULL) { tracee->sigstop = SIGSTOP_PENDING; signal = -1; } /* For each tracee, the first SIGSTOP * is only used to notify the tracer. */ if (tracee->sigstop == SIGSTOP_IGNORED) { tracee->sigstop = SIGSTOP_ALLOWED; signal = 0; } break; default: /* Deliver this signal as-is. */ break; } } /* Clear the pending event, if any. */ tracee->as_ptracee.event4.proot.pending = false; return signal; } /** * Restart the given @tracee with the specified @signal. This * function returns false if the tracee was not restarted (error or * put in the "waiting for ptracee" state), otherwise true. */ bool restart_tracee(Tracee *tracee, int signal) { int status; /* Put in the "stopped"/"waiting for ptracee" state?. */ if (tracee->as_ptracer.wait_pid != 0 || signal == -1) return false; /* Restart the tracee and stop it at the next instruction, or * at the next entry or exit of a system call. */ status = ptrace(tracee->restart_how, tracee->pid, NULL, signal); if (status < 0) { /* The process died in a syscall. */ TALLOC_FREE(tracee); return false; } tracee->restart_how = 0; tracee->running = true; return true; } care-2.2.1/src/tracee/tracee.h0000644000175000017500000001775612430615044015467 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef TRACEE_H #define TRACEE_H #include /* pid_t, size_t, */ #include /* struct user*, */ #include /* bool, */ #include /* LIST_*, */ #include /* enum __ptrace_request */ #include /* talloc_*, */ #include "arch.h" /* word_t, user_regs_struct, */ #include "compat.h" typedef enum { CURRENT = 0, ORIGINAL = 1, MODIFIED = 2, NB_REG_VERSION } RegVersion; struct bindings; struct extensions; struct direct_ptracees; struct chained_syscalls; /* Information related to a file-system name-space. */ typedef struct { struct { /* List of bindings as specified by the user but not canonicalized yet. */ struct bindings *pending; /* List of bindings canonicalized and sorted in the "guest" order. */ struct bindings *guest; /* List of bindings canonicalized and sorted in the "host" order. */ struct bindings *host; } bindings; /* Current working directory, à la /proc/self/pwd. */ char *cwd; } FileSystemNameSpace; /* Virtual heap, emulated with a regular memory mapping. */ typedef struct { word_t base; size_t size; size_t prealloc_size; bool disabled; } Heap; /* Information related to a tracee process. */ typedef struct tracee { /********************************************************************** * Private resources * **********************************************************************/ /* Link for the list of all tracees. */ LIST_ENTRY(tracee) link; /* Process identifier. */ pid_t pid; /* Is it currently running or not? */ bool running; /* Parent of this tracee, NULL if none. */ struct tracee *parent; /* Is it a "clone", i.e has the same parent as its creator. */ bool clone; /* Support for ptrace emulation (tracer side). */ struct { size_t nb_ptracees; LIST_HEAD(zombies, tracee) zombies; struct direct_ptracees *direct_ptracees; pid_t wait_pid; word_t wait_options; enum { DOESNT_WAIT = 0, WAITS_IN_KERNEL, WAITS_IN_PROOT } waits_in; } as_ptracer; /* Support for ptrace emulation (tracee side). */ struct { struct tracee *ptracer; struct { #define STRUCT_EVENT struct { int value; bool pending; } STRUCT_EVENT proot; STRUCT_EVENT ptracer; } event4; bool tracing_started; bool ignore_syscall; word_t options; bool is_zombie; bool is_load_pending; } as_ptracee; /* Current status: * 0: enter syscall * 1: exit syscall no error * -errno: exit syscall with error. */ int status; #define IS_IN_SYSENTER(tracee) ((tracee)->status == 0) #define IS_IN_SYSEXIT(tracee) (!IS_IN_SYSENTER(tracee)) #define IS_IN_SYSEXIT2(tracee, sysnum) (IS_IN_SYSEXIT(tracee) \ && get_sysnum((tracee), ORIGINAL) == sysnum) /* How this tracee is restarted. */ enum __ptrace_request restart_how; /* Value of the tracee's general purpose registers. */ struct user_regs_struct _regs[NB_REG_VERSION]; bool _regs_were_changed; bool restore_original_regs; /* State for the special handling of SIGSTOP. */ enum { SIGSTOP_IGNORED = 0, /* Ignore SIGSTOP (once the parent is known). */ SIGSTOP_ALLOWED, /* Allow SIGSTOP (once the parent is known). */ SIGSTOP_PENDING, /* Block SIGSTOP until the parent is unknown. */ } sigstop; /* Context used to collect all the temporary dynamic memory * allocations. */ TALLOC_CTX *ctx; /* Context used to collect all dynamic memory allocations that * should be released once this tracee is freed. */ TALLOC_CTX *life_context; /* Note: I could rename "ctx" in "event_span" and * "life_context" in "life_span". */ /* Specify the type of the final component during the * initialization of a binding. This variable is first * defined in bind_path() then used in build_glue(). */ mode_t glue_type; /* During a sub-reconfiguration, the new setup is relatively * to @tracee's file-system name-space. Also, @paths holds * its $PATH environment variable in order to emulate the * execvp(3) behavior. */ struct { struct tracee *tracee; const char *paths; } reconf; /* Unrequested syscalls inserted by PRoot after an actual * syscall. */ struct { struct chained_syscalls *syscalls; bool force_final_result; word_t final_result; } chain; /********************************************************************** * Private but inherited resources * **********************************************************************/ /* Verbose level. */ int verbose; /* State of the seccomp acceleration for this tracee. */ enum { DISABLED = 0, DISABLING, ENABLED } seccomp; /* Ensure the sysexit stage is always hit under seccomp. */ bool sysexit_pending; /********************************************************************** * Shared or private resources, depending on the CLONE_FS/VM flags. * **********************************************************************/ /* Information related to a file-system name-space. */ FileSystemNameSpace *fs; /* Virtual heap, emulated with a regular memory mapping. */ Heap *heap; /********************************************************************** * Shared resources until the tracee makes a call to execve(). * **********************************************************************/ /* Path to the executable, à la /proc/self/exe. */ char *exe; /* Initial command-line, à la /proc/self/cmdline. */ char **cmdline; /********************************************************************** * Shared or private resources, depending on the (re-)configuration * **********************************************************************/ /* Runner command-line. */ char **qemu; /* Can the ELF interpreter for QEMU be safely skipped? */ bool qemu_pie_workaround; /* Path to glue between the guest rootfs and the host rootfs. */ const char *glue; /* List of extensions enabled for this tracee. */ struct extensions *extensions; /********************************************************************** * Shared but read-only resources * **********************************************************************/ /* For the mixed-mode, the guest LD_LIBRARY_PATH is saved * during the "guest -> host" transition, in order to be * restored during the "host -> guest" transition (only if the * host LD_LIBRARY_PATH hasn't changed). */ const char *host_ldso_paths; const char *guest_ldso_paths; /* For diagnostic purpose. */ const char *tool_name; } Tracee; #define HOST_ROOTFS "/host-rootfs" #define TRACEE(a) talloc_get_type_abort(talloc_parent(talloc_parent(a)), Tracee) extern Tracee *get_tracee(const Tracee *tracee, pid_t pid, bool create); extern Tracee *get_stopped_ptracee(const Tracee *ptracer, pid_t pid, bool only_with_pevent, word_t wait_options); extern bool has_ptracees(const Tracee *ptracer, pid_t pid, word_t wait_options); extern int new_child(Tracee *parent, word_t clone_flags); extern Tracee *new_dummy_tracee(TALLOC_CTX *context); extern int swap_config(Tracee *tracee1, Tracee *tracee2); extern int parse_config(Tracee *tracee, size_t argc, char *argv[]); extern void kill_all_tracees(); #endif /* TRACEE_H */ care-2.2.1/src/tracee/tracee.c0000644000175000017500000004162212430615044015447 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* CLONE_*, */ #include /* pid_t, size_t, */ #include /* NULL, */ #include /* assert(3), */ #include /* bzero(3), */ #include /* bool, true, false, */ #include /* LIST_*, */ #include /* talloc_*, */ #include /* kill(2), SIGKILL, */ #include /* ptrace(2), PTRACE_*, */ #include /* E*, */ #include "tracee/tracee.h" #include "tracee/reg.h" #include "path/binding.h" #include "syscall/sysnum.h" #include "tracee/event.h" #include "ptrace/ptrace.h" #include "ptrace/wait.h" #include "extension/extension.h" #include "cli/notice.h" #include "compat.h" typedef LIST_HEAD(tracees, tracee) Tracees; static Tracees tracees; /** * Remove @zombie from its parent's list of zombies. Note: this is a * talloc destructor. */ static int remove_zombie(Tracee *zombie) { Tracee *ptracer; ptracer = talloc_get_type_abort(talloc_parent(zombie), Tracee); PTRACER.nb_ptracees--; LIST_REMOVE(zombie, link); return 0; } /** * Perform some specific treatments against @pointer according to its * type, before it gets unlinked from @tracee_->life_context. */ static void clean_life_span_object(const void *pointer, int depth UNUSED, int max_depth UNUSED, int is_ref UNUSED, void *tracee_) { Binding *binding; Tracee *tracee; tracee = talloc_get_type_abort(tracee_, Tracee); /* So far, only bindings need a special treatment. */ binding = talloc_get_type(pointer, Binding); if (binding != NULL) remove_binding_from_all_lists(tracee, binding); } /** * Remove @tracee from the list of tracees and update all of its * children & ptracees, and its ptracer. Note: this is a talloc * destructor. */ static int remove_tracee(Tracee *tracee) { Tracee *relative; Tracee *ptracer; int status; int event; LIST_REMOVE(tracee, link); /* Clean objects that are linked to this tracee's life * span. */ talloc_report_depth_cb(tracee->life_context, 0, 100, clean_life_span_object, tracee); /* This could be optimize by using a dedicated list of * children and ptracees. */ LIST_FOREACH(relative, &tracees, link) { /* Its children are now orphan. */ if (relative->parent == tracee) relative->parent = NULL; /* Its tracees are now free. */ if (relative->as_ptracee.ptracer == tracee) { /* Release the pending event, if any. */ relative->as_ptracee.ptracer = NULL; if (relative->as_ptracee.event4.proot.pending) { event = handle_tracee_event(relative, relative->as_ptracee.event4.proot.value); (void) restart_tracee(relative, event); } else if (relative->as_ptracee.event4.ptracer.pending) { event = relative->as_ptracee.event4.proot.value; (void) restart_tracee(relative, event); } bzero(&relative->as_ptracee, sizeof(relative->as_ptracee)); } } /* Nothing else to do if it's not a ptracee. */ ptracer = tracee->as_ptracee.ptracer; if (ptracer == NULL) return 0; /* Sanity checks. */ assert(ptracer != tracee); assert(PTRACER.nb_ptracees > 0); /* Zombify this ptracee until its ptracer is notified about * its death. */ event = tracee->as_ptracee.event4.ptracer.value; if (tracee->as_ptracee.event4.ptracer.pending && (WIFEXITED(event) || WIFSIGNALED(event))) { Tracee *zombie; zombie = new_dummy_tracee(ptracer); if (zombie != NULL) { LIST_INSERT_HEAD(&PTRACER.zombies, zombie, link); talloc_set_destructor(zombie, remove_zombie); zombie->as_ptracee.event4.ptracer.pending = true; zombie->as_ptracee.event4.ptracer.value = event; zombie->as_ptracee.is_zombie = true; zombie->clone = tracee->clone; zombie->pid = tracee->pid; return 0; } /* Fallback to the common path. */ } /* Wake its ptracer if there's nothing else to wait for. */ PTRACER.nb_ptracees--; if (PTRACER.nb_ptracees == 0 && PTRACER.wait_pid != 0) { /* Update the return value of ptracer's wait(2). */ poke_reg(ptracer, SYSARG_RESULT, -ECHILD); /* Don't forget to write its register cache back. */ status = push_regs(ptracer); if (status < 0) { TALLOC_FREE(ptracer); return 0; } PTRACER.wait_pid = 0; (void) restart_tracee(ptracer, 0); } return 0; } /** * Allocate a new entry for a dummy tracee (no pid, no destructor, not * in the list of tracees, ...). The new allocated memory is attached * to the given @context. This function returns NULL if an error * occurred (ENOMEM), otherwise it returns the newly allocated * structure. */ Tracee *new_dummy_tracee(TALLOC_CTX *context) { Tracee *tracee; tracee = talloc_zero(context, Tracee); if (tracee == NULL) return NULL; /* Allocate a memory collector. */ tracee->ctx = talloc_new(tracee); if (tracee->ctx == NULL) goto no_mem; /* By default new tracees have an empty file-system * name-space and heap. */ tracee->fs = talloc_zero(tracee, FileSystemNameSpace); tracee->heap = talloc_zero(tracee, Heap); if (tracee->fs == NULL || tracee->heap == NULL) goto no_mem; return tracee; no_mem: TALLOC_FREE(tracee); return NULL; } /** * Allocate a new entry for the tracee @pid, then set its destructor * and add it to the list of tracees. This function returns NULL if * an error occurred (ENOMEM), otherwise it returns the newly * allocated structure. */ static Tracee *new_tracee(pid_t pid) { Tracee *tracee; tracee = new_dummy_tracee(NULL); if (tracee == NULL) return NULL; talloc_set_destructor(tracee, remove_tracee); tracee->pid = pid; LIST_INSERT_HEAD(&tracees, tracee, link); tracee->life_context = talloc_new(tracee); return tracee; } /** * Return the first [stopped?] tracee with the given * @pid (-1 for any) which has the given @ptracer, and which has a * pending event for its ptracer if @only_with_pevent is true. See * wait(2) manual for the meaning of @wait_options. This function * returns NULL if there's no such ptracee. */ static Tracee *get_ptracee(const Tracee *ptracer, pid_t pid, bool only_stopped, bool only_with_pevent, word_t wait_options) { Tracee *ptracee; /* Return zombies first. */ LIST_FOREACH(ptracee, &PTRACER.zombies, link) { /* Not the ptracee you're looking for? */ if (pid != ptracee->pid && pid != -1) continue; /* Not the expected kind of cloned process? */ if (!EXPECTED_WAIT_CLONE(wait_options, ptracee)) continue; return ptracee; } LIST_FOREACH(ptracee, &tracees, link) { /* Discard tracees that don't have this ptracer. */ if (PTRACEE.ptracer != ptracer) continue; /* Not the ptracee you're looking for? */ if (pid != ptracee->pid && pid != -1) continue; /* Not the expected kind of cloned process? */ if (!EXPECTED_WAIT_CLONE(wait_options, ptracee)) continue; /* No need to do more checks if its stopped state * doesn't matter. Be careful when using such * maybe-running tracee. */ if (!only_stopped) return ptracee; /* Is this tracee in the stopped state? */ if (ptracee->running) continue; /* Has a pending event for its ptracer? */ if (PTRACEE.event4.ptracer.pending || !only_with_pevent) return ptracee; /* No need to go further if the specific tracee isn't * in the expected state? */ if (pid == ptracee->pid) return NULL; } return NULL; } /** * Wrapper for get_ptracee(), this ensures only a stopped tracee is * returned (or NULL). */ Tracee *get_stopped_ptracee(const Tracee *ptracer, pid_t pid, bool only_with_pevent, word_t wait_options) { return get_ptracee(ptracer, pid, true, only_with_pevent, wait_options); } /** * Wrapper for get_ptracee(), this ensures no running tracee is * returned. */ bool has_ptracees(const Tracee *ptracer, pid_t pid, word_t wait_options) { return (get_ptracee(ptracer, pid, false, false, wait_options) != NULL); } /** * Return the entry related to the tracee @pid. If no entry were * found, a new one is created if @create is true, otherwise NULL is * returned. */ Tracee *get_tracee(const Tracee *current_tracee, pid_t pid, bool create) { Tracee *tracee; /* Don't reset the memory collector if the searched tracee is * the current one: there's likely pointers to the * sub-allocated data in the caller. */ if (current_tracee != NULL && current_tracee->pid == pid) return (Tracee *)current_tracee; LIST_FOREACH(tracee, &tracees, link) { if (tracee->pid == pid) { /* Flush then allocate a new memory collector. */ TALLOC_FREE(tracee->ctx); tracee->ctx = talloc_new(tracee); return tracee; } } return (create ? new_tracee(pid) : NULL); } /** * Make new @parent's child inherit from it. Depending on * @clone_flags, some information are copied or shared. This function * returns -errno if an error occured, otherwise 0. */ int new_child(Tracee *parent, word_t clone_flags) { int ptrace_options; unsigned long pid; Tracee *child; int status; /* If the tracee calls clone(2) with the CLONE_VFORK flag, * PTRACE_EVENT_VFORK will be delivered instead [...]; * otherwise if the tracee calls clone(2) with the exit signal * set to SIGCHLD, PTRACE_EVENT_FORK will be delivered [...] * * -- ptrace(2) man-page * * That means we have to check if it's actually a clone(2) in * order to get the right flags. */ status = fetch_regs(parent); if (status >= 0 && get_sysnum(parent, CURRENT) == PR_clone) clone_flags = peek_reg(parent, CURRENT, SYSARG_1); /* Get the pid of the parent's new child. */ status = ptrace(PTRACE_GETEVENTMSG, parent->pid, NULL, &pid); if (status < 0 || pid == 0) { notice(parent, WARNING, SYSTEM, "ptrace(GETEVENTMSG)"); return status; } child = get_tracee(parent, (pid_t) pid, true); if (child == NULL) { notice(parent, WARNING, SYSTEM, "running out of memory"); return -ENOMEM; } /* Sanity checks. */ assert(child != NULL && child->exe == NULL && child->cmdline == NULL && child->fs->cwd == NULL && child->fs->bindings.pending == NULL && child->fs->bindings.guest == NULL && child->fs->bindings.host == NULL && child->qemu == NULL && child->glue == NULL && child->parent == NULL && child->as_ptracee.ptracer == NULL); child->verbose = parent->verbose; child->seccomp = parent->seccomp; child->sysexit_pending = parent->sysexit_pending; /* If CLONE_VM is set, the calling process and the child * process run in the same memory space [...] any memory * mapping or unmapping performed with mmap(2) or munmap(2) by * the child or calling process also affects the other * process. * * If CLONE_VM is not set, the child process runs in a * separate copy of the memory space of the calling process at * the time of clone(). Memory writes or file * mappings/unmappings performed by one of the processes do * not affect the other, as with fork(2). * * -- clone(2) man-page */ TALLOC_FREE(child->heap); child->heap = ((clone_flags & CLONE_VM) != 0) ? talloc_reference(child, parent->heap) : talloc_memdup(child, parent->heap, sizeof(Heap)); if (child->heap == NULL) return -ENOMEM; /* If CLONE_PARENT is set, then the parent of the new child * (as returned by getppid(2)) will be the same as that of the * calling process. * * If CLONE_PARENT is not set, then (as with fork(2)) the * child's parent is the calling process. * * -- clone(2) man-page */ child->clone = ((clone_flags & CLONE_PARENT) != 0); if (child->clone) child->parent = parent->parent; else child->parent = parent; /* Depending on how the new process is created, it may be * automatically traced by the parent's tracer. */ ptrace_options = ( clone_flags == 0 ? PTRACE_O_TRACEFORK : (clone_flags & 0xFF) == SIGCHLD ? PTRACE_O_TRACEFORK : (clone_flags & CLONE_VFORK) != 0 ? PTRACE_O_TRACEVFORK : PTRACE_O_TRACECLONE); if (parent->as_ptracee.ptracer != NULL && ( (ptrace_options & parent->as_ptracee.options) != 0 || (clone_flags & CLONE_PTRACE) != 0)) { Tracee *ptracer = parent->as_ptracee.ptracer; child->as_ptracee.ptracer = ptracer; PTRACER.nb_ptracees++; /* All these flags are inheritable, no matter why this * child is being traced. */ child->as_ptracee.options |= (parent->as_ptracee.options & ( PTRACE_O_TRACECLONE | PTRACE_O_TRACEEXEC | PTRACE_O_TRACEEXIT | PTRACE_O_TRACEFORK | PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEVFORK | PTRACE_O_TRACEVFORKDONE)); } /* If CLONE_FS is set, the parent and the child process share * the same file system information. This includes the root * of the file system, the current working directory, and the * umask. Any call to chroot(2), chdir(2), or umask(2) * performed by the parent process or the child process also * affects the other process. * * If CLONE_FS is not set, the child process works on a copy * of the file system information of the parent process at the * time of the clone() call. Calls to chroot(2), chdir(2), * umask(2) performed later by one of the processes do not * affect the other process. * * -- clone(2) man-page */ TALLOC_FREE(child->fs); if ((clone_flags & CLONE_FS) != 0) { /* File-system name-space is shared. */ child->fs = talloc_reference(child, parent->fs); } else { /* File-system name-space is copied. */ child->fs = talloc_zero(child, FileSystemNameSpace); if (child->fs == NULL) return -ENOMEM; child->fs->cwd = talloc_strdup(child->fs, parent->fs->cwd); if (child->fs->cwd == NULL) return -ENOMEM; talloc_set_name_const(child->fs->cwd, "$cwd"); /* Bindings are shared across file-system name-spaces since a * "mount --bind" made by a process affects all other processes * under Linux. Actually they are copied when a sub * reconfiguration occured (nested proot or chroot(2)). */ child->fs->bindings.guest = talloc_reference(child->fs, parent->fs->bindings.guest); child->fs->bindings.host = talloc_reference(child->fs, parent->fs->bindings.host); } /* The path to the executable and the command-line are unshared only * once the child process does a call to execve(2). */ child->exe = talloc_reference(child, parent->exe); child->cmdline = talloc_reference(child, parent->cmdline); child->qemu_pie_workaround = parent->qemu_pie_workaround; child->qemu = talloc_reference(child, parent->qemu); child->glue = talloc_reference(child, parent->glue); child->host_ldso_paths = talloc_reference(child, parent->host_ldso_paths); child->guest_ldso_paths = talloc_reference(child, parent->guest_ldso_paths); child->tool_name = parent->tool_name; inherit_extensions(child, parent, clone_flags); /* Restart the child tracee if it was already alive but * stopped until that moment. */ if (child->sigstop == SIGSTOP_PENDING) { bool keep_stopped = false; child->sigstop = SIGSTOP_ALLOWED; /* Notify its ptracer if it is ready to be traced. */ if (child->as_ptracee.ptracer != NULL) { /* Sanity check. */ assert(!child->as_ptracee.tracing_started); keep_stopped = handle_ptracee_event(child, __W_STOPCODE(SIGSTOP)); /* Note that this event was already handled by * PRoot since child->as_ptracee.ptracer was * NULL up to now. */ child->as_ptracee.event4.proot.pending = false; child->as_ptracee.event4.proot.value = 0; } if (!keep_stopped) (void) restart_tracee(child, 0); } return 0; } /** * Helper for swap_config(). */ static void reparent_config(Tracee *new_parent, Tracee *old_parent) { new_parent->verbose = old_parent->verbose; new_parent->qemu_pie_workaround = old_parent->qemu_pie_workaround; #define REPARENT(field) do { \ talloc_reparent(old_parent, new_parent, old_parent->field); \ new_parent->field = old_parent->field; \ } while(0); REPARENT(fs); REPARENT(exe); REPARENT(cmdline); REPARENT(qemu); REPARENT(glue); REPARENT(extensions); #undef REPARENT } /** * Swap configuration (pointers and parentality) between @tracee1 and @tracee2. */ int swap_config(Tracee *tracee1, Tracee *tracee2) { Tracee *tmp; tmp = talloc_zero(tracee1->ctx, Tracee); if (tmp == NULL) return -ENOMEM; reparent_config(tmp, tracee1); reparent_config(tracee1, tracee2); reparent_config(tracee2, tmp); return 0; } /* Send the KILL signal to all tracees. */ void kill_all_tracees() { Tracee *tracee; LIST_FOREACH(tracee, &tracees, link) kill(tracee->pid, SIGKILL); } care-2.2.1/src/tracee/mem.h0000644000175000017500000000724012430615044014765 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef TRACEE_MEM_H #define TRACEE_MEM_H #include /* PATH_MAX, */ #include /* pid_t, size_t, */ #include /* pid_t, size_t, */ #include /* struct iovec, */ #include /* ENAMETOOLONG, */ #include "arch.h" /* word_t, */ #include "tracee/tracee.h" extern int write_data(const Tracee *tracee, word_t dest_tracee, const void *src_tracer, word_t size); extern int writev_data(const Tracee *tracee, word_t dest_tracee, const struct iovec *src_tracer, int src_tracer_count); extern int read_data(const Tracee *tracee, void *dest_tracer, word_t src_tracee, word_t size); extern int read_string(const Tracee *tracee, char *dest_tracer, word_t src_tracee, word_t max_size); extern word_t peek_word(const Tracee *tracee, word_t address); extern void poke_word(const Tracee *tracee, word_t address, word_t value); extern word_t alloc_mem(Tracee *tracee, ssize_t size); extern int clear_mem(const Tracee *tracee, word_t address, size_t size); /** * Copy to @dest_tracer at most PATH_MAX bytes -- including the * end-of-string terminator -- from the string pointed to by * @src_tracee within the memory space of the @tracee process. This * function returns -errno on error, otherwise it returns the number * in bytes of the string, including the end-of-string terminator. */ static inline int read_path(const Tracee *tracee, char dest_tracer[PATH_MAX], word_t src_tracee) { int status; status = read_string(tracee, dest_tracer, src_tracee, PATH_MAX); if (status < 0) return status; if (status >= PATH_MAX) return -ENAMETOOLONG; return status; } /** * Generate a function that returns the value of the @type at the * given @address in the @tracee's memory space. The caller must test * errno to check if an error occured. */ #define GENERATE_peek(type) \ static inline type ## _t peek_ ## type(const Tracee *tracee, word_t address) \ { \ type ## _t result; \ errno = -read_data(tracee, &result, address, sizeof(type ## _t)); \ return result; \ } GENERATE_peek(uint8); GENERATE_peek(uint16); GENERATE_peek(uint32); GENERATE_peek(uint64); GENERATE_peek(int8); GENERATE_peek(int16); GENERATE_peek(int32); GENERATE_peek(int64); #undef GENERATE_peek /** * Generate a function that set the @type at the given @address in the * @tracee's memory space to the given @value. The caller must test * errno to check if an error occured. */ #define GENERATE_poke(type) \ static inline void poke_ ## type(const Tracee *tracee, word_t address, type ## _t value) \ { \ errno = -write_data(tracee, address, &value, sizeof(type ## _t)); \ } GENERATE_poke(uint8); GENERATE_poke(uint16); GENERATE_poke(uint32); GENERATE_poke(uint64); GENERATE_poke(int8); GENERATE_poke(int16); GENERATE_poke(int32); GENERATE_poke(int64); #undef GENERATE_poke #endif /* TRACEE_MEM_H */ care-2.2.1/src/tracee/abi.h0000644000175000017500000000614112430615044014741 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef TRACEE_ABI_H #define TRACEE_ABI_H #include #include /* offsetof(), */ #include "tracee/tracee.h" #include "tracee/reg.h" #include "arch.h" #include "attribute.h" typedef enum { ABI_DEFAULT = 0, ABI_2, /* x86_32 on x86_64. */ ABI_3, /* x32 on x86_64. */ NB_MAX_ABIS, } Abi; /** * Return the ABI currently used by the given @tracee. */ #if defined(ARCH_X86_64) static inline Abi get_abi(const Tracee *tracee) { /* The ABI can be changed by a syscall ("execve" typically), * however the change is only effective once the syscall has * *fully* returned, hence the use of _regs[ORIGINAL]. */ switch (tracee->_regs[ORIGINAL].cs) { case 0x23: return ABI_2; case 0x33: if (tracee->_regs[ORIGINAL].ds == 0x2B) return ABI_3; /* Fall through. */ default: return ABI_DEFAULT; } } /** * Return true if @tracee is a 32-bit process running on a 64-bit * kernel. */ static inline bool is_32on64_mode(const Tracee *tracee) { /* Unlike the ABI, 32-bit/64-bit mode change is effective * immediately, hence _regs[CURRENT].cs. */ switch (tracee->_regs[CURRENT].cs) { case 0x23: return true; case 0x33: if (tracee->_regs[ORIGINAL].ds == 0x2B) return true; /* Fall through. */ default: return false; } } #else static inline Abi get_abi(const Tracee *tracee UNUSED) { return ABI_DEFAULT; } static inline bool is_32on64_mode(const Tracee *tracee UNUSED) { return false; } #endif /** * Return the size of a word according to the ABI currently used by * the given @tracee. */ static inline size_t sizeof_word(const Tracee *tracee) { return (is_32on64_mode(tracee) ? sizeof(word_t) / 2 : sizeof(word_t)); } #include /** * Return the offset of the 'uid' field in a 'stat' structure * according to the ABI currently used by the given @tracee. */ static inline off_t offsetof_stat_uid(const Tracee *tracee) { return (is_32on64_mode(tracee) ? OFFSETOF_STAT_UID_32 : offsetof(struct stat, st_uid)); } /** * Return the offset of the 'gid' field in a 'stat' structure * according to the ABI currently used by the given @tracee. */ static inline off_t offsetof_stat_gid(const Tracee *tracee) { return (is_32on64_mode(tracee) ? OFFSETOF_STAT_GID_32 : offsetof(struct stat, st_gid)); } #endif /* TRACEE_ABI_H */ care-2.2.1/src/tracee/array.c0000644000175000017500000002561712430615044015330 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* ARG_MAX, */ #include /* assert(3), */ #include /* strlen(3), memcmp(3), memcpy(3), */ #include /* bzero(3), */ #include /* bool, true, false, */ #include /* E*, */ #include /* va_*, */ #include /* uint32_t, */ #include /* talloc_*, */ #include "arch.h" struct array_entry { /* Pointer (tracee's address space) to the current value, if * local == NULL. */ word_t remote; /* Pointer (tracer's address space) to the current value, if * local != NULL. */ void *local; }; #include "tracee/tracee.h" #include "tracee/array.h" #include "tracee/mem.h" #include "tracee/abi.h" #include "build.h" /** * Set *@value to the address of the data pointed to by the entry in * @array at the given @index. This function returns -errno when an * error occured, otherwise 0. */ int read_item_data(Array *array, size_t index, void **value) { int status; int size; assert(index < array->length); /* Already cached locally? */ if (array->_cache[index].local != NULL) goto end; /* Remote NULL is mapped to local NULL. */ if (array->_cache[index].remote == 0) { array->_cache[index].local = NULL; goto end; } size = sizeof_item(array, index); if (size < 0) return size; array->_cache[index].local = talloc_size(array, size); if (array->_cache[index].local == NULL) return -ENOMEM; /* Copy locally the remote data. */ status = read_data(TRACEE(array), array->_cache[index].local, array->_cache[index].remote, size); if (status < 0) { array->_cache[index].local = NULL; return status; } end: *value = array->_cache[index].local; return 0; } /** * Set *@value to the address of the string pointed to by the entry in * @array at the given @index. This function returns -errno when an * error occured, otherwise 0. */ int read_item_string(Array *array, size_t index, char **value) { char tmp[ARG_MAX]; int status; assert(index < array->length); /* Already cached locally? */ if (array->_cache[index].local != NULL) goto end; /* Remote NULL is mapped to local NULL. */ if (array->_cache[index].remote == 0) { array->_cache[index].local = NULL; goto end; } /* Copy locally the remote string into a temporary buffer. */ status = read_string(TRACEE(array), tmp, array->_cache[index].remote, ARG_MAX); if (status < 0) return status; if (status >= ARG_MAX) return -ENOMEM; /* Save the local string in a "persistent" buffer. */ array->_cache[index].local = talloc_strdup(array, tmp); if (array->_cache[index].local == NULL) return -ENOMEM; end: *value = array->_cache[index].local; return 0; } /** * This function returns the number of bytes of the string pointed to * by the entry in @array at the given @index, otherwise -errno when * an error occured. */ int sizeof_item_string(Array *array, size_t index) { char *value; int status; assert(index < array->length); status = read_item_string(array, index, &value); if (status < 0) return status; if (value == NULL) return 0; return strlen(value) + 1; } /** * This function returns 1 or 0 depending on the equivalence of the * @reference item and the one pointed to by the entry in @array at * the given @index, otherwise it returns -errno when an error * occured. */ int compare_item_generic(Array *array, size_t index, const void *reference) { void *value; int status; assert(index < array->length); status = read_item(array, index, &value); if (status < 0) return status; if (value == NULL && reference == NULL) return 1; if (value == NULL && reference != NULL) return 0; if (value != NULL && reference == NULL) return 0; status = sizeof_item(array, index); if (status < 0) return status; return (int)(memcmp(value, reference, status) == 0); } /** * This function returns the index in @array of the first item * equivalent to the @reference item, otherwise it returns -errno when * an error occured. */ int find_item(Array *array, const void *reference) { size_t i; for (i = 0; i < array->length; i++) { int status; status = compare_item(array, i, reference); if (status < 0) return status; if (status != 0) break; } return i; } /** * Make the entry at the given @index in @array points to a copy of * the string pointed to by @value. This function returns -errno when * an error occured, otherwise 0. */ int write_item_string(Array *array, size_t index, const char *value) { assert(index < array->length); array->_cache[index].local = talloc_strdup(array, value); if (array->_cache[index].local == NULL) return -ENOMEM; return 0; } /** * Make the @nb_items entries at the given @index in @array points to * a copy of the item pointed to by the variadic arguments. This * function returns -errno when an error occured, otherwise 0. */ int write_items(Array *array, size_t index, size_t nb_items, ...) { va_list va_items; int status; size_t i; va_start(va_items, nb_items); for (i = 0; i < nb_items; i++) { void *value = va_arg(va_items, void *); status = write_item(array, index + i, value); if (status < 0) goto end; } status = 0; end: va_end(va_items); return status; } /** * Resize the @array at the given @index by the @delta_nb_entries. * This function returns -errno when an error occured, otherwise 0. */ int resize_array(Array *array, size_t index, ssize_t delta_nb_entries) { size_t nb_moved_entries; size_t new_length; void *tmp; assert(index < array->length); if (delta_nb_entries == 0) return 0; new_length = array->length + delta_nb_entries; nb_moved_entries = array->length - index; if (delta_nb_entries > 0) { tmp = talloc_realloc(array, array->_cache, ArrayEntry, new_length); if (tmp == NULL) return -ENOMEM; array->_cache = tmp; memmove(array->_cache + index + delta_nb_entries, array->_cache + index, nb_moved_entries * sizeof(ArrayEntry)); bzero(array->_cache + index, delta_nb_entries * sizeof(ArrayEntry)); } else { assert(delta_nb_entries <= 0); assert(index >= (size_t) -delta_nb_entries); memmove(array->_cache + index + delta_nb_entries, array->_cache + index, nb_moved_entries * sizeof(ArrayEntry)); tmp = talloc_realloc(array, array->_cache, ArrayEntry, new_length); if (tmp == NULL) return -ENOMEM; array->_cache = tmp; } array->length = new_length; return 0; } /** * Copy and cache into @array the pointer table pointed to by @reg * from the @tracee memory space general. Only the first @nb_entries * are copied, unless it is 0 then all the entries up to the NULL * pointer are copied. This function returns -errno when an error * occured, otherwise 0. */ int fetch_array(Tracee *tracee, Array **array_, Reg reg, size_t nb_entries) { word_t pointer = (word_t)-1; word_t address; Array *array; size_t i; assert(array_ != NULL); *array_ = talloc_zero(tracee->ctx, Array); if (*array_ == NULL) return -ENOMEM; array = *array_; address = peek_reg(tracee, CURRENT, reg); for (i = 0; nb_entries != 0 ? i < nb_entries : pointer != 0; i++) { void *tmp = talloc_realloc(array, array->_cache, ArrayEntry, i + 1); if (tmp == NULL) return -ENOMEM; array->_cache = tmp; pointer = peek_word(tracee, address + i * sizeof_word(tracee)); if (errno != 0) return -errno; array->_cache[i].remote = pointer; array->_cache[i].local = NULL; } array->length = i; /* By default, assume it is an array of string pointers. */ array->read_item = (read_item_t)read_item_string; array->sizeof_item = sizeof_item_string; array->write_item = (write_item_t)write_item_string; /* By default, use generic callbacks: they rely on * array->read_item() and array->sizeof_item(). */ array->compare_item = compare_item_generic; return 0; } /** * Copy -- if needed -- the pointer table and new items cached by * @array into the tracee's memory, and make @reg points to the new * pointer table. This function returns -errno if an error occured, * otherwise 0. */ int push_array(Array *array, Reg reg) { Tracee *tracee; struct iovec *local; size_t local_count; size_t total_size; word_t *pod_array; word_t tracee_ptr; int status; size_t i; /* Nothing to do, for sure. */ if (array == NULL) return 0; tracee = TRACEE(array); /* The pointer table is a POD array in the tracee's memory. */ pod_array = talloc_zero_size(tracee->ctx, array->length * sizeof_word(tracee)); if (pod_array == NULL) return -ENOMEM; /* There's one vector per modified item + one vector for the * pod array. */ local = talloc_zero_array(tracee->ctx, struct iovec, array->length + 1); if (local == NULL) return -ENOMEM; /* The pod array is expected to be at the beginning of the * allocated memory by the caller. */ total_size = array->length * sizeof_word(tracee); local[0].iov_base = pod_array; local[0].iov_len = total_size; local_count = 1; /* Create one vector for each modified item. */ for (i = 0; i < array->length; i++) { ssize_t size; if (array->_cache[i].local == NULL) continue; /* At this moment, we only know the offsets in the * tracee's memory block. */ array->_cache[i].remote = total_size; size = sizeof_item(array, i); if (size < 0) return size; total_size += size; local[local_count].iov_base = array->_cache[i].local; local[local_count].iov_len = size; local_count++; } /* Nothing has changed, don't update anything. */ if (local_count == 1) return 0; assert(local_count < array->length + 1); /* Modified items and the pod array are stored in a tracee's * memory block. */ tracee_ptr = alloc_mem(tracee, total_size); if (tracee_ptr == 0) return -E2BIG; /* Now, we know the absolute addresses in the tracee's * memory. */ for (i = 0; i < array->length; i++) { if (array->_cache[i].local != NULL) array->_cache[i].remote += tracee_ptr; if (is_32on64_mode(tracee)) ((uint32_t *)pod_array)[i] = array->_cache[i].remote; else pod_array[i] = array->_cache[i].remote; } /* Write all the modified items and the pod array at once. */ status = writev_data(tracee, tracee_ptr, local, local_count); if (status < 0) return status; poke_reg(tracee, reg, tracee_ptr); return 0; } care-2.2.1/src/arch.h0000644000175000017500000001027612430615044013664 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* linux.git:c0a3a20b */ #include /* AUDIT_ARCH_*, */ #ifndef ARCH_H #define ARCH_H typedef unsigned long word_t; #define SYSCALL_AVOIDER ((word_t) -2) #define SYSTRAP_NUM SYSARG_NUM #if !defined(ARCH_X86_64) && !defined(ARCH_ARM_EABI) && !defined(ARCH_X86) && !defined(ARCH_SH4) # if defined(__x86_64__) # define ARCH_X86_64 1 # elif defined(__ARM_EABI__) # define ARCH_ARM_EABI 1 # elif defined(__aarch64__) # define ARCH_ARM64 1 # elif defined(__arm__) # error "Only EABI is currently supported for ARM" # elif defined(__i386__) # define ARCH_X86 1 # elif defined(__SH4__) # define ARCH_SH4 1 # else # error "Unsupported architecture" # endif #endif /* Architecture specific definitions. */ #if defined(ARCH_X86_64) #define SYSNUMS_HEADER1 "syscall/sysnums-x86_64.h" #define SYSNUMS_HEADER2 "syscall/sysnums-i386.h" #define SYSNUMS_HEADER3 "syscall/sysnums-x32.h" #define SYSNUMS_ABI1 sysnums_x86_64 #define SYSNUMS_ABI2 sysnums_i386 #define SYSNUMS_ABI3 sysnums_x32 #undef SYSTRAP_NUM #define SYSTRAP_NUM SYSARG_RESULT #define SYSTRAP_SIZE 2 #define SECCOMP_ARCHS { \ { .value = AUDIT_ARCH_X86_64, .nb_abis = 2, .abis = { ABI_DEFAULT, ABI_3 } }, \ { .value = AUDIT_ARCH_I386, .nb_abis = 1, .abis = { ABI_2 } }, \ } #define HOST_ELF_MACHINE {62, 3, 6, 0} #define RED_ZONE_SIZE 128 #define OFFSETOF_STAT_UID_32 24 #define OFFSETOF_STAT_GID_32 28 #elif defined(ARCH_ARM_EABI) #define SYSNUMS_HEADER1 "syscall/sysnums-arm.h" #define SYSNUMS_ABI1 sysnums_arm #define SYSTRAP_SIZE 4 #define SECCOMP_ARCHS { { .value = AUDIT_ARCH_ARM, .nb_abis = 1, .abis = { ABI_DEFAULT } } } #define user_regs_struct user_regs #define HOST_ELF_MACHINE {40, 0}; #define RED_ZONE_SIZE 0 #define OFFSETOF_STAT_UID_32 0 #define OFFSETOF_STAT_GID_32 0 #define EM_ARM 40 /* The syscall number has to be valid on ARM, so use tuxcall(2) as * the "void" syscall since it has no side effects. */ #undef SYSCALL_AVOIDER #define SYSCALL_AVOIDER ((word_t) 222) #elif defined(ARCH_ARM64) #define SYSNUMS_HEADER1 "syscall/sysnums-arm64.h" #define SYSNUMS_ABI1 sysnums_arm64 #define SYSTRAP_SIZE 4 #define SECCOMP_ARCHS { } #define HOST_ELF_MACHINE {183, 0}; #define RED_ZONE_SIZE 0 #define OFFSETOF_STAT_UID_32 0 #define OFFSETOF_STAT_GID_32 0 #elif defined(ARCH_X86) #define SYSNUMS_HEADER1 "syscall/sysnums-i386.h" #define SYSNUMS_ABI1 sysnums_i386 #undef SYSTRAP_NUM #define SYSTRAP_NUM SYSARG_RESULT #define SYSTRAP_SIZE 2 #define SECCOMP_ARCHS { { .value = AUDIT_ARCH_I386, .nb_abis = 1, .abis = { ABI_DEFAULT } } } #define HOST_ELF_MACHINE {3, 6, 0}; #define RED_ZONE_SIZE 0 #define OFFSETOF_STAT_UID_32 0 #define OFFSETOF_STAT_GID_32 0 #elif defined(ARCH_SH4) #define SYSNUMS_HEADER1 "syscall/sysnums-sh4.h" #define SYSNUMS_ABI1 sysnums_sh4 #define SYSTRAP_SIZE 2 #define SECCOMP_ARCHS { } #define user_regs_struct pt_regs #define HOST_ELF_MACHINE {42, 0}; #define RED_ZONE_SIZE 0 #define OFFSETOF_STAT_UID_32 0 #define OFFSETOF_STAT_GID_32 0 #define NO_MISALIGNED_ACCESS 1 #else #error "Unsupported architecture" #endif #endif /* ARCH_H */ care-2.2.1/src/.check_seccomp_filter.c0000644000175000017500000000171612430615044017152 0ustar ivoireivoire#include /* prctl(2), PR_* */ #include /* SECCOMP_MODE_FILTER, */ #include /* struct sock_*, */ #include /* AUDIT_ARCH_*, */ #include /* offsetof(3), */ int main(void) { const size_t arch_offset = offsetof(struct seccomp_data, arch); const size_t syscall_offset = offsetof(struct seccomp_data, nr); struct sock_fprog program; #define ARCH_NR AUDIT_ARCH_X86_64 struct sock_filter filter[] = { BPF_STMT(BPF_LD + BPF_W + BPF_ABS, arch_offset), BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, AUDIT_ARCH_X86_64, 0, 1), BPF_STMT(BPF_LD + BPF_W + BPF_ABS, syscall_offset), BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 1), BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_TRACE) }; program.filter = filter; program.len = sizeof(filter) / sizeof(struct sock_filter); (void) prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); (void) prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &program); return 1; } care-2.2.1/src/path/0000755000175000017500000000000012430615044013524 5ustar ivoireivoirecare-2.2.1/src/path/canon.h0000644000175000017500000000213712430615044014776 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef CANON_H #define CANON_H #include #include #include "tracee/tracee.h" extern int canonicalize(Tracee *tracee, const char *user_path, bool deref_final, char guest_path[PATH_MAX], unsigned int nb_recursion); #endif /* CANON_H */ care-2.2.1/src/path/canon.c0000644000175000017500000002034112430615044014766 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* pid_t */ #include /* PATH_MAX, */ #include /* MAXSYMLINKS, */ #include /* E*, */ #include /* lstat(2), S_ISREG(), */ #include /* access(2), lstat(2), */ #include /* string(3), */ #include /* assert(3), */ #include /* sscanf(3), */ #include "path/canon.h" #include "path/path.h" #include "path/binding.h" #include "path/glue.h" #include "path/proc.h" #include "extension/extension.h" /** * Resolve bindings (if any) in @guest_path and copy the translated * path into @host_path. Also, this function checks that a non-final * component is either a directory (returned value is 0) or a symlink * (returned value is 1), otherwise it returns -errno (-ENOENT or * -ENOTDIR). */ static inline int substitute_binding_stat(Tracee *tracee, Finality finality, const char guest_path[PATH_MAX], char host_path[PATH_MAX]) { struct stat statl; int status; strcpy(host_path, guest_path); status = substitute_binding(tracee, GUEST, host_path); if (status < 0) return status; /* Don't notify extensions during the initialization of a binding. */ if (tracee->glue_type == 0) { status = notify_extensions(tracee, HOST_PATH, (intptr_t)host_path, finality); if (status < 0) return status; } statl.st_mode = 0; status = lstat(host_path, &statl); /* Build the glue between the hostfs and the guestfs during * the initialization of a binding. */ if (status < 0 && tracee->glue_type != 0) { statl.st_mode = build_glue(tracee, guest_path, host_path, finality); if (statl.st_mode == 0) status = -1; } /* Return an error if a non-final component isn't a * directory nor a symlink. The error is "No such * file or directory" if this component doesn't exist, * otherwise the error is "Not a directory". */ if (!IS_FINAL(finality) && !S_ISDIR(statl.st_mode) && !S_ISLNK(statl.st_mode)) return (status < 0 ? -ENOENT : -ENOTDIR); return (S_ISLNK(statl.st_mode) ? 1 : 0); } /** * Copy in @guest_path the canonicalization (see `man 3 realpath`) of * @user_path regarding to @tracee->root. The path to canonicalize * could be either absolute or relative to @guest_path. When the last * component of @user_path is a link, it is dereferenced only if * @deref_final is true -- it is useful for syscalls like lstat(2). * The parameter @recursion_level should be set to 0 unless you know * what you are doing. This function returns -errno if an error * occured, otherwise it returns 0. */ int canonicalize(Tracee *tracee, const char *user_path, bool deref_final, char guest_path[PATH_MAX], unsigned int recursion_level) { char scratch_path[PATH_MAX]; Finality finality; const char *cursor; int status; /* Avoid infinite loop on circular links. */ if (recursion_level > MAXSYMLINKS) return -ELOOP; /* Sanity checks. */ assert(user_path != NULL); assert(guest_path != NULL); assert(user_path != guest_path); if (strnlen(guest_path, PATH_MAX) >= PATH_MAX) return -ENAMETOOLONG; if (user_path[0] != '/') { /* Ensure 'guest_path' contains an absolute base of * the relative `user_path`. */ if (guest_path[0] != '/') return -EINVAL; } else strcpy(guest_path, "/"); /* Canonicalize recursely 'user_path' into 'guest_path'. */ cursor = user_path; finality = NOT_FINAL; while (!IS_FINAL(finality)) { Comparison comparison; char component[NAME_MAX]; char host_path[PATH_MAX]; finality = next_component(component, &cursor); status = (int) finality; if (status < 0) return status; if (strcmp(component, ".") == 0) { if (IS_FINAL(finality)) finality = FINAL_DOT; continue; } if (strcmp(component, "..") == 0) { pop_component(guest_path); if (IS_FINAL(finality)) finality = FINAL_SLASH; continue; } status = join_paths(2, scratch_path, guest_path, component); if (status < 0) return status; /* Resolve bindings and check that a non-final * component exists and either is a directory or is a * symlink. For this latter case, we check that the * symlink points to a directory once it is * canonicalized, at the end of this loop. */ status = substitute_binding_stat(tracee, finality, scratch_path, host_path); if (status < 0) return status; /* Nothing special to do if it's not a link or if we * explicitly ask to not dereference 'user_path', as * required by syscalls like lstat(2). Obviously, this * later condition does not apply to intermediate path * components. Errors are explicitly ignored since * they should be handled by the caller. */ if (status <= 0 || (finality == FINAL_NORMAL && !deref_final)) { strcpy(scratch_path, guest_path); status = join_paths(2, guest_path, scratch_path, component); if (status < 0) return status; continue; } /* It's a link, so we have to dereference *and* * canonicalize to ensure we are not going outside the * new root. */ comparison = compare_paths("/proc", guest_path); switch (comparison) { case PATHS_ARE_EQUAL: case PATH1_IS_PREFIX: /* Some links in "/proc" are generated * dynamically by the kernel. PRoot has to * emulate some of them. */ status = readlink_proc(tracee, scratch_path, guest_path, component, comparison); switch (status) { case CANONICALIZE: /* The symlink is already dereferenced, * now canonicalize it. */ goto canon; case DONT_CANONICALIZE: /* If and only very final, this symlink * shouldn't be dereferenced nor canonicalized. */ if (finality == FINAL_NORMAL) { strcpy(guest_path, scratch_path); return 0; } break; default: if (status < 0) return status; } default: break; } status = readlink(host_path, scratch_path, sizeof(scratch_path)); if (status < 0) return status; else if (status == sizeof(scratch_path)) return -ENAMETOOLONG; scratch_path[status] = '\0'; /* Remove the leading "root" part if needed, it's * useful for "/proc/self/cwd/" for instance. */ status = detranslate_path(tracee, scratch_path, host_path); if (status < 0) return status; canon: /* Canonicalize recursively the referee in case it * is/contains a link, moreover if it is not an * absolute link then it is relative to * 'guest_path'. */ status = canonicalize(tracee, scratch_path, true, guest_path, recursion_level + 1); if (status < 0) return status; /* Check that a non-final canonicalized/dereferenced * symlink exists and is a directory. */ status = substitute_binding_stat(tracee, finality, guest_path, host_path); if (status < 0) return status; /* Here, 'guest_path' shouldn't be a symlink anymore, * unless it is a named file descriptor. */ assert(status != 1 || sscanf(guest_path, "/proc/%*d/fd/%d", &status) == 1); } /* At the exit stage of the first level of recursion, * `guest_path` is fully canonicalized but a terminating '/' * or a terminating '.' may be required to keep the initial * semantic of `user_path`. */ if (recursion_level == 0) { switch (finality) { case FINAL_NORMAL: break; case FINAL_SLASH: strcpy(scratch_path, guest_path); status = join_paths(2, guest_path, scratch_path, ""); if (status < 0) return status; break; case FINAL_DOT: strcpy(scratch_path, guest_path); status = join_paths(2, guest_path, scratch_path, "."); if (status < 0) return status; break; default: assert(0); } } return 0; } care-2.2.1/src/path/binding.h0000644000175000017500000000371512430615044015315 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef BINDING_H #define BINDING_H #include /* PATH_MAX, */ #include #include "tracee/tracee.h" #include "path.h" typedef struct binding { Path host; Path guest; bool need_substitution; bool must_exist; struct { CIRCLEQ_ENTRY(binding) pending; CIRCLEQ_ENTRY(binding) guest; CIRCLEQ_ENTRY(binding) host; } link; } Binding; typedef CIRCLEQ_HEAD(bindings, binding) Bindings; extern Binding *insort_binding3(const Tracee *tracee, const TALLOC_CTX *context, const char host_path[PATH_MAX], const char guest_path[PATH_MAX]); extern Binding *new_binding(Tracee *tracee, const char *host, const char *guest, bool must_exist); extern int initialize_bindings(Tracee *tracee); extern const char *get_path_binding(const Tracee* tracee, Side side, const char path[PATH_MAX]); extern Binding *get_binding(const Tracee *tracee, Side side, const char path[PATH_MAX]); extern const char *get_root(const Tracee* tracee); extern int substitute_binding(const Tracee* tracee, Side side, char path[PATH_MAX]); extern void remove_binding_from_all_lists(const Tracee *tracee, Binding *binding); #endif /* BINDING_H */ care-2.2.1/src/path/path.h0000644000175000017500000000607512430615044014641 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef PATH_H #define PATH_H #include /* pid_t, */ #include /* AT_FDCWD, */ #include /* PATH_MAX, */ #include #include "tracee/tracee.h" /* File type. */ typedef enum { REGULAR, SYMLINK, } Type; /* Path point-of-view. */ typedef enum { GUEST, HOST, /* Used for bindings as specified by the user but not * canonicalized yet (new_binding, initialize_binding). */ PENDING, } Side; /* Path with cached attributes. */ typedef struct { char path[PATH_MAX]; size_t length; Side side; } Path; /* Path ending type. */ typedef enum { NOT_FINAL, FINAL_NORMAL, FINAL_SLASH, FINAL_DOT } Finality; #define IS_FINAL(a) ((a) != NOT_FINAL) /* Comparison between two paths. */ typedef enum Comparison { PATHS_ARE_EQUAL, PATH1_IS_PREFIX, PATH2_IS_PREFIX, PATHS_ARE_NOT_COMPARABLE, } Comparison; extern int which(Tracee *tracee, const char *paths, char host_path[PATH_MAX], char *const command); extern int realpath2(Tracee *tracee, char host_path[PATH_MAX], const char *path, bool deref_final); extern int getcwd2(Tracee *tracee, char guest_path[PATH_MAX]); extern void chop_finality(char *path); extern int translate_path(Tracee *tracee, char host_path[PATH_MAX], int dir_fd, const char *guest_path, bool deref_final); extern int detranslate_path(Tracee *tracee, char path[PATH_MAX], const char t_referrer[PATH_MAX]); extern bool belongs_to_guestfs(const Tracee *tracee, const char *path); extern int join_paths(int number_paths, char result[PATH_MAX], ...); extern Finality next_component(char component[NAME_MAX], const char **cursor); extern void pop_component(char *path); extern int list_open_fd(const Tracee *tracee); extern Comparison compare_paths(const char *path1, const char *path2); extern Comparison compare_paths2(const char *path1, size_t length1, const char *path2, size_t length2); extern size_t substitute_path_prefix(char path[PATH_MAX], size_t old_prefix_length, const char *new_prefix, size_t new_prefix_length); extern int readlink_proc_pid_fd(pid_t pid, int fd, char path[PATH_MAX]); /* Check if path interpretable relatively to dirfd, see openat(2) for details. */ #define AT_FD(dirfd, path) ((dirfd) != AT_FDCWD && ((path) != NULL && (path)[0] != '/')) #endif /* PATH_H */ care-2.2.1/src/path/proc.h0000644000175000017500000000303212430615044014636 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef PROC_H #define PROC_H #include #include "tracee/tracee.h" #include "path/path.h" /* Action to do after a call to readlink_proc(). */ typedef enum { DEFAULT, /* Nothing special to do, treat it as a regular link. */ CANONICALIZE, /* The symlink was dereferenced, now canonicalize it. */ DONT_CANONICALIZE, /* The symlink shouldn't be dereferenced nor canonicalized. */ } Action; extern Action readlink_proc(const Tracee *tracee, char result[PATH_MAX], const char path[PATH_MAX], const char component[NAME_MAX], Comparison comparison); extern ssize_t readlink_proc2(const Tracee *tracee, char result[PATH_MAX], const char path[PATH_MAX]); #endif /* PROC_H */ care-2.2.1/src/path/temp.c0000644000175000017500000001555112430615044014644 0ustar ivoireivoire#include /* stat(2), opendir(3), */ #include /* stat(2), chmod(2), */ #include /* stat(2), rmdir(2), unlink(2), readlink(2), */ #include /* errno(2), */ #include /* readdir(3), opendir(3), */ #include /* strcmp(3), */ #include /* free(3), */ #include /* P_tmpdir, */ #include /* talloc(3), */ #include "cli/notice.h" /** * Remove recursively the content of the current working directory. * This latter has to lie in P_tmpdir (ie. "/tmp" on most systems). * This function returns -1 if a fatal error occured (ie. the * recursion must be stopped), the number of non-fatal errors * otherwise. * * WARNING: this function changes the current working directory for * the calling process. */ static int clean_temp_cwd() { const size_t length_tmpdir = strlen(P_tmpdir); char prefix[length_tmpdir]; int nb_errors = 0; int status; DIR *dir; /* Sanity check: ensure the current directory lies in * "/tmp". */ status = readlink("/proc/self/cwd", prefix, length_tmpdir); if (status < 0) { notice(NULL, WARNING, SYSTEM, "can't readlink '/proc/self/cwd'"); return ++nb_errors; } if (strncmp(prefix, P_tmpdir, length_tmpdir) != 0) { notice(NULL, ERROR, INTERNAL, "trying to remove a directory outside of '%s', " "please report this error.\n", P_tmpdir); return ++nb_errors; } dir = opendir("."); if (dir == NULL) { notice(NULL, WARNING, SYSTEM, "can't open '.'"); return ++nb_errors; } while (1) { struct dirent *entry; errno = 0; entry = readdir(dir); if (entry == NULL) break; if ( strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue; status = chmod(entry->d_name, 0700); if (status < 0) { notice(NULL, WARNING, SYSTEM, "cant chmod '%s'", entry->d_name); nb_errors++; continue; } if (entry->d_type == DT_DIR) { status = chdir(entry->d_name); if (status < 0) { notice(NULL, WARNING, SYSTEM, "can't chdir '%s'", entry->d_name); nb_errors++; continue; } /* Recurse. */ status = clean_temp_cwd(); if (status < 0) { nb_errors = -1; goto end; } nb_errors += status; status = chdir(".."); if (status < 0) { notice(NULL, ERROR, SYSTEM, "can't chdir to '..'"); nb_errors = -1; goto end; } status = rmdir(entry->d_name); } else { status = unlink(entry->d_name); } if (status < 0) { notice(NULL, WARNING, SYSTEM, "can't remove '%s'", entry->d_name); nb_errors++; continue; } } if (errno != 0) { notice(NULL, WARNING, SYSTEM, "can't readdir '.'"); nb_errors++; } end: (void) closedir(dir); return nb_errors; } /** * Remove recursively @path. This latter has to be a directory lying * in P_tmpdir (ie. "/tmp" on most systems). This function returns -1 * on error, otherwise 0. */ static int remove_temp_directory2(const char *path) { int result; int status; char *cwd; cwd = get_current_dir_name(); status = chmod(path, 0700); if (status < 0) { notice(NULL, ERROR, SYSTEM, "can't chmod '%s'", path); result = -1; goto end; } status = chdir(path); if (status < 0) { notice(NULL, ERROR, SYSTEM, "can't chdir to '%s'", path); result = -1; goto end; } status = clean_temp_cwd(); result = (status == 0 ? 0 : -1); /* Try to remove path even if something went wrong. */ status = chdir(".."); if (status < 0) { notice(NULL, ERROR, SYSTEM, "can't chdir to '..'"); result = -1; goto end; } status = rmdir(path); if (status < 0) { notice(NULL, ERROR, SYSTEM, "cant remove '%s'", path); result = -1; goto end; } end: if (cwd != NULL) { status = chdir(cwd); if (status < 0) { result = -1; notice(NULL, ERROR, SYSTEM, "can't chdir to '%s'", cwd); } free(cwd); } return result; } /** * Like remove_temp_directory2() but always return 0. * * Note: this is a talloc destructor. */ static int remove_temp_directory(char *path) { (void) remove_temp_directory2(path); return 0; } /** * Remove the file @path. This function always returns 0. * * Note: this is a talloc destructor. */ static int remove_temp_file(char *path) { int status; status = unlink(path); if (status < 0) notice(NULL, ERROR, SYSTEM, "can't remove '%s'", path); return 0; } /** * Create a path name with the following format: * "/tmp/@prefix-$PID-XXXXXX". The returned C string is either * auto-freed if @tracee is NULL, or attached to @tracee->ctx. This * function returns NULL if an error occurred. */ char *create_temp_name(const Tracee *tracee, const char *prefix) { TALLOC_CTX *context; char *name; if (tracee == NULL) { context = talloc_autofree_context(); if (context == NULL) { notice(tracee, ERROR, INTERNAL, "can't allocate memory"); return NULL; } } else context = tracee->ctx; name = talloc_asprintf(context, "%s/%s-%d-XXXXXX", P_tmpdir, prefix, getpid()); if (name == NULL) { notice(tracee, ERROR, INTERNAL, "can't allocate memory"); return NULL; } return name; } /** * Create a directory that will be automatically removed either on * PRoot termination if @tracee is NULL, or once its path name * (attached to @tracee->ctx) is freed. This function returns NULL on * error, otherwise the absolute path name to the created directory * (@prefix-ed). */ const char *create_temp_directory(const Tracee *tracee, const char *prefix) { char *name; name = create_temp_name(tracee, prefix); if (name == NULL) return NULL; name = mkdtemp(name); if (name == NULL) { notice(tracee, ERROR, SYSTEM, "can't create temporary directory"); return NULL; } talloc_set_destructor(name, remove_temp_directory); return name; } /** * Create a file that will be automatically removed either on PRoot * termination if @tracee is NULL, or once its path name (attached to * @tracee->ctx) is freed. This function returns NULL on error, * otherwise the absolute path name to the created file (@prefix-ed). */ const char *create_temp_file(const Tracee *tracee, const char *prefix) { char *name; int fd; name = create_temp_name(tracee, prefix); if (name == NULL) return NULL; fd = mkstemp(name); if (fd < 0) { notice(tracee, ERROR, SYSTEM, "can't create temporary file"); return NULL; } close(fd); talloc_set_destructor(name, remove_temp_file); return name; } /** * Like create_temp_file() but returns an open file stream to the * created file. It's up to the caller to close returned stream. */ FILE* open_temp_file(const Tracee *tracee, const char *prefix) { char *name; FILE *file; int fd; name = create_temp_name(tracee, prefix); if (name == NULL) return NULL; fd = mkstemp(name); if (fd < 0) goto error; talloc_set_destructor(name, remove_temp_file); file = fdopen(fd, "w"); if (file == NULL) goto error; return file; error: if (fd >= 0) close(fd); notice(tracee, ERROR, SYSTEM, "can't create temporary file"); return NULL; } care-2.2.1/src/path/path.c0000644000175000017500000005156312430615044014636 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* string(3), */ #include /* va_*(3), */ #include /* assert(3), */ #include /* AT_*, */ #include /* readlink*(2), *stat(2), getpid(2), */ #include /* pid_t, */ #include /* S_ISDIR, */ #include /* opendir(3), readdir(3), */ #include /* snprintf(3), */ #include /* E*, */ #include /* ptrdiff_t, */ #include "path/path.h" #include "path/binding.h" #include "path/canon.h" #include "path/proc.h" #include "extension/extension.h" #include "cli/notice.h" #include "build.h" #include "compat.h" /** * Copy in @component the first path component pointed to by @cursor, * this later is updated to point to the next component for a further * call. This function returns: * * - -errno if an error occured. * * - FINAL_SLASH if it the last component of the path but we * really expect a directory. * * - FINAL_NORMAL if it the last component of the path. * * - 0 otherwise. */ Finality next_component(char component[NAME_MAX], const char **cursor) { const char *start; ptrdiff_t length; bool want_dir; /* Sanity checks. */ assert(component != NULL); assert(cursor != NULL); /* Skip leading path separators. */ while (**cursor != '\0' && **cursor == '/') (*cursor)++; /* Find the next component. */ start = *cursor; while (**cursor != '\0' && **cursor != '/') (*cursor)++; length = *cursor - start; if (length >= NAME_MAX) return -ENAMETOOLONG; /* Extract the component. */ strncpy(component, start, length); component[length] = '\0'; /* Check if a [link to a] directory is expected. */ want_dir = (**cursor == '/'); /* Skip trailing path separators. */ while (**cursor != '\0' && **cursor == '/') (*cursor)++; if (**cursor == '\0') return (want_dir ? FINAL_SLASH : FINAL_NORMAL); return NOT_FINAL; } /** * Put an end-of-string ('\0') right before the last component of @path. */ void pop_component(char *path) { int offset; /* Sanity checks. */ assert(path != NULL); offset = strlen(path) - 1; assert(offset >= 0); /* Don't pop over "/", it doesn't mean anything. */ if (offset == 0) { assert(path[0] == '/' && path[1] == '\0'); return; } /* Skip trailing path separators. */ while (offset > 1 && path[offset] == '/') offset--; /* Search for the previous path separator. */ while (offset > 1 && path[offset] != '/') offset--; /* Cut the end of the string before the last component. */ path[offset] = '\0'; assert(path[0] == '/'); } /** * Copy in @result the concatenation of several paths (@number_paths) * and adds a path separator ('/') in between when needed. This * function returns -errno if an error occured, otherwise it returns 0. */ int join_paths(int number_paths, char result[PATH_MAX], ...) { va_list paths; size_t length; int status; int i; result[0] = '\0'; length = 0; status = 0; /* Parse the list of variadic arguments. */ va_start(paths, result); for (i = 0; i < number_paths; i++) { const char *path; size_t path_length; size_t new_length; path = va_arg(paths, const char *); if (path == NULL) continue; path_length = strlen(path); /* A new path separator is needed. */ if (length > 0 && result[length - 1] != '/' && path[0] != '/') { new_length = length + path_length + 1; if (new_length + 1 >= PATH_MAX) { status = -ENAMETOOLONG; break; } strcat(result + length, "/"); strcat(result + length, path); length = new_length; } /* There are already two path separators. */ else if (length > 0 && result[length - 1] == '/' && path[0] == '/') { new_length = length + path_length - 1; if (new_length + 1 >= PATH_MAX) { status = -ENAMETOOLONG; break; } strcat(result + length, path + 1); length += path_length - 1; } /* There's already one path separator or result[] is empty. */ else { new_length = length + path_length; if (new_length + 1 >= PATH_MAX) { status = -ENAMETOOLONG; break; } strcat(result + length, path); length += path_length; } status = 0; } va_end(paths); return status; } /** * Put in @host_path the full path to the given shell @command. The * @command is searched in @paths if not null, otherwise in $PATH * (relatively to the @tracee's file-system name-space). This * function always returns -1 on error, otherwise 0. */ int which(Tracee *tracee, const char *paths, char host_path[PATH_MAX], char *const command) { char path[PATH_MAX]; const char *cursor; struct stat statr; int status; bool is_explicit; bool found; assert(command != NULL); is_explicit = (strchr(command, '/') != NULL); /* Is the command available without any $PATH look-up? */ status = realpath2(tracee, host_path, command, true); if (status == 0 && stat(host_path, &statr) == 0) { if (is_explicit && !S_ISREG(statr.st_mode)) { notice(tracee, ERROR, USER, "'%s' is not a regular file", command); return -EACCES; } if (is_explicit && (statr.st_mode & S_IXUSR) == 0) { notice(tracee, ERROR, USER, "'%s' is not executable", command); return -EACCES; } found = true; /* Don't dereference the final component to preserve * argv0 in case it is a symlink to script. */ (void) realpath2(tracee, host_path, command, false); } else found = false; /* Is the the explicit command was found? */ if (is_explicit) { if (found) return 0; else goto not_found; } /* Otherwise search the command in $PATH. */ paths = paths ?: getenv("PATH"); if (paths == NULL || strcmp(paths, "") == 0) goto not_found; cursor = paths; do { size_t length; length = strcspn(cursor, ":"); cursor += length + 1; if (length >= PATH_MAX) continue; else if (length == 0) strcpy(path, "."); else { strncpy(path, cursor - length - 1, length); path[length] = '\0'; } /* Avoid buffer-overflow. */ if (length + strlen(command) + 2 >= PATH_MAX) continue; strcat(path, "/"); strcat(path, command); status = realpath2(tracee, host_path, path, true); if (status == 0 && stat(host_path, &statr) == 0 && S_ISREG(statr.st_mode) && (statr.st_mode & S_IXUSR) != 0) { /* Don't dereference the final component to preserve * argv0 in case it is a symlink to script. */ (void) realpath2(tracee, host_path, path, false); return 0; } } while (*(cursor - 1) != '\0'); not_found: status = getcwd2(tracee, path); if (status < 0) strcpy(path, ""); notice(tracee, ERROR, USER, "'%s' not found (root = %s, cwd = %s, $PATH=%s)", command, get_root(tracee), path, paths); /* Check if the command was found without any $PATH look-up * but it didn't contain "/". */ if (found && !is_explicit) notice(tracee, ERROR, USER, "to execute a local program, use the './' prefix, for example: ./%s", command); return -1; } /** * Put in @host_path the canonicalized form of @path. In the nominal * case (@tracee == NULL), this function is barely equivalent to * realpath(), but when doing sub-reconfiguration, the path is * canonicalized relatively to the current @tracee's file-system * name-space. This function returns -errno on error, otherwise 0. */ int realpath2(Tracee *tracee, char host_path[PATH_MAX], const char *path, bool deref_final) { int status; if (tracee == NULL) status = (realpath(path, host_path) == NULL ? -errno : 0); else status = translate_path(tracee, host_path, AT_FDCWD, path, deref_final); return status; } /** * Put in @guest_path the canonicalized current working directory. In * the nominal case (@tracee == NULL), this function is barely * equivalent to realpath(), but when doing sub-reconfiguration, the * path is canonicalized relatively to the current @tracee's * file-system name-space. This function returns -errno on error, * otherwise 0. */ int getcwd2(Tracee *tracee, char guest_path[PATH_MAX]) { if (tracee == NULL) { if (getcwd(guest_path, PATH_MAX) == NULL) return -errno; } else { if (strlen(tracee->fs->cwd) >= PATH_MAX) return -ENAMETOOLONG; strcpy(guest_path, tracee->fs->cwd); } return 0; } /** * Remove the trailing "/" or "/.". */ void chop_finality(char *path) { size_t length = strlen(path); if (path[length - 1] == '.') { assert(length >= 2); /* Special case for "/." */ if (length == 2) path[length - 1] = '\0'; else path[length - 2] = '\0'; } else if (path[length - 1] == '/') { /* Special case for "/" */ if (length > 1) path[length - 1] = '\0'; } } /** * Put in @path the result of readlink(/proc/@pid/fd/@fd). This * function returns -errno if an error occured, othrwise 0. */ int readlink_proc_pid_fd(pid_t pid, int fd, char path[PATH_MAX]) { char link[32]; /* 32 > sizeof("/proc//cwd") + sizeof(#ULONG_MAX) */ int status; /* Format the path to the "virtual" link. */ status = snprintf(link, sizeof(link), "/proc/%d/fd/%d", pid, fd); if (status < 0) return -EBADF; if ((size_t) status >= sizeof(link)) return -EBADF; /* Read the value of this "virtual" link. */ status = readlink(link, path, PATH_MAX); if (status < 0) return -EBADF; if (status >= PATH_MAX) return -ENAMETOOLONG; path[status] = '\0'; return 0; } /** * Copy in @result the equivalent of "@tracee->root + canon(@dir_fd + * @user_path)". If @user_path is not absolute then it is relative to * the directory referred by the descriptor @dir_fd (AT_FDCWD is for * the current working directory). See the documentation of * canonicalize() for the meaning of @deref_final. This function * returns -errno if an error occured, otherwise 0. */ int translate_path(Tracee *tracee, char result[PATH_MAX], int dir_fd, const char *user_path, bool deref_final) { char guest_path[PATH_MAX]; int status; /* Use "/" as the base if it is an absolute guest path. */ if (user_path[0] == '/') { strcpy(result, "/"); } /* It is relative to a directory referred by a descriptor, see * openat(2) for details. */ else if (dir_fd != AT_FDCWD) { /* /proc/@tracee->pid/fd/@dir_fd -> result. */ status = readlink_proc_pid_fd(tracee->pid, dir_fd, result); if (status < 0) return status; /* Named file descriptors may reference special * objects like pipes, sockets, inodes, ... Such * objects do not belong to the file-system. */ if (result[0] != '/') return -ENOTDIR; /* Remove the leading "root" part of the base * (required!). */ status = detranslate_path(tracee, result, NULL); if (status < 0) return status; } /* It is relative to the current working directory. */ else { status = getcwd2(tracee, result); if (status < 0) return status; } VERBOSE(tracee, 2, "pid %d: translate(\"%s\" + \"%s\")", tracee != NULL ? tracee->pid : 0, result, user_path); status = notify_extensions(tracee, GUEST_PATH, (intptr_t) result, (intptr_t) user_path); if (status < 0) return status; if (status > 0) goto skip; /* So far "result" was used as a base path, it's time to join * it to the user path. */ assert(result[0] == '/'); status = join_paths(2, guest_path, result, user_path); if (status < 0) return status; strcpy(result, "/"); /* Canonicalize regarding the new root. */ status = canonicalize(tracee, guest_path, deref_final, result, 0); if (status < 0) return status; /* Final binding substitution to convert "result" into a host * path, since canonicalize() works from the guest * point-of-view. */ status = substitute_binding(tracee, GUEST, result); if (status < 0) return status; skip: VERBOSE(tracee, 2, "pid %d: -> \"%s\"", tracee != NULL ? tracee->pid : 0, result); return 0; } /** * Remove/substitute the leading part of a "translated" @path. It * returns 0 if no transformation is required (ie. symmetric binding), * otherwise it returns the size in bytes of the updated @path, * including the end-of-string terminator. On error it returns * -errno. */ int detranslate_path(Tracee *tracee, char path[PATH_MAX], const char t_referrer[PATH_MAX]) { size_t prefix_length; ssize_t new_length; bool sanity_check; bool follow_binding; /* Sanity check. */ if (strnlen(path, PATH_MAX) >= PATH_MAX) return -ENAMETOOLONG; /* Don't try to detranslate relative paths (typically the * target of a relative symbolic link). */ if (path[0] != '/') return 0; /* Is it a symlink? */ if (t_referrer != NULL) { Comparison comparison; sanity_check = false; follow_binding = false; /* In some cases bindings have to be resolved. */ comparison = compare_paths("/proc", t_referrer); if (comparison == PATH1_IS_PREFIX) { /* Some links in "/proc" are generated * dynamically by the kernel. PRoot has to * emulate some of them. */ char proc_path[PATH_MAX]; strcpy(proc_path, path); new_length = readlink_proc2(tracee, proc_path, t_referrer); if (new_length < 0) return new_length; if (new_length != 0) { strcpy(path, proc_path); return new_length + 1; } /* Always resolve bindings for symlinks in * "/proc", they always point to the emulated * file-system namespace by design. */ follow_binding = true; } else if (!belongs_to_guestfs(tracee, t_referrer)) { const char *binding_referree; const char *binding_referrer; binding_referree = get_path_binding(tracee, HOST, path); binding_referrer = get_path_binding(tracee, HOST, t_referrer); assert(binding_referrer != NULL); /* Resolve bindings for symlinks that belong * to a binding and point to the same binding. * For example, if "-b /lib:/foo" is specified * and the symlink "/lib/a -> /lib/b" exists * in the host rootfs namespace, then it * should appear as "/foo/a -> /foo/b" in the * guest rootfs namespace for consistency * reasons. */ if (binding_referree != NULL) { comparison = compare_paths(binding_referree, binding_referrer); follow_binding = (comparison == PATHS_ARE_EQUAL); } } } else { sanity_check = true; follow_binding = true; } if (follow_binding) { switch (substitute_binding(tracee, HOST, path)) { case 0: return 0; case 1: return strlen(path) + 1; default: break; } } switch (compare_paths(get_root(tracee), path)) { case PATH1_IS_PREFIX: /* Remove the leading part, that is, the "root". */ prefix_length = strlen(get_root(tracee)); /* Special case when path to the guest rootfs == "/". */ if (prefix_length == 1) prefix_length = 0; new_length = strlen(path) - prefix_length; memmove(path, path + prefix_length, new_length); path[new_length] = '\0'; break; case PATHS_ARE_EQUAL: /* Special case when path == root. */ new_length = 1; strcpy(path, "/"); break; default: /* Ensure the path is within the new root. */ if (sanity_check) return -EPERM; else return 0; } return new_length + 1; } /** * Check if the translated @host_path belongs to the guest rootfs, * that is, isn't from a binding. */ bool belongs_to_guestfs(const Tracee *tracee, const char *host_path) { Comparison comparison; comparison = compare_paths(get_root(tracee), host_path); return (comparison == PATHS_ARE_EQUAL || comparison == PATH1_IS_PREFIX); } /** * Compare @path1 with @path2, which are respectively @length1 and * @length2 long. * * This function works only with paths canonicalized in the same * namespace (host/guest)! */ Comparison compare_paths2(const char *path1, size_t length1, const char *path2, size_t length2) { size_t length_min; bool is_prefix; char sentinel; #if defined DEBUG_OPATH assert(length(path1) == length1); assert(length(path2) == length2); #endif assert(length1 > 0); assert(length2 > 0); if (!length1 || !length2) { return PATHS_ARE_NOT_COMPARABLE; } /* Remove potential trailing '/' for the comparison. */ if (path1[length1 - 1] == '/') length1--; if (path2[length2 - 1] == '/') length2--; if (length1 < length2) { length_min = length1; sentinel = path2[length_min]; } else { length_min = length2; sentinel = path1[length_min]; } /* Optimize obvious cases. */ if (sentinel != '/' && sentinel != '\0') return PATHS_ARE_NOT_COMPARABLE; is_prefix = (strncmp(path1, path2, length_min) == 0); if (!is_prefix) return PATHS_ARE_NOT_COMPARABLE; if (length1 == length2) return PATHS_ARE_EQUAL; else if (length1 < length2) return PATH1_IS_PREFIX; else if (length1 > length2) return PATH2_IS_PREFIX; assert(0); return PATHS_ARE_NOT_COMPARABLE; } Comparison compare_paths(const char *path1, const char *path2) { return compare_paths2(path1, strlen(path1), path2, strlen(path2)); } typedef int (*foreach_fd_t)(const Tracee *tracee, int fd, char path[PATH_MAX]); /** * Call @callback on each open file descriptors of @pid. It returns * the status of the first failure, that is, if @callback returned * seomthing lesser than 0, otherwise 0. */ static int foreach_fd(const Tracee *tracee, foreach_fd_t callback) { struct dirent *dirent; char path[PATH_MAX]; char proc_fd[32]; /* 32 > sizeof("/proc//fd") + sizeof(#ULONG_MAX) */ int status; DIR *dirp; /* Format the path to the "virtual" directory. */ status = snprintf(proc_fd, sizeof(proc_fd), "/proc/%d/fd", tracee->pid); if (status < 0 || (size_t) status >= sizeof(proc_fd)) return 0; /* Open the virtual directory "/proc/$pid/fd". */ dirp = opendir(proc_fd); if (dirp == NULL) return 0; while ((dirent = readdir(dirp)) != NULL) { /* Read the value of this "virtual" link. Don't use * readlinkat(2) here since it would require Linux >= * 2.6.16 and Glibc >= 2.4, whereas PRoot is supposed * to work on any Linux 2.6 systems. */ char tmp[PATH_MAX]; if (strlen(proc_fd) + strlen(dirent->d_name) + 1 >= PATH_MAX) continue; strcpy(tmp, proc_fd); strcat(tmp, "/"); strcat(tmp, dirent->d_name); status = readlink(tmp, path, PATH_MAX); if (status < 0 || status >= PATH_MAX) continue; path[status] = '\0'; /* Ensure it points to a path (not a socket or somethink like that). */ if (path[0] != '/') continue; status = callback(tracee, atoi(dirent->d_name), path); if (status < 0) goto end; } status = 0; end: closedir(dirp); return status; } /** * Helper for list_open_fd(). */ static int list_open_fd_callback(const Tracee *tracee, int fd, char path[PATH_MAX]) { VERBOSE(tracee, 1, "pid %d: access to \"%s\" (fd %d) won't be translated until closed", tracee->pid, path, fd); return 0; } /** * Warn for files that are open. It is useful right after PRoot has * attached a process. */ int list_open_fd(const Tracee *tracee) { return foreach_fd(tracee, list_open_fd_callback); } /** * Substitute the first @old_prefix_length bytes of @path with * @new_prefix (the caller has to provides a correct * @new_prefix_length). This function returns the new length of * @path. Note: this function takes care about special cases (like * "/"). */ size_t substitute_path_prefix(char path[PATH_MAX], size_t old_prefix_length, const char *new_prefix, size_t new_prefix_length) { size_t path_length; size_t new_length; path_length = strlen(path); assert(old_prefix_length < PATH_MAX); assert(new_prefix_length < PATH_MAX); if (new_prefix_length == 1) { /* Special case: "/foo" -> "/". Substitute "/foo/bin" * with "/bin" not "//bin". */ new_length = path_length - old_prefix_length; if (new_length != 0) memmove(path, path + old_prefix_length, new_length); else { /* Special case: "/". */ path[0] = '/'; new_length = 1; } } else if (old_prefix_length == 1) { /* Special case: "/" -> "/foo". Substitute "/bin" with * "/foo/bin" not "/foobin". */ new_length = new_prefix_length + path_length; if (new_length >= PATH_MAX) return -ENAMETOOLONG; if (path_length > 1) { memmove(path + new_prefix_length, path, path_length); memcpy(path, new_prefix, new_prefix_length); } else { /* Special case: "/". */ memcpy(path, new_prefix, new_prefix_length); new_length = new_prefix_length; } } else { /* Generic case. */ new_length = path_length - old_prefix_length + new_prefix_length; if (new_length >= PATH_MAX) return -ENAMETOOLONG; memmove(path + new_prefix_length, path + old_prefix_length, path_length - old_prefix_length); memcpy(path, new_prefix, new_prefix_length); } assert(new_length < PATH_MAX); path[new_length] = '\0'; return new_length; } care-2.2.1/src/path/glue.c0000644000175000017500000001360612430615044014632 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* mkdir(2), lstat(2), */ #include /* mkdir(2), lstat(2), */ #include /* mknod(2), */ #include /* mknod(2), lstat(2), unlink(2), rmdir(2), */ #include /* string(3), */ #include /* assert(3), */ #include /* PATH_MAX, */ #include /* errno, E* */ #include /* talloc_*, */ #include "path/binding.h" #include "path/path.h" #include "path/temp.h" #include "cli/notice.h" #include "compat.h" /** * Remove @path if it is empty only. * * Note: this is a Talloc destructor. */ static int remove_placeholder(char *path) { struct stat statl; int status; status = lstat(path, &statl); if (status) { notice(NULL, WARNING, SYSTEM, "can't stat placeholder '%s'", path); return 0; /* Not fatal. */ } if (!S_ISDIR(statl.st_mode)) { if (statl.st_size != 0) { notice(NULL, WARNING, USER, "placeholder '%s' is not empty", path); return 0; /* Not fatal. */ } status = unlink(path); } else status = rmdir(path); if (status) { notice(NULL, WARNING, SYSTEM, "can't remove placeholder '%s'", path); return 0; /* Not fatal. */ } return 0; } /** * Attach a copy of @path to the autofree context, and set its * destructor to remove_placeholder(). */ static void set_placeholder_destructor(const char *path) { TALLOC_CTX *autofreed; char *placeholder; autofreed = talloc_autofree_context(); if (autofreed == NULL) return; placeholder = talloc_strdup(autofreed, path); if (placeholder == NULL) return; talloc_set_destructor(placeholder, remove_placeholder); } /** * Build in a temporary filesystem the glue between the guest part and * the host part of the @binding_path. This function returns the type * of the bound path, otherwise 0 if an error occured. * * For example, assuming the host path "/opt" is mounted/bound to the * guest path "/black/holes/and/revelations", and assuming this path * can't be created in the guest rootfs (eg. permission denied), then * it is created in a temporary rootfs and all these paths are glued * that way: * * $GUEST/black/ --> $GLUE/black/ * ./holes * ./holes/and * ./holes/and/revelations --> $HOST/opt/ * * This glue allows operations on paths that do not exist in the guest * rootfs but that were specified as the guest part of a binding. */ mode_t build_glue(Tracee *tracee, const char *guest_path, char host_path[PATH_MAX], Finality finality) { bool belongs_to_gluefs; Comparison comparison; Binding *binding; mode_t type; mode_t mode; int status; assert(tracee->glue_type != 0); /* Create the temporary directory where the "glue" rootfs will * lie. */ if (tracee->glue == NULL) { tracee->glue = create_temp_directory(NULL, tracee->tool_name); if (tracee->glue == NULL) { notice(tracee, ERROR, INTERNAL, "can't create glue rootfs"); return 0; } talloc_set_name_const(tracee->glue, "$glue"); } comparison = compare_paths(tracee->glue, host_path); belongs_to_gluefs = (comparison == PATHS_ARE_EQUAL || comparison == PATH1_IS_PREFIX); /* If it's not a final component then it is a directory. I definitely * hate how the potential type of the final component is propagated * from initialize_binding() down to here, sadly there's no elegant way * to know its type at this stage. */ if (IS_FINAL(finality)) { type = tracee->glue_type; mode = (belongs_to_gluefs ? 0777 : 0); } else { type = S_IFDIR; mode = 0777; } if (getenv("PROOT_DONT_POLLUTE_ROOTFS") != NULL && !belongs_to_gluefs) goto create_binding; /* Try to create this component into the "guest" or "glue" * rootfs (depending if there were a glue previously). */ if (S_ISDIR(type)) status = mkdir(host_path, mode); else /* S_IFREG, S_IFCHR, S_IFBLK, S_IFIFO or S_IFSOCK. */ status = mknod(host_path, mode | type, 0); /* Remove placeholders from the guest rootfs once PRoot is * terminated. */ if (status >= 0 && !belongs_to_gluefs) set_placeholder_destructor(host_path); /* Nothing else to do if the path already exists or if it is * the final component since it will be pointed to by the * binding being initialized (from the example, * "$GUEST/black/holes/and/revelations" -> "$HOST/opt"). */ if (status >= 0 || errno == EEXIST || IS_FINAL(finality)) return type; /* mkdir/mknod are supposed to always succeed in * tracee->glue. */ if (belongs_to_gluefs) { notice(tracee, WARNING, SYSTEM, "mkdir/mknod"); return 0; } create_binding: /* Sanity checks. */ if ( strnlen(tracee->glue, PATH_MAX) >= PATH_MAX || strnlen(guest_path, PATH_MAX) >= PATH_MAX) { notice(tracee, WARNING, INTERNAL, "installing the binding: guest path too long"); return 0; } /* From the example, create the binding "/black" -> * "$GLUE/black". */ binding = insort_binding3(tracee, tracee->glue, tracee->glue, guest_path); if (binding == NULL) return 0; /* TODO: emulation of getdents(parent(guest_path)) to finalize * the glue, "black" in getdents("/") from the example. */ return type; } care-2.2.1/src/path/binding.c0000644000175000017500000005112012430615044015301 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* lstat(2), */ #include /* getcwd(2), lstat(2), */ #include /* string(3), */ #include /* bzero(3), */ #include /* assert(3), */ #include /* PATH_MAX, */ #include /* E* */ #include /* CIRCLEQ_*, */ #include /* talloc_*, */ #include "path/binding.h" #include "path/path.h" #include "path/canon.h" #include "cli/notice.h" #include "compat.h" #define HEAD(tracee, side) \ (side == GUEST \ ? (tracee)->fs->bindings.guest \ : (side == HOST \ ? (tracee)->fs->bindings.host \ : (tracee)->fs->bindings.pending)) #define NEXT(binding, side) \ (side == GUEST \ ? CIRCLEQ_NEXT(binding, link.guest) \ : (side == HOST \ ? CIRCLEQ_NEXT(binding, link.host) \ : CIRCLEQ_NEXT(binding, link.pending))) #define CIRCLEQ_FOREACH_(tracee, binding, side) \ for (binding = CIRCLEQ_FIRST(HEAD(tracee, side)); \ binding != (void *) HEAD(tracee, side); \ binding = NEXT(binding, side)) #define CIRCLEQ_INSERT_AFTER_(tracee, previous, binding, side) do { \ switch (side) { \ case GUEST: CIRCLEQ_INSERT_AFTER(HEAD(tracee, side), previous, binding, link.guest); break; \ case HOST: CIRCLEQ_INSERT_AFTER(HEAD(tracee, side), previous, binding, link.host); break; \ default: CIRCLEQ_INSERT_AFTER(HEAD(tracee, side), previous, binding, link.pending); break; \ } \ (void) talloc_reference(HEAD(tracee, side), binding); \ } while (0) #define CIRCLEQ_INSERT_BEFORE_(tracee, next, binding, side) do { \ switch (side) { \ case GUEST: CIRCLEQ_INSERT_BEFORE(HEAD(tracee, side), next, binding, link.guest); break; \ case HOST: CIRCLEQ_INSERT_BEFORE(HEAD(tracee, side), next, binding, link.host); break; \ default: CIRCLEQ_INSERT_BEFORE(HEAD(tracee, side), next, binding, link.pending); break; \ } \ (void) talloc_reference(HEAD(tracee, side), binding); \ } while (0) #define CIRCLEQ_INSERT_HEAD_(tracee, binding, side) do { \ switch (side) { \ case GUEST: CIRCLEQ_INSERT_HEAD(HEAD(tracee, side), binding, link.guest); break; \ case HOST: CIRCLEQ_INSERT_HEAD(HEAD(tracee, side), binding, link.host); break; \ default: CIRCLEQ_INSERT_HEAD(HEAD(tracee, side), binding, link.pending); break; \ } \ (void) talloc_reference(HEAD(tracee, side), binding); \ } while (0) #define IS_LINKED(binding, link) \ ((binding)->link.cqe_next != NULL && (binding)->link.cqe_prev != NULL) #define CIRCLEQ_REMOVE_(tracee, binding, name) do { \ CIRCLEQ_REMOVE((tracee)->fs->bindings.name, binding, link.name);\ (binding)->link.name.cqe_next = NULL; \ (binding)->link.name.cqe_prev = NULL; \ talloc_unlink((tracee)->fs->bindings.name, binding); \ } while (0) /** * Print all bindings (verbose purpose). */ static void print_bindings(const Tracee *tracee) { const Binding *binding; if (tracee->fs->bindings.guest == NULL) return; CIRCLEQ_FOREACH_(tracee, binding, GUEST) { if (compare_paths(binding->host.path, binding->guest.path) == PATHS_ARE_EQUAL) notice(tracee, INFO, USER, "binding = %s", binding->host.path); else notice(tracee, INFO, USER, "binding = %s:%s", binding->host.path, binding->guest.path); } } /** * Get the binding for the given @path (relatively to the given * binding @side). */ Binding *get_binding(const Tracee *tracee, Side side, const char path[PATH_MAX]) { Binding *binding; size_t path_length = strlen(path); /* Sanity checks. */ assert(path != NULL && path[0] == '/'); CIRCLEQ_FOREACH_(tracee, binding, side) { Comparison comparison; const Path *ref; switch (side) { case GUEST: ref = &binding->guest; break; case HOST: ref = &binding->host; break; default: assert(0); return NULL; } comparison = compare_paths2(ref->path, ref->length, path, path_length); if ( comparison != PATHS_ARE_EQUAL && comparison != PATH1_IS_PREFIX) continue; /* Avoid false positive when a prefix of the rootfs is * used as an asymmetric binding, ex.: * * proot -m /usr:/location /usr/local/slackware */ if ( side == HOST && compare_paths(get_root(tracee), "/") != PATHS_ARE_EQUAL && belongs_to_guestfs(tracee, path)) continue; return binding; } return NULL; } /** * Get the binding path for the given @path (relatively to the given * binding @side). */ const char *get_path_binding(const Tracee *tracee, Side side, const char path[PATH_MAX]) { const Binding *binding; binding = get_binding(tracee, side, path); if (!binding) return NULL; switch (side) { case GUEST: return binding->guest.path; case HOST: return binding->host.path; default: assert(0); return NULL; } } /** * Return the path to the guest rootfs for the given @tracee, from the * host point-of-view obviously. Depending on whether * initialize_bindings() was called or not, the path is retrieved from * the "bindings.guest" list or from the "bindings.pending" list, * respectively. */ const char *get_root(const Tracee* tracee) { const Binding *binding; if (tracee == NULL || tracee->fs == NULL) return NULL; if (tracee->fs->bindings.guest == NULL) { if (tracee->fs->bindings.pending == NULL || CIRCLEQ_EMPTY(tracee->fs->bindings.pending)) return NULL; binding = CIRCLEQ_LAST(tracee->fs->bindings.pending); if (compare_paths(binding->guest.path, "/") != PATHS_ARE_EQUAL) return NULL; return binding->host.path; } assert(!CIRCLEQ_EMPTY(tracee->fs->bindings.guest)); binding = CIRCLEQ_LAST(tracee->fs->bindings.guest); assert(strcmp(binding->guest.path, "/") == 0); return binding->host.path; } /** * Substitute the guest path (if any) with the host path in @path. * This function returns: * * * -errno if an error occured * * * 0 if it is a binding location but no substitution is needed * ("symetric" binding) * * * 1 if it is a binding location and a substitution was performed * ("asymmetric" binding) */ int substitute_binding(const Tracee *tracee, Side side, char path[PATH_MAX]) { const Path *reverse_ref; const Path *ref; const Binding *binding; binding = get_binding(tracee, side, path); if (!binding) return -ENOENT; /* Is it a "symetric" binding? */ if (!binding->need_substitution) return 0; switch (side) { case GUEST: ref = &binding->guest; reverse_ref = &binding->host; break; case HOST: ref = &binding->host; reverse_ref = &binding->guest; break; default: assert(0); return -EACCES; } substitute_path_prefix(path, ref->length, reverse_ref->path, reverse_ref->length); return 1; } /** * Remove @binding from all the @tracee's lists of bindings it belongs to. */ void remove_binding_from_all_lists(const Tracee *tracee, Binding *binding) { if (IS_LINKED(binding, link.pending)) CIRCLEQ_REMOVE_(tracee, binding, pending); if (IS_LINKED(binding, link.guest)) CIRCLEQ_REMOVE_(tracee, binding, guest); if (IS_LINKED(binding, link.host)) CIRCLEQ_REMOVE_(tracee, binding, host); } /** * Insert @binding into the list of @bindings, in a sorted manner so * as to make the substitution of nested bindings determistic, ex.: * * -b /bin:/foo/bin -b /usr/bin/more:/foo/bin/more * * Note: "nested" from the @side point-of-view. */ static void insort_binding(const Tracee *tracee, Side side, Binding *binding) { Binding *iterator; Binding *previous = NULL; Binding *next = CIRCLEQ_FIRST(HEAD(tracee, side)); /* Find where it should be added in the list. */ CIRCLEQ_FOREACH_(tracee, iterator, side) { Comparison comparison; const Path *binding_path; const Path *iterator_path; switch (side) { case PENDING: case GUEST: binding_path = &binding->guest; iterator_path = &iterator->guest; break; case HOST: binding_path = &binding->host; iterator_path = &iterator->host; break; default: assert(0); return; } comparison = compare_paths2(binding_path->path, binding_path->length, iterator_path->path, iterator_path->length); switch (comparison) { case PATHS_ARE_EQUAL: if (side == HOST) { previous = iterator; break; } if (tracee->verbose > 0 && getenv("PROOT_IGNORE_MISSING_BINDINGS") == NULL) { notice(tracee, WARNING, USER, "both '%s' and '%s' are bound to '%s', " "only the last binding is active.", iterator->host.path, binding->host.path, binding->guest.path); } /* Replace this iterator with the new binding. */ CIRCLEQ_INSERT_AFTER_(tracee, iterator, binding, side); remove_binding_from_all_lists(tracee, iterator); return; case PATH1_IS_PREFIX: /* The new binding contains the iterator. */ previous = iterator; break; case PATH2_IS_PREFIX: /* The iterator contains the new binding. * Use the deepest container. */ if (next == (void *) HEAD(tracee, side)) next = iterator; break; case PATHS_ARE_NOT_COMPARABLE: break; default: assert(0); return; } } /* Insert this binding in the list. */ if (previous != NULL) CIRCLEQ_INSERT_AFTER_(tracee, previous, binding, side); else if (next != (void *) HEAD(tracee, side)) CIRCLEQ_INSERT_BEFORE_(tracee, next, binding, side); else CIRCLEQ_INSERT_HEAD_(tracee, binding, side); } /** * c.f. function above. */ static void insort_binding2(const Tracee *tracee, Binding *binding) { binding->need_substitution = compare_paths(binding->host.path, binding->guest.path) != PATHS_ARE_EQUAL; insort_binding(tracee, GUEST, binding); insort_binding(tracee, HOST, binding); } /** * Create and insert a new binding (@host_path:@guest_path) into the * list of @tracee's bindings. The Talloc parent of this new binding * is @context. This function returns NULL if an error occurred, * otherwise a pointer to the newly created binding. */ Binding *insort_binding3(const Tracee *tracee, const TALLOC_CTX *context, const char host_path[PATH_MAX], const char guest_path[PATH_MAX]) { Binding *binding; binding = talloc_zero(context, Binding); if (binding == NULL) return NULL; strcpy(binding->host.path, host_path); strcpy(binding->guest.path, guest_path); binding->host.length = strlen(binding->host.path); binding->guest.length = strlen(binding->guest.path); insort_binding2(tracee, binding); return binding; } /** * Free all bindings from @bindings. * * Note: this is a Talloc destructor. */ static int remove_bindings(Bindings *bindings) { Binding *binding; Tracee *tracee; /* Unlink all bindings from the @link list. */ #define CIRCLEQ_REMOVE_ALL(name) do { \ binding = CIRCLEQ_FIRST(bindings); \ while (binding != (void *) bindings) { \ Binding *next = CIRCLEQ_NEXT(binding, link.name);\ CIRCLEQ_REMOVE_(tracee, binding, name); \ binding = next; \ } \ } while (0) /* Search which link is used by this list. */ tracee = TRACEE(bindings); if (bindings == tracee->fs->bindings.pending) CIRCLEQ_REMOVE_ALL(pending); else if (bindings == tracee->fs->bindings.guest) CIRCLEQ_REMOVE_ALL(guest); else if (bindings == tracee->fs->bindings.host) CIRCLEQ_REMOVE_ALL(host); bzero(bindings, sizeof(Bindings)); return 0; } /** * Allocate a new binding "@host:@guest" and attach it to * @tracee->fs->bindings.pending. This function complains about * missing @host path only if @must_exist is true. This function * returns the allocated binding on success, NULL on error. */ Binding *new_binding(Tracee *tracee, const char *host, const char *guest, bool must_exist) { Binding *binding; char base[PATH_MAX]; int status; /* Lasy allocation of the list of bindings specified by the * user. This list will be used by initialize_bindings(). */ if (tracee->fs->bindings.pending == NULL) { tracee->fs->bindings.pending = talloc_zero(tracee->fs, Bindings); if (tracee->fs->bindings.pending == NULL) return NULL; CIRCLEQ_INIT(tracee->fs->bindings.pending); talloc_set_destructor(tracee->fs->bindings.pending, remove_bindings); } /* Allocate an empty binding. */ binding = talloc_zero(tracee->ctx, Binding); if (binding == NULL) return NULL; /* Canonicalize the host part of the binding, as expected by * get_binding(). */ status = realpath2(tracee->reconf.tracee, binding->host.path, host, true); if (status < 0) { if (must_exist && getenv("PROOT_IGNORE_MISSING_BINDINGS") == NULL) notice(tracee, WARNING, INTERNAL, "can't sanitize binding \"%s\": %s", host, strerror(-status)); goto error; } binding->host.length = strlen(binding->host.path); /* Symetric binding? */ guest = guest ?: host; /* When not absolute, assume the guest path is relative to the * current working directory, as with ``-b .`` for instance. */ if (guest[0] != '/') { status = getcwd2(tracee->reconf.tracee, base); if (status < 0) { notice(tracee, WARNING, INTERNAL, "can't sanitize binding \"%s\": %s", binding->guest.path, strerror(-status)); goto error; } } else strcpy(base, "/"); status = join_paths(2, binding->guest.path, base, guest); if (status < 0) { notice(tracee, WARNING, SYSTEM, "can't sanitize binding \"%s\"", binding->guest.path); goto error; } binding->guest.length = strlen(binding->guest.path); /* Keep the list of bindings specified by the user ordered, * for the sake of consistency. For instance binding to "/" * has to be the last in the list. */ insort_binding(tracee, PENDING, binding); return binding; error: TALLOC_FREE(binding); return NULL; } /** * Canonicalize the guest part of the given @binding, insert it into * @tracee->fs->bindings.guest and @tracee->fs->bindings.host. This * function returns -1 if an error occured, 0 otherwise. */ static void initialize_binding(Tracee *tracee, Binding *binding) { char path[PATH_MAX]; struct stat statl; int status; /* All bindings but "/" must be canonicalized. The exception * for "/" is required to bootstrap the canonicalization. */ if (compare_paths(binding->guest.path, "/") != PATHS_ARE_EQUAL) { bool dereference; size_t length; strcpy(path, binding->guest.path); length = strlen(path); assert(length > 0); /* Does the user explicitly tell not to dereference * guest path? */ dereference = (path[length - 1] != '!'); if (!dereference) path[length - 1] = '\0'; /* Initial state before canonicalization. */ strcpy(binding->guest.path, "/"); /* Remember the type of the final component, it will * be used in build_glue() later. */ status = lstat(binding->host.path, &statl); tracee->glue_type = (status < 0 || S_ISBLK(statl.st_mode) || S_ISCHR(statl.st_mode) ? S_IFREG : statl.st_mode & S_IFMT); /* Sanitize the guest path of the binding within the alternate rootfs since it is assumed by substitute_binding(). */ status = canonicalize(tracee, path, dereference, binding->guest.path, 0); if (status < 0) { notice(tracee, WARNING, INTERNAL, "sanitizing the guest path (binding) \"%s\": %s", path, strerror(-status)); return; } /* Remove the trailing "/" or "/." as expected by * substitute_binding(). */ chop_finality(binding->guest.path); /* Disable definitively the creation of the glue for * this binding. */ tracee->glue_type = 0; } binding->guest.length = strlen(binding->guest.path); insort_binding2(tracee, binding); } /** * Add bindings induced by @new_binding when @tracee is being sub-reconfigured. * For example, if the previous configuration ("-r /rootfs1") contains this * binding: * * -b /home/ced:/usr/local/ced * * and if the current configuration ("-r /rootfs2") introduces such a new * binding: * * -b /usr:/media * * then the following binding is induced: * * -b /home/ced:/media/local/ced */ static void add_induced_bindings(Tracee *tracee, const Binding *new_binding) { Binding *old_binding; char path[PATH_MAX]; int status; /* Only for reconfiguration. */ if (tracee->reconf.tracee == NULL) return; /* From the example, PRoot has already converted "-b /usr:/media" into * "-b /rootfs1/usr:/media" in order to ensure the host part is really a * host path. Here, the host part is converted back to "/usr" since the * comparison can't be made on "/rootfs1/usr". */ strcpy(path, new_binding->host.path); status = detranslate_path(tracee->reconf.tracee, path, NULL); if (status < 0) return; CIRCLEQ_FOREACH_(tracee->reconf.tracee, old_binding, GUEST) { Binding *induced_binding; Comparison comparison; char path2[PATH_MAX]; size_t prefix_length; /* Check if there's an induced binding by searching a common * path prefix in between new/old bindings: * * -b /home/ced:[/usr]/local/ced * -b [/usr]:/media */ comparison = compare_paths(path, old_binding->guest.path); if (comparison != PATH1_IS_PREFIX) continue; /* Convert the path of this induced binding to the new * filesystem namespace. From the example, "/usr/local/ced" is * converted into "/media/local/ced". Note: substitute_binding * can't be used in this case since it would expect * "/rootfs1/usr/local/ced instead". */ prefix_length = strlen(path); if (prefix_length == 1) prefix_length = 0; status = join_paths(2, path2, new_binding->guest.path, old_binding->guest.path + prefix_length); if (status < 0) continue; /* Install the induced binding. From the example: * * -b /home/ced:/media/local/ced */ induced_binding = talloc_zero(tracee->ctx, Binding); if (induced_binding == NULL) continue; strcpy(induced_binding->host.path, old_binding->host.path); strcpy(induced_binding->guest.path, path2); induced_binding->host.length = strlen(induced_binding->host.path); induced_binding->guest.length = strlen(induced_binding->guest.path); VERBOSE(tracee, 2, "induced binding: %s:%s (old) & %s:%s (new) -> %s:%s (induced)", old_binding->host.path, old_binding->guest.path, path, new_binding->guest.path, induced_binding->host.path, induced_binding->guest.path); insort_binding2(tracee, induced_binding); } } /** * Allocate @tracee->fs->bindings.guest and * @tracee->fs->bindings.host, then call initialize_binding() on each * binding listed in @tracee->fs->bindings.pending. */ int initialize_bindings(Tracee *tracee) { Binding *binding; /* Sanity checks. */ assert(get_root(tracee) != NULL); assert(tracee->fs->bindings.pending != NULL); assert(tracee->fs->bindings.guest == NULL); assert(tracee->fs->bindings.host == NULL); /* Allocate @tracee->fs->bindings.guest and * @tracee->fs->bindings.host. */ tracee->fs->bindings.guest = talloc_zero(tracee->fs, Bindings); tracee->fs->bindings.host = talloc_zero(tracee->fs, Bindings); if (tracee->fs->bindings.guest == NULL || tracee->fs->bindings.host == NULL) { notice(tracee, ERROR, INTERNAL, "can't allocate enough memory"); TALLOC_FREE(tracee->fs->bindings.guest); TALLOC_FREE(tracee->fs->bindings.host); return -1; } CIRCLEQ_INIT(tracee->fs->bindings.guest); CIRCLEQ_INIT(tracee->fs->bindings.host); talloc_set_destructor(tracee->fs->bindings.guest, remove_bindings); talloc_set_destructor(tracee->fs->bindings.host, remove_bindings); /* The binding to "/" has to be installed before other * bindings since this former is required to canonicalize * these latters. */ binding = CIRCLEQ_LAST(tracee->fs->bindings.pending); assert(compare_paths(binding->guest.path, "/") == PATHS_ARE_EQUAL); /* Call initialize_binding() on each pending binding in * reverse order: the last binding "/" is used to bootstrap * the canonicalization. */ while (binding != (void *) tracee->fs->bindings.pending) { Binding *previous; previous = CIRCLEQ_PREV(binding, link.pending); /* Canonicalize then insert this binding into * tracee->fs->bindings.guest/host. */ initialize_binding(tracee, binding); /* Add induced bindings on sub-reconfiguration. */ add_induced_bindings(tracee, binding); binding = previous; } TALLOC_FREE(tracee->fs->bindings.pending); if (tracee->verbose > 0) print_bindings(tracee); return 0; } care-2.2.1/src/path/temp.h0000644000175000017500000000235712430615044014651 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef TEMP_H #define TEMP_H #include #include "tracee/tracee.h" extern char *create_temp_name(const Tracee *tracee, const char *prefix); extern const char *create_temp_directory(const Tracee *tracee, const char *prefix); extern const char *create_temp_file(const Tracee *tracee, const char *prefix); extern FILE* open_temp_file(const Tracee *tracee, const char *prefix); #endif /* TEMP_H */ care-2.2.1/src/path/proc.c0000644000175000017500000001301212430615044014630 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* snprintf(3), */ #include /* strcmp(3), */ #include /* atoi(3), strtol(3), */ #include /* E*, */ #include /* assert(3), */ #include "path/proc.h" #include "tracee/tracee.h" #include "path/path.h" #include "path/binding.h" /** * This function emulates the @result of readlink("@base/@component") * with respect to @tracee, where @base belongs to "/proc" (according * to @comparison). This function returns -errno on error, an enum * @action otherwise (c.f. above). * * Unlike readlink(), this function includes the nul terminating byte * to @result. */ Action readlink_proc(const Tracee *tracee, char result[PATH_MAX], const char base[PATH_MAX], const char component[NAME_MAX], Comparison comparison) { const Tracee *known_tracee; char proc_path[64]; /* 64 > sizeof("/proc//fd/") + 2 * sizeof(#ULONG_MAX) */ int status; pid_t pid; assert(comparison == compare_paths("/proc", base)); /* Remember: comparison = compare_paths("/proc", base) */ switch (comparison) { case PATHS_ARE_EQUAL: /* Substitute "/proc/self" with "/proc/". */ if (strcmp(component, "self") != 0) return DEFAULT; status = snprintf(result, PATH_MAX, "/proc/%d", tracee->pid); if (status < 0 || status >= PATH_MAX) return -EPERM; return CANONICALIZE; case PATH1_IS_PREFIX: /* Handle "/proc/" below, where is process * monitored by PRoot. */ break; default: return DEFAULT; } pid = atoi(base + strlen("/proc/")); if (pid == 0) return DEFAULT; /* Handle links in "/proc//". */ status = snprintf(proc_path, sizeof(proc_path), "/proc/%d", pid); if (status < 0 || (size_t) status >= sizeof(proc_path)) return -EPERM; comparison = compare_paths(proc_path, base); switch (comparison) { case PATHS_ARE_EQUAL: known_tracee = get_tracee(tracee, pid, false); if (known_tracee == NULL) return DEFAULT; #define SUBSTITUTE(name, string) \ do { \ if (strcmp(component, #name) != 0) \ break; \ \ status = strlen(string); \ if (status >= PATH_MAX) \ return -EPERM; \ \ strncpy(result, string, status + 1); \ return CANONICALIZE; \ } while (0) /* Substitute link "/proc//???" with the content * of tracee->???. */ SUBSTITUTE(exe, known_tracee->exe); SUBSTITUTE(cwd, known_tracee->fs->cwd); SUBSTITUTE(root, get_root(known_tracee)); #undef SUBSTITUTE return DEFAULT; case PATH1_IS_PREFIX: /* Handle "/proc//???" below. */ break; default: return DEFAULT; } /* Handle links in "/proc//fd/". */ status = snprintf(proc_path, sizeof(proc_path), "/proc/%d/fd", pid); if (status < 0 || (size_t) status >= sizeof(proc_path)) return -EPERM; comparison = compare_paths(proc_path, base); switch (comparison) { char *end_ptr; case PATHS_ARE_EQUAL: /* Sanity check: a number is expected. */ errno = 0; (void) strtol(component, &end_ptr, 10); if (errno != 0 || end_ptr == component) return -EPERM; /* Don't dereference "/proc//fd/???" now: they * can point to anonymous pipe, socket, ... otherwise * they point to a path already canonicalized by the * kernel. * * Note they are still correctly detranslated in * syscall/exit.c if a monitored process uses * readlink() against any of them. */ status = snprintf(result, PATH_MAX, "%s/%s", base, component); if (status < 0 || status >= PATH_MAX) return -EPERM; return DONT_CANONICALIZE; default: break; } return DEFAULT; } /** * This function emulates the @result of readlink("@referer") with * respect to @tracee, where @referer is a strict subpath of "/proc". * This function returns -errno if an error occured, the length of * @result if the readlink was emulated, 0 otherwise. * * Unlike readlink(), this function includes the nul terminating byte * to @result (but this byte is not counted in the returned value). */ ssize_t readlink_proc2(const Tracee *tracee, char result[PATH_MAX], const char referer[PATH_MAX]) { Action action; char base[PATH_MAX]; char *component; /* Sanity check. */ if (strnlen(referer, PATH_MAX) >= PATH_MAX) return -ENAMETOOLONG; assert(compare_paths("/proc", referer) == PATH1_IS_PREFIX); /* It's safe to use strrchr() here since @referer was * previously canonicalized. */ strcpy(base, referer); component = strrchr(base, '/'); /* These cases are not possible: @referer is supposed to be a * canonicalized subpath of "/proc". */ assert(component != NULL && component != base); component[0] = '\0'; component++; if (component[0] == '\0') return 0; action = readlink_proc(tracee, result, base, component, PATH1_IS_PREFIX); return (action == CANONICALIZE ? strlen(result) : 0); } care-2.2.1/src/path/glue.h0000644000175000017500000000212012430615044014624 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef GLUE_H #define GLUE_H #include /* PATH_MAX, */ #include "tracee/tracee.h" #include "path.h" extern mode_t build_glue(Tracee *tracee, const char *guest_path, char host_path[PATH_MAX], Finality finality); #endif /* GLUE_H */ care-2.2.1/src/GNUmakefile0000644000175000017500000001126612430615044014650 0ustar ivoireivoire# If you want to build outside of the source tree, use the -f option: # make -f ${SOMEWHERE}/proot/src/GNUmakefile # the VPATH variable must point to the actual makefile directory VPATH := $(dir $(lastword $(MAKEFILE_LIST))) GIT = git RM = rm INSTALL = install CC = gcc LD = $(CC) CPPFLAGS += -D_FILE_OFFSET_BITS=64 -D_GNU_SOURCE -I. -I$(VPATH) CFLAGS += -Wall -Wextra -O2 LDFLAGS += -ltalloc CARE_LDFLAGS = -larchive OBJECTS += \ cli/cli.o \ cli/proot.o \ cli/notice.o \ execve/execve.o \ execve/interp.o \ execve/elf.o \ execve/ldso.o \ execve/auxv.o \ path/binding.o \ path/glue.o \ path/canon.o \ path/path.o \ path/proc.o \ path/temp.o \ syscall/seccomp.o \ syscall/syscall.o \ syscall/chain.o \ syscall/enter.o \ syscall/exit.o \ syscall/sysnum.o \ syscall/socket.o \ syscall/heap.o \ tracee/tracee.o \ tracee/array.o \ tracee/mem.o \ tracee/reg.o \ tracee/event.o \ ptrace/ptrace.o \ ptrace/user.o \ ptrace/wait.o \ extension/extension.o \ extension/kompat/kompat.o \ extension/fake_id0/fake_id0.o CARE_OBJECTS = \ cli/care.o \ cli/care-manual.o \ extension/care/care.o \ extension/care/final.o \ extension/care/extract.o \ extension/care/archive.o .DEFAULT_GOAL = proot all: proot ###################################################################### # Beautified output quiet_GEN = @echo " GEN $@"; $(GEN) quiet_CC = @echo " CC $@"; $(CC) quiet_LD = @echo " LD $@"; $(LD) quiet_INSTALL = @echo " INSTALL $?"; $(INSTALL) V = 0 ifeq ($(V), 0) quiet = quiet_ Q = @ silently = >/dev/null 2>&1 else quiet = Q = silently = endif ###################################################################### # Auto-configuration CHECK_VERSION = VERSION=$$($(GIT) describe --tags --dirty --abbrev=8 --always 2>/dev/null); \ if [ ! -z "$${VERSION}" ]; \ then /bin/echo -e "\#undef VERSION\n\#define VERSION \"$${VERSION}\""; \ fi; CHECK_FEATURES = process_vm seccomp_filter CHECK_PROGRAMS = $(foreach feature,$(CHECK_FEATURES),.check_$(feature)) CHECK_OBJECTS = $(foreach feature,$(CHECK_FEATURES),.check_$(feature).o) CHECK_RESULTS = $(foreach feature,$(CHECK_FEATURES),.check_$(feature).res) .SILENT .IGNORE .INTERMEDIATE: $(CHECK_OBJECTS) $(CHECK_PROGRAMS) .check_%.o: .check_%.c $(COMPILE:echo=false) $(silently) || true .check_%: .check_%.o $(LINK:echo=false) $(silently) || true .check_%.res: .check_% $(Q)if [ -e $< ]; then echo "#define HAVE_$(shell echo $* | tr a-z A-Z)" > $@; else echo "" > $@; fi build.h: $(CHECK_RESULTS) $($(quiet)GEN) $(Q)echo "/* This file is auto-generated, edit at your own risk. */" > $@ $(Q)echo "#ifndef BUILD_H" >> $@ $(Q)echo "#define BUILD_H" >> $@ $(Q)sh -c '$(CHECK_VERSION)' >> $@ $(Q)cat $^ >> $@ $(Q)echo "#endif /* BUILD_H */" >> $@ ###################################################################### # Build rules SRC = $(dir $(firstword $(MAKEFILE_LIST))) COMPILE = $($(quiet)CC) $(CPPFLAGS) $(CFLAGS) -MD -c $(SRC)$(@:.o=.c) -o $@ LINK = $($(quiet)LD) -o $@ $^ $(LDFLAGS) OBJIFY = $($(quiet)GEN) \ objcopy \ --input binary \ --output `env LANG=C objdump -f cli/cli.o | \ perl -pe '/file format (.*)$$/; $$_ = $$1'` \ --binary-architecture `env LANG=C objdump -f cli/cli.o | \ perl -pe '/architecture: (\w+).*/; $$_ = $$1'` \ $< $@ proot: $(OBJECTS) $(LINK) care: $(OBJECTS) $(CARE_OBJECTS) $(LINK) $(CARE_LDFLAGS) # Special case to compute which files depend on the auto-generated # file "build.h". USE_BUILD_H := $(patsubst $(SRC)%,%,$(patsubst %.c,%.o,$(shell egrep -l 'include[[:space:]]+"build.h"' $(SRC)*/*.c))) $(USE_BUILD_H): build.h %.o: %.c @mkdir -p $(dir $@) $(COMPILE) .INTERMEDIATE: manual manual: $(VPATH)/../doc/care/manual.txt $(Q)cp $< $@ cli/care-manual.o: manual cli/cli.o $(OBJIFY) cli/%-licenses.o: licenses cli/cli.o $(OBJIFY) ###################################################################### # Dependencies .DELETE_ON_ERROR: $(OBJECTS) $(CARE_OBJECTS): $(firstword $(MAKEFILE_LIST)) DEPS = $(OBJECTS:.o=.d) $(CARE_OBJECTS:.o=.d) $(CHECK_OBJECTS:.o=.d) -include $(DEPS) ###################################################################### # PHONY targets PREFIX = /usr/local DESTDIR = $(PREFIX)/bin .PHONY: clean distclean install install-care uninstall clean distclean: -$(RM) -f $(CHECK_OBJECTS) $(CHECK_PROGRAMS) $(CHECK_RESULTS) $(OBJECTS) $(CARE_OBJECTS) proot care cli/care-manual.o $(DEPS) build.h licenses install: proot $($(quiet)INSTALL) -D $< $(DESTDIR)/$< install-care: care $($(quiet)INSTALL) -D $< $(DESTDIR)/$< uninstall: -$(RM) -f $(DESTDIR)/proot uninstall-care: -$(RM) -f $(DESTDIR)/care care-2.2.1/src/execve/0000755000175000017500000000000012430615044014047 5ustar ivoireivoirecare-2.2.1/src/execve/interp.h0000644000175000017500000000254612430615044015530 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef INTERP_H #define INTERP_H #include /* PATH_MAX, ARG_MAX, */ #include "tracee/tracee.h" typedef int (* extract_interp_t)(const Tracee *tracee, const char *t_path, char u_interp[PATH_MAX], char argument[ARG_MAX]); extern int extract_script_interp(const Tracee *tracee, const char *t_path, char u_interp[PATH_MAX], char argument[ARG_MAX]); extern int extract_elf_interp(const Tracee *tracee, const char *t_path, char u_interp[PATH_MAX], char argument[ARG_MAX]); #endif /* INTERP_H */ care-2.2.1/src/execve/interp.c0000644000175000017500000001342412430615044015520 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #define _XOPEN_SOURCE 500 /* pread(2), */ #include /* open(2), */ #include /* read(2), close(2), */ #include /* PATH_MAX, ARG_MAX, */ #include /* ENAMETOOLONG, */ #include /* strcpy(3), */ #include "execve/interp.h" #include "execve/elf.h" #include "attribute.h" #include "compat.h" /** * Extract the shebang of @t_path in @u_interp and @arg_max. This * function returns -errno if an error occured, 1 if a shebang was * found and extracted, otherwise 0. * * Extract from "man 2 execve": * * On Linux, the entire string following the interpreter name is * passed as a *single* argument to the interpreter, and this * string can include white space. */ int extract_script_interp(const Tracee *tracee UNUSED, const char *t_path, char u_interp[PATH_MAX], char argument[ARG_MAX]) { char tmp; int status; int fd; int i; argument[0] = '\0'; /* Inspect the executable. */ fd = open(t_path, O_RDONLY); if (fd < 0) return -errno; status = read(fd, u_interp, 2 * sizeof(char)); if (status < 0) { status = -errno; goto end; } if ((size_t) status < 2 * sizeof(char)) { /* EOF */ status = 0; goto end; } /* Check if it really is a script text. */ if (u_interp[0] != '#' || u_interp[1] != '!') { status = 0; goto end; } /* Skip leading spaces. */ do { status = read(fd, &tmp, sizeof(char)); if (status < 0) { status = -errno; goto end; } if ((size_t) status < sizeof(char)) { /* EOF */ status = 0; goto end; } } while (tmp == ' ' || tmp == '\t'); /* Slurp the interpreter path until the first space or end-of-line. */ for (i = 0; i < PATH_MAX; i++) { switch (tmp) { case ' ': case '\t': /* Remove spaces in between the interpreter * and the hypothetical argument. */ u_interp[i] = '\0'; break; case '\n': case '\r': /* There is no argument. */ u_interp[i] = '\0'; argument[0] = '\0'; status = 1; goto end; default: /* There is an argument if the previous * character in u_interp[] is '\0'. */ if (i > 1 && u_interp[i - 1] == '\0') goto argument; else u_interp[i] = tmp; break; } status = read(fd, &tmp, sizeof(char)); if (status < 0) { status = -errno; goto end; } if ((size_t) status < sizeof(char)) { /* EOF */ u_interp[i] = '\0'; argument[0] = '\0'; status = 1; goto end; } } /* The interpreter path is too long. */ status = -ENAMETOOLONG; goto end; argument: /* Slurp the argument until the end-of-line. */ for (i = 0; i < ARG_MAX; i++) { switch (tmp) { case '\n': case '\r': argument[i] = '\0'; /* Remove trailing spaces. */ for (i--; i > 0 && (argument[i] == ' ' || argument[i] == '\t'); i--) argument[i] = '\0'; status = 1; goto end; default: argument[i] = tmp; break; } status = read(fd, &tmp, sizeof(char)); if (status < 0) { status = -errno; goto end; } if ((size_t) status < sizeof(char)) { /* EOF */ argument[0] = '\0'; status = 1; goto end; } } /* The argument is too long, just ignore it. */ argument[0] = '\0'; end: close(fd); /* Did an error occur or isn't a script? */ if (status <= 0) return status; return 1; } /** * Extract the ELF interpreter of @path in @u_interp. This function * returns -errno if an error occured, 1 if a ELF interpreter was * found and extracted, otherwise 0. */ int extract_elf_interp(const Tracee *tracee, const char *t_path, char u_interp[PATH_MAX], char argument[ARG_MAX]) { ElfHeader elf_header; ProgramHeader program_header; size_t extra_size; int status; int fd; uint64_t segment_offset; uint64_t segment_size; u_interp[0] = '\0'; argument[0] = '\0'; fd = open_elf(t_path, &elf_header); if (fd < 0) return fd; status = find_program_header(tracee, fd, &elf_header, &program_header, PT_INTERP, (uint64_t) -1); if (status <= 0) goto end; segment_offset = PROGRAM_FIELD(elf_header, program_header, offset); segment_size = PROGRAM_FIELD(elf_header, program_header, filesz); /* If we are executing a host binary under a QEMUlated * environment, we have to access its ELF interpreter through * the "host-rootfs" binding. Technically it means the host * ELF interpreter "/lib/ld-linux.so.2" is accessed as * "${HOST_ROOTFS}/lib/ld-linux.so.2" to avoid conflict with * the guest "/lib/ld-linux.so.2". */ if (tracee->qemu != NULL) { strcpy(u_interp, HOST_ROOTFS); extra_size = strlen(HOST_ROOTFS); } else extra_size = 0; if (segment_size + extra_size >= PATH_MAX) { status = -EACCES; goto end; } status = pread(fd, u_interp + extra_size, segment_size, segment_offset); if (status < 0) goto end; if ((size_t) status != segment_size) { /* Unexpected size. */ status = -EACCES; goto end; } u_interp[segment_size + extra_size] = '\0'; end: close(fd); /* Delayed error handling */ if (status < 0) return status; /* Is there an INTERP entry? */ if (u_interp[0] == '\0') return 0; else return 1; } care-2.2.1/src/execve/ldso.h0000644000175000017500000000247612430615044015172 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef LDSO_H #define LDSO_H #include #include #include "tracee/array.h" extern int ldso_env_passthru(const Tracee *tracee, Array *envp, Array *argv, const char *define, const char *undefine); extern int rebuild_host_ldso_paths(Tracee *tracee, const char t_program[PATH_MAX], Array *envp); extern int compare_item_env(Array *array, size_t index, const char *name); extern bool is_env_name(const char *variable, const char *name); #endif /* LDSO_H */ care-2.2.1/src/execve/ldso.c0000644000175000017500000002353012430615044015157 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* bool, true, false, */ #include /* strlen(3), strcpy(3), strcat(3), strcmp(3), */ #include /* getenv(3), */ #include /* assert(3), */ #include /* ENOMEM, */ #include /* close(2), */ #include /* PATH_MAX, ARG_MAX, */ #include "execve/ldso.h" #include "execve/execve.h" #include "execve/elf.h" #include "tracee/tracee.h" #include "tracee/array.h" #include "cli/notice.h" /** * Check if the environment @variable has the given @name. */ bool is_env_name(const char *variable, const char *name) { size_t length = strlen(name); return (variable[0] == name[0] && length < strlen(variable) && variable[length] == '=' && strncmp(variable, name, length) == 0); } /** * This function returns 1 or 0 depending on the equivalence of the * @reference environment variable and the one pointed to by the entry * in @array at the given @index, otherwise it returns -errno when an * error occured. */ int compare_item_env(Array *array, size_t index, const char *reference) { char *value; int status; assert(index < array->length); status = read_item_string(array, index, &value); if (status < 0) return status; if (value == NULL) return 0; return (int)is_env_name(value, reference); } /** * This function ensures that environment variables related to the * dynamic linker are applied to the emulated program, not to QEMU * itself. For instance, let's say the user has entered the * command-line below: * * env LD_TRACE_LOADED_OBJECTS=1 /bin/ls * * It should be converted to: * * qemu -E LD_TRACE_LOADED_OBJECTS=1 /bin/ls * * instead of: * * env LD_TRACE_LOADED_OBJECTS=1 qemu /bin/ls * * Note that the LD_LIBRARY_PATH variable is always required to run * QEMU (a host binary): * * env LD_LIBRARY_PATH=... qemu -U LD_LIBRARY_PATH /bin/ls * * or when LD_LIBRARY_PATH was also specified by the user: * * env LD_LIBRARY_PATH=... qemu -E LD_LIBRARY_PATH=... /bin/ls * * This funtion returns -errno if an error occured, otherwise 0. */ int ldso_env_passthru(const Tracee *tracee, Array *envp, Array *argv, const char *define, const char *undefine) { bool has_seen_library_path = false; int status; size_t i; for (i = 0; i < envp->length; i++) { bool is_known = false; char *env; status = read_item_string(envp, i, &env); if (status < 0) return status; /* Skip variables that do not start with "LD_". */ if (env == NULL || strncmp(env, "LD_", sizeof("LD_") - 1) != 0) continue; /* When a host program executes a guest program, use * the value of LD_LIBRARY_PATH as it was before being * swapped by the mixed-mode support. */ if ( tracee->host_ldso_paths != NULL && tracee->guest_ldso_paths != NULL && is_env_name(env, "LD_LIBRARY_PATH") && strcmp(env, tracee->host_ldso_paths) == 0) env = (char *) tracee->guest_ldso_paths; #define PASSTHRU(check, name) \ if (is_env_name(env, name)) { \ check |= true; \ /* Errors are not fatal here. */ \ status = resize_array(argv, 1, 2); \ if (status >= 0) { \ status = write_items(argv, 1, 2, define, env); \ if (status < 0) \ return status; \ } \ write_item(envp, i, ""); \ continue; \ } \ PASSTHRU(has_seen_library_path, "LD_LIBRARY_PATH"); PASSTHRU(is_known, "LD_PRELOAD"); PASSTHRU(is_known, "LD_BIND_NOW"); PASSTHRU(is_known, "LD_TRACE_LOADED_OBJECTS"); PASSTHRU(is_known, "LD_AOUT_LIBRARY_PATH"); PASSTHRU(is_known, "LD_AOUT_PRELOAD"); PASSTHRU(is_known, "LD_AUDIT"); PASSTHRU(is_known, "LD_BIND_NOT"); PASSTHRU(is_known, "LD_DEBUG"); PASSTHRU(is_known, "LD_DEBUG_OUTPUT"); PASSTHRU(is_known, "LD_DYNAMIC_WEAK"); PASSTHRU(is_known, "LD_HWCAP_MASK"); PASSTHRU(is_known, "LD_KEEPDIR"); PASSTHRU(is_known, "LD_NOWARN"); PASSTHRU(is_known, "LD_ORIGIN_PATH"); PASSTHRU(is_known, "LD_POINTER_GUARD"); PASSTHRU(is_known, "LD_PROFILE"); PASSTHRU(is_known, "LD_PROFILE_OUTPUT"); PASSTHRU(is_known, "LD_SHOW_AUXV"); PASSTHRU(is_known, "LD_USE_LOAD_BIAS"); PASSTHRU(is_known, "LD_VERBOSE"); PASSTHRU(is_known, "LD_WARN"); } if (!has_seen_library_path) { /* Errors are not fatal here. */ status = resize_array(argv, 1, 2); if (status >= 0) { status = write_items(argv, 1, 2, undefine, "LD_LIBRARY_PATH"); if (status < 0) return status; } } return 0; } /** * Add to @host_ldso_paths the list of @paths prefixed with the path * to the host rootfs. */ static int add_host_ldso_paths(char host_ldso_paths[ARG_MAX], const char *paths) { char *cursor1; const char *cursor2; cursor1 = host_ldso_paths + strlen(host_ldso_paths); cursor2 = paths; do { bool is_absolute; size_t length1; size_t length2 = strcspn(cursor2, ":"); is_absolute = (*cursor2 == '/'); length1 = 1 + length2; if (is_absolute) length1 += strlen(HOST_ROOTFS); /* Check there's enough room. */ if (cursor1 + length1 >= host_ldso_paths + ARG_MAX) return -ENOEXEC; if (cursor1 != host_ldso_paths) { strcpy(cursor1, ":"); cursor1++; } /* Since we are executing a host binary under a * QEMUlated environment, we have to access its * library paths through the "host-rootfs" binding. * Technically it means a path like "/lib" is accessed * as "${HOST_ROOTFS}/lib" to avoid conflict with the * guest "/lib". */ if (is_absolute) { strcpy(cursor1, HOST_ROOTFS); cursor1 += strlen(HOST_ROOTFS); } strncpy(cursor1, cursor2, length2); cursor1 += length2; cursor2 += length2 + 1; } while (*(cursor2 - 1) != '\0'); *cursor1 = '\0'; return 0; } /** * Rebuild the variable LD_LIBRARY_PATH in @envp for @t_program * according to its RPATH, RUNPATH, and the initial LD_LIBRARY_PATH. * This function returns -errno if an error occured, 1 if * RPATH/RUNPATH entries were found, 0 otherwise. */ int rebuild_host_ldso_paths(Tracee *tracee, const char t_program[PATH_MAX], Array *envp) { static char *initial_ldso_paths = NULL; ElfHeader elf_header; char host_ldso_paths[ARG_MAX] = ""; bool inhibit_rpath = false; char *rpaths = NULL; char *runpaths = NULL; size_t length1; size_t length2; size_t index; int status; int fd; fd = open_elf(t_program, &elf_header); if (fd < 0) return fd; status = read_ldso_rpaths(tracee, fd, &elf_header, &rpaths, &runpaths); close(fd); if (status < 0) return status; /* 1. DT_RPATH */ if (rpaths && !runpaths) { status = add_host_ldso_paths(host_ldso_paths, rpaths); if (status < 0) return 0; /* Not fatal. */ inhibit_rpath = true; } /* 2. LD_LIBRARY_PATH */ if (initial_ldso_paths == NULL) initial_ldso_paths = strdup(getenv("LD_LIBRARY_PATH") ?: "/"); if (initial_ldso_paths != NULL && initial_ldso_paths[0] != '\0') { status = add_host_ldso_paths(host_ldso_paths, initial_ldso_paths); if (status < 0) return 0; /* Not fatal. */ } /* 3. DT_RUNPATH */ if (runpaths) { status = add_host_ldso_paths(host_ldso_paths, runpaths); if (status < 0) return 0; /* Not fatal. */ inhibit_rpath = true; } /* 4. /etc/ld.so.cache NYI. */ /* 5. /lib[32|64], /usr/lib[32|64] + /usr/local/lib[32|64] */ /* 6. /lib, /usr/lib + /usr/local/lib */ if (IS_CLASS32(elf_header)) status = add_host_ldso_paths(host_ldso_paths, #if defined(ARCH_X86) || defined(ARCH_X86_64) "/lib/i386-linux-gnu:/usr/lib/i386-linux-gnu:" #endif "/lib32:/usr/lib32:/usr/local/lib32" ":/lib:/usr/lib:/usr/local/lib"); else status = add_host_ldso_paths(host_ldso_paths, #if defined(ARCH_X86_64) "/lib/x86_64-linux-gnu:/usr/lib/x86_64-linux-gnu:" #endif "/lib64:/usr/lib64:/usr/local/lib64" ":/lib:/usr/lib:/usr/local/lib"); if (status < 0) return 0; /* Not fatal. */ status = find_item(envp, "LD_LIBRARY_PATH"); if (status < 0) return 0; /* Not fatal. */ index = (size_t) status; if (index == envp->length) { /* Allocate a new entry at the end of envp[] when * LD_LIBRARY_PATH was not found. */ index = (envp->length > 0 ? envp->length - 1 : 0); status = resize_array(envp, index, 1); if (status < 0) return 0; /* Not fatal. */ } else if (tracee->guest_ldso_paths == NULL) { /* Remember guest LD_LIBRARY_PATH in order to restore * it when a host program will execute a guest * program. */ char *env; /* Errors are not fatal here. */ status = read_item_string(envp, index, &env); if (status >= 0) tracee->guest_ldso_paths = talloc_strdup(tracee, env); } /* Forge the new LD_LIBRARY_PATH variable from * host_ldso_paths. */ length1 = strlen("LD_LIBRARY_PATH="); length2 = strlen(host_ldso_paths); if (ARG_MAX - length2 - 1 < length1) return 0; /* Not fatal. */ memmove(host_ldso_paths + length1, host_ldso_paths, length2 + 1); memcpy(host_ldso_paths, "LD_LIBRARY_PATH=" , length1); write_item(envp, index, host_ldso_paths); /* The guest LD_LIBRARY_PATH will be restored only if the host * program didn't change it explicitly, so remember its * initial value. */ if (tracee->host_ldso_paths == NULL) tracee->host_ldso_paths = talloc_strdup(tracee, host_ldso_paths); return (int) inhibit_rpath; } care-2.2.1/src/execve/execve.c0000644000175000017500000003624612430615044015505 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* lstat(2), S_ISREG(), */ #include /* access(2), lstat(2), */ #include /* string(3), */ #include /* assert(3), */ #include /* E*, */ #include /* talloc_, */ #include "execve/execve.h" #include "execve/interp.h" #include "execve/elf.h" #include "execve/ldso.h" #include "tracee/array.h" #include "tracee/tracee.h" #include "syscall/syscall.h" #include "cli/notice.h" #include "path/path.h" #include "path/binding.h" #include "extension/extension.h" #include "compat.h" /** * Translate @u_path into @t_path and check if this latter exists, is * executable and is a regular file. This function returns -errno if * an error occured, 0 otherwise. */ static int translate_n_check(Tracee *tracee, char t_path[PATH_MAX], const char *u_path) { struct stat statl; int status; status = translate_path(tracee, t_path, AT_FDCWD, u_path, true); if (status < 0) return status; status = access(t_path, F_OK); if (status < 0) return -ENOENT; status = access(t_path, X_OK); if (status < 0) return -EACCES; status = lstat(t_path, &statl); if (status < 0) return -EPERM; return 0; } /** * Substitute *@argv[0] with the interpreter (and its argument) of the * program pointed to by @u_path. The paths to the interpreter (or to * the program itself if it doesn't use an interpreter) are stored in * @t_interp and @u_interp (respectively translated and untranslated). */ static int expand_interp(Tracee *tracee, const char *u_path, char t_interp[PATH_MAX], char u_interp[PATH_MAX], Array *argv, extract_interp_t callback, bool ignore_interpreter) { char argument[ARG_MAX]; char dummy_path[PATH_MAX]; char dummy_arg[ARG_MAX]; int status; status = translate_n_check(tracee, t_interp, u_path); if (status < 0) return status; /* Skip the extraction of the interpreter on demand, in * this case we execute the translation of u_path (t_interp) * directly. */ if (ignore_interpreter) { strcpy(u_interp, u_path); return 0; } /* Extract the interpreter of t_interp in u_interp + argument. */ status = callback(tracee, t_interp, u_interp, argument); if (status < 0) return status; /* Specific support for GDB: it assumes the program is loaded * in memory once execve() has completed, however this is not * the case under PRoot since it replaces the executed * programs with a loader (the ELF interpreter for now). */ if (callback == extract_elf_interp) tracee->as_ptracee.is_load_pending = (status != 0); /* No interpreter was found, in this case we execute the * translation of u_path (t_interp) directly. */ if (status == 0) { strcpy(u_interp, u_path); return 0; } status = translate_n_check(tracee, t_interp, u_interp); if (status < 0) return status; /* Sanity check: an interpreter doesn't request an[other] * interpreter on Linux. In the case of ELF this check * ensures the interpreter is in the ELF format. */ status = callback(tracee, t_interp, dummy_path, dummy_arg); if (status < 0) return status; if (status != 0) return -EPERM; /* * Substitute argv[0] with the ELF/script interpreter: * * execve("/bin/sh", { "/bin/sh", "/test.sh", NULL }, { NULL }); * * becomes: * * execve("/lib/ld.so", { "/lib/ld.so", "/bin/sh", "/test.sh", NULL }, { NULL }); * * Note: actually the first parameter of execve() is changed * in the caller because it depends on the use of a runner. */ VERBOSE(tracee, 3, "expand shebang: -> %s %s %s", u_interp, argument, u_path); /* Note: argv[0] is not substituted if it is the NULL * terminator (argv->length == 1). */ if (argument[0] != '\0') { status = resize_array(argv, 0, 2 + (argv->length == 1)); if (status < 0) return status; status = write_items(argv, 0, 3, u_interp, argument, u_path); if (status < 0) return status; } else { status = resize_array(argv, 0, 1 + (argv->length == 1)); if (status < 0) return status; status = write_items(argv, 0, 2, u_interp, u_path); if (status < 0) return status; } /* Remember at this point t_interp is the translation of * u_interp. */ return 1; } /** * Check if the binary @host_path is PRoot itself. In that case, @argv and * @envp are used to reconfigure the current @tracee. This function returns * -errno if an error occured, 0 if the binary isn't PRoot, and 1 if @tracee was * reconfigured correctly. */ static int handle_sub_reconf(Tracee *tracee, Array *argv, const char *host_path) { static char *self_exe = NULL; static int no_subreconf = -1; char path[PATH_MAX]; char **argv_pod; Tracee *dummy; int status; size_t i; if (no_subreconf == -1) no_subreconf = (int) (getenv("PROOT_NO_SUBRECONF") != NULL); if (no_subreconf != 0) return 0; /* The path to PRoot itself is cached. */ if (self_exe == NULL) { status = readlink("/proc/self/exe", path, PATH_MAX); if (status < 0 || status >= PATH_MAX) return 0; path[status] = '\0'; self_exe = strdup(path); if (self_exe == NULL) return -ENOMEM; } /* Check if the executed program is PRoot itself. */ if (strcmp(host_path, self_exe) != 0 || argv->length <= 1) return 0; /* Rebuild a POD argv[], as expected by parse_config(). */ argv_pod = talloc_size(tracee->ctx, argv->length * sizeof(char *)); if (argv_pod == NULL) return -ENOMEM; for (i = 0; i < argv->length; i++) { status = read_item_string(argv, i, &argv_pod[i]); if (status < 0) return status; } /* This dummy tracee holds the new configuration that will be copied * back to the original tracee if everything is OK. */ dummy = new_dummy_tracee(tracee->ctx); if (dummy == NULL) return -ENOMEM; /* Inform parse_config() that paths are relative to the current tracee. * For instance, "-w ./foo" will be translated to "-w * ${tracee->cwd}/foo". */ dummy->reconf.tracee = tracee; dummy->reconf.paths = NULL; status = parse_config(dummy, argv->length - 1, argv_pod); if (status < 0) return -ECANCELED; bzero(&dummy->reconf, sizeof(dummy->reconf)); /* How many arguments for the actual command? */ for (i = 0; dummy->cmdline[i] != NULL; i++) ; /* Sanity checks. */ if (i < 1 || i >= argv->length) { notice(tracee, WARNING, INTERNAL, "wrong number of arguments (%zd)", i); return -ECANCELED; } /* Write the actual command back to the tracee's memory. */ status = resize_array(argv, argv->length - 1, i + 1 - argv->length); if (status < 0) return status; for (i = 0; dummy->cmdline[i] != NULL; i++) write_item_string(argv, i, dummy->cmdline[i]); write_item_string(argv, i, NULL); status = push_array(argv, SYSARG_2); if (status < 0) return status; status = set_sysarg_path(tracee, dummy->exe, SYSARG_1); if (status < 0) return status; /* Commit the new configuration. */ status = swap_config(tracee, dummy); if (status < 0) return status; /* Restart the execve() but with the actual command. */ status = translate_execve(tracee); if (status < 0) { /* Something went wrong, revert the new configuration. Maybe * this should be done in syscall/exit.c. */ (void) swap_config(tracee, dummy); return status; } inherit_extensions(tracee, dummy, CLONE_RECONF); /* Disable seccomp acceleration for this tracee and all its * children since unfiltered syscalls might be requested by * new extensions. */ if (tracee->seccomp == ENABLED) tracee->seccomp = DISABLING; return 1; } /** * Translate the arguments of the execve() syscall made by the @tracee * process. This function return -errno if an error occured, * otherwise 0. * * The execve() syscall needs a very special treatment for script * files because according to "man 2 execve": * * An interpreter script is a text file [...] whose first line is * of the form: * * #! interpreter [optional-arg] * * The interpreter must be a valid pathname for an executable * which is not itself a script. If the filename argument of * execve() specifies an interpreter script, then interpreter will * be invoked with the following arguments: * * interpreter [optional-arg] filename arg... * * where arg... is the series of words pointed to by the argv * argument of execve(). * * Let's take the following example: * * execve("/bin/script.sh", argv = [ "script.sh", "arg1", arg2", ... ], envp); * * We can't just translate the first parameter because the kernel * will actually run the interpreter "/bin/sh" with the translated * path to the script file "/tmp/new_root/bin/script.sh" as its first * argument. Technically, we want the opposite behaviour, that is, we * want to run the translated path to the interpreter * "/tmp/new_root/bin/sh" with the de-translated path to the script * "/bin/script.sh" as its first parameter (will be translated later): * * execve("/tmp/new_root/bin/sh", argv = [ "/bin/sh", "/bin/script.sh", "arg1", arg2", ... ], envp); */ int translate_execve(Tracee *tracee) { char u_path[PATH_MAX]; char t_interp[PATH_MAX]; char u_interp[PATH_MAX]; Array *envp = NULL; Array *argv = NULL; char *argv0 = NULL; char **new_cmdline; char *new_exe; size_t i; bool ignore_elf_interpreter; bool inhibit_rpath = false; bool is_script; int status; status = get_sysarg_path(tracee, u_path, SYSARG_1); if (status < 0) return status; status = fetch_array(tracee, &argv, SYSARG_2, 0); if (status < 0) return status; if (tracee->qemu != NULL) { status = read_item_string(argv, 0, &argv0); if (status < 0) return status; /* Save the initial argv[0] since it will be replaced * by tracee->qemu[0]. Errors are not fatal here. */ if (argv0 != NULL) argv0 = talloc_strdup(tracee->ctx, argv0); status = fetch_array(tracee, &envp, SYSARG_3, 0); if (status < 0) return status; /* Environment variables should be compared with the * "name" part in the "name=value" string format. */ envp->compare_item = (compare_item_t)compare_item_env; } status = expand_interp(tracee, u_path, t_interp, u_interp, argv, extract_script_interp, false); if (status < 0) /* The Linux kernel actually returns -EACCES when * trying to execute a directory. */ return status == -EISDIR ? -EACCES : status; is_script = (status > 0); /* It's the rigth place to check if the binary is PRoot itself. */ status = handle_sub_reconf(tracee, argv, t_interp); if (status < 0) return status; if (status > 0) return 0; /* Remember the value for "/proc/self/exe". Note that it points to a * canonicalized guest path, hence detranslate_path(). We re-use u_path * since it is not useful anymore. */ strcpy(u_path, t_interp); (void) detranslate_path(tracee, u_path, NULL); new_exe = talloc_strdup(tracee->ctx, u_path); if (new_exe == NULL) return -ENOMEM; /* Remember the value for "/proc/self/cmdline". */ new_cmdline = talloc_zero_array(tracee->ctx, char *, argv->length); if (new_cmdline == NULL) return -ENOMEM; for (i = 0; i < argv->length; i++) { char *ptr; status = read_item_string(argv, i, &ptr); if (status < 0) return status; /* It's safe to reference these strings since they are never * overwritten, they are just replaced. */ new_cmdline[i] = talloc_reference(new_cmdline, ptr); } if (tracee->qemu != NULL) { /* Sanity check. */ assert(envp != NULL); /* Prepend the QEMU command to the initial argv[] if * it's a "foreign" binary. */ if (!is_host_elf(tracee, t_interp)) { status = resize_array(argv, 0, 3); if (status < 0) return status; /* For example, the second argument of: * execve("/bin/true", { "true", NULL }, ...) * becomes: * { "/usr/bin/qemu", "-0", "true", "/bin/true"} */ status = write_items(argv, 0, 4, tracee->qemu[0], "-0", !is_script && argv0 != NULL ? argv0 : u_interp, u_interp); if (status < 0) return status; status = ldso_env_passthru(tracee, envp, argv, "-E", "-U"); if (status < 0) return status; /* Compute the number of QEMU's arguments and * add them to the modified argv[]. */ for (i = 1; tracee->qemu[i] != NULL; i++) ; status = resize_array(argv, 1, i - 1); if (status < 0) return status; for (i--; i > 0; i--) { status = write_item(argv, i, tracee->qemu[i]); if (status < 0) return status; } /* Launch the runner actually. */ strcpy(t_interp, tracee->qemu[0]); status = join_paths(2, u_interp, HOST_ROOTFS, tracee->qemu[0]); if (status < 0) return status; } /* Provide information to the host dynamic linker to * find host libraries (remember the guest root * file-system contains libraries for the guest * architecture only). */ status = rebuild_host_ldso_paths(tracee, t_interp, envp); if (status < 0) return status; inhibit_rpath = (status > 0); } /* Dont't use the ELF interpreter as a loader if executing * QEMU or if there's no need for RPATH inhibition in * mixed-mode. */ ignore_elf_interpreter = (tracee->qemu_pie_workaround && !inhibit_rpath); status = expand_interp(tracee, u_interp, t_interp, u_path /* dummy */, argv, extract_elf_interp, ignore_elf_interpreter); if (status < 0) return status; if (status > 0 && inhibit_rpath) { /* Tell the dynamic linker to ignore RPATHs specified * in the *main* program. To disable the RPATH * mechanism globally, we have to list all objects * here (NYI). Errors are not fatal here. */ status = resize_array(argv, 1, 2); if (status >= 0) { status = write_items(argv, 1, 2, "--inhibit-rpath", "''"); if (status < 0) return status; } } VERBOSE(tracee, 4, "execve: %s", t_interp); status = set_sysarg_path(tracee, t_interp, SYSARG_1); if (status < 0) return status; status = push_array(argv, SYSARG_2); if (status < 0) return status; status = push_array(envp, SYSARG_3); if (status < 0) return status; /* So far so good, we can now safely update tracee->exe and * tracee->cmdline. Actually it would be safer in syscall/exit.c * however I'm not able to write a test where execve(2) would fail at * kernel level but not in PRoot. Moreover this would require to store * temporarily the original values for exe and cmdline, that is, before * the insertion of the loader (ELF interpreter or QEMU). */ talloc_unlink(tracee, tracee->exe); tracee->exe = talloc_reference(tracee, new_exe); talloc_set_name_const(tracee->exe, "$exe"); talloc_unlink(tracee, tracee->cmdline); tracee->cmdline = talloc_reference(tracee, new_cmdline); talloc_set_name_const(tracee->cmdline, "@cmdline"); return 0; } care-2.2.1/src/execve/execve.h0000644000175000017500000000173212430615044015502 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef EXECVE_H #define EXECVE_H #include "tracee/tracee.h" extern int translate_execve(Tracee *tracee); #endif /* EXECVE_H */ care-2.2.1/src/execve/elf.c0000644000175000017500000002223412430615044014764 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* open(2), */ #include /* read(2), close(2), */ #include /* EACCES, ENOTSUP, */ #include /* UINT64_MAX, */ #include /* PATH_MAX, */ #include /* strnlen(3), strcat(3), strcpy(3), */ #include /* assert(3), */ #include /* strnlen(3), */ #include /* talloc_*, */ #include /* bool, true, false, */ #include "execve/elf.h" #include "tracee/tracee.h" #include "cli/notice.h" #include "arch.h" #include "compat.h" /** * Open the ELF file @t_path and extract its header into @elf_header. * This function returns -errno if an error occured, otherwise the * file descriptor for @t_path. */ int open_elf(const char *t_path, ElfHeader *elf_header) { int fd; int status; /* * Read the ELF header. */ fd = open(t_path, O_RDONLY); if (fd < 0) return -errno; /* Check if it is an ELF file. */ status = read(fd, elf_header, sizeof(ElfHeader)); if (status < 0) goto end; if ((size_t) status < sizeof(ElfHeader) || ELF_IDENT(*elf_header, 0) != 0x7f || ELF_IDENT(*elf_header, 1) != 'E' || ELF_IDENT(*elf_header, 2) != 'L' || ELF_IDENT(*elf_header, 3) != 'F') { status = -ENOEXEC; goto end; } /* Check if it is a known class (32-bit or 64-bit). */ if ( !IS_CLASS32(*elf_header) && !IS_CLASS64(*elf_header)) { status = -ENOEXEC; goto end; } status = 0; end: /* Delayed error handling. */ if (status < 0) { close(fd); return status; } return fd; } /** * Find in the file referenced by @fd -- which has the provided * @elf_header -- the first @program_header of a given @type and * loaded at the given @address (-1 for wherever). This function * returns -errno if an error occured, 1 if the program header was * found, otherwise 0. */ int find_program_header(const Tracee *tracee, int fd, const ElfHeader *elf_header, ProgramHeader *program_header, SegmentType type, uint64_t address) { uint64_t elf_phoff; uint16_t elf_phentsize; uint16_t elf_phnum; int status; int i; /* Get class-specific fields. */ elf_phnum = ELF_FIELD(*elf_header, phnum); elf_phentsize = ELF_FIELD(*elf_header, phentsize); elf_phoff = ELF_FIELD(*elf_header, phoff); /* * Some sanity checks regarding the current * support of the ELF specification in PRoot. */ if (elf_phnum >= 0xffff) { notice(tracee, WARNING, INTERNAL, "%d: big PH tables are not yet supported.", fd); return -ENOTSUP; } if (!KNOWN_PHENTSIZE(*elf_header, elf_phentsize)) { notice(tracee, WARNING, INTERNAL, "%d: unsupported size of program header.", fd); return -ENOTSUP; } /* * Search the first entry of the requested type into the * program header table. */ status = (int) lseek(fd, elf_phoff, SEEK_SET); if (status < 0) return -errno; for (i = 0; i < elf_phnum; i++) { status = read(fd, program_header, elf_phentsize); if (status != elf_phentsize) return (status < 0 ? -errno : -ENOTSUP); if (PROGRAM_FIELD(*elf_header, *program_header, type) == type) { uint64_t start; uint64_t end; if (address == (uint64_t) -1) return 1; start = PROGRAM_FIELD(*elf_header, *program_header, vaddr); end = start + PROGRAM_FIELD(*elf_header, *program_header, memsz); if (start < end && address >= start && address <= end) return 1; } } return 0; } /** * Check if @t_path is an ELF file for the host architecture. */ bool is_host_elf(const Tracee *tracee, const char *t_path) { int host_elf_machine[] = HOST_ELF_MACHINE; static int force_foreign = -1; ElfHeader elf_header; uint16_t elf_machine; int fd; int i; if (force_foreign < 0) force_foreign = (getenv("PROOT_FORCE_FOREIGN_BINARY") != NULL); if (force_foreign > 0 || !tracee->qemu) return false; fd = open_elf(t_path, &elf_header); if (fd < 0) return false; close(fd); elf_machine = ELF_FIELD(elf_header, machine); for (i = 0; host_elf_machine[i] != 0; i++) { if (host_elf_machine[i] == elf_machine) { VERBOSE(tracee, 1, "'%s' is a host ELF", t_path); return true; } } return false; } /** * Add to @xpaths the paths (':'-separated list) from the file * referenced by @fd at the given @offset. This function returns * -errno if an error occured, otherwise 0. */ static int add_xpaths(const Tracee *tracee, int fd, uint64_t offset, char **xpaths) { char *paths = NULL; char *tmp; size_t length; size_t size; int status; status = (int) lseek(fd, offset, SEEK_SET); if (status < 0) return -errno; /* Read the complete list of paths. */ length = 0; paths = NULL; do { size = length + 1024; tmp = talloc_realloc(tracee->ctx, paths, char, size); if (!tmp) return -ENOMEM; paths = tmp; status = read(fd, paths + length, 1024); if (status < 0) return status; length += strnlen(paths + length, 1024); } while (length == size); /* Concatene this list of paths to xpaths. */ if (!*xpaths) { *xpaths = talloc_array(tracee->ctx, char, length + 1); if (!*xpaths) return -ENOMEM; strcpy(*xpaths, paths); } else { length += strlen(*xpaths); length++; /* ":" separator */ tmp = talloc_realloc(tracee->ctx, *xpaths, char, length + 1); if (!tmp) return -ENOMEM; *xpaths = tmp; strcat(*xpaths, ":"); strcat(*xpaths, paths); } /* I don't know if DT_R*PATH entries are unique. In * doubt I support multiple entries. */ return 0; } /** * Put the RPATH and RUNPATH dynamic entries from the file referenced * by @fd -- which has the provided @elf_header -- in @rpaths and * @runpaths respectively. This function returns -errno if an error * occured, otherwise 0. */ int read_ldso_rpaths(const Tracee* tracee, int fd, const ElfHeader *elf_header, char **rpaths, char **runpaths) { ProgramHeader dynamic_segment; ProgramHeader strtab_segment; uint64_t strtab_address = (uint64_t) -1; off_t strtab_offset; int status; size_t i; uint64_t offsetof_dynamic_segment; uint64_t sizeof_dynamic_segment; size_t sizeof_dynamic_entry; status = find_program_header(tracee, fd, elf_header, &dynamic_segment, PT_DYNAMIC, (uint64_t) -1); if (status <= 0) return status; offsetof_dynamic_segment = PROGRAM_FIELD(*elf_header, dynamic_segment, offset); sizeof_dynamic_segment = PROGRAM_FIELD(*elf_header, dynamic_segment, filesz); if (IS_CLASS32(*elf_header)) sizeof_dynamic_entry = sizeof(DynamicEntry32); else sizeof_dynamic_entry = sizeof(DynamicEntry64); if (sizeof_dynamic_segment % sizeof_dynamic_entry != 0) return -ENOEXEC; /** * Invoke @embedded_code on each dynamic entry of the given @type. */ #define FOREACH_DYNAMIC_ENTRY(type, embedded_code) \ for (i = 0; i < sizeof_dynamic_segment / sizeof_dynamic_entry; i++) { \ DynamicEntry dynamic_entry; \ uint64_t value; \ \ /* callback() may change the file offset. */ \ status = (int) lseek(fd, offsetof_dynamic_segment + i * sizeof_dynamic_entry, SEEK_SET); \ if (status < 0) \ return -errno; \ \ status = read(fd, &dynamic_entry, sizeof_dynamic_entry); \ if (status < 0) \ return status; \ \ if (DYNAMIC_FIELD(*elf_header, dynamic_entry, tag) != type) \ continue; \ \ value = DYNAMIC_FIELD(*elf_header, dynamic_entry, val); \ \ embedded_code \ } /* Get the address of the *first* string table. The ELF * specification doesn't mention if it may have several string * table references. */ FOREACH_DYNAMIC_ENTRY(DT_STRTAB, { strtab_address = value; break; }) if (strtab_address == (uint64_t) -1) return 0; /* Search the program header that contains the given string table. */ status = find_program_header(tracee, fd, elf_header, &strtab_segment, PT_LOAD, strtab_address); if (status < 0) return status; strtab_offset = PROGRAM_FIELD(*elf_header, strtab_segment, offset) + (strtab_address - PROGRAM_FIELD(*elf_header, strtab_segment, vaddr)); FOREACH_DYNAMIC_ENTRY(DT_RPATH, { if (strtab_offset < 0 || (uint64_t) strtab_offset > UINT64_MAX - value) return -ENOEXEC; status = add_xpaths(tracee, fd, strtab_offset + value, rpaths); if (status < 0) return status; }) FOREACH_DYNAMIC_ENTRY(DT_RUNPATH, { if (strtab_offset < 0 || (uint64_t) strtab_offset > UINT64_MAX - value) return -ENOEXEC; status = add_xpaths(tracee, fd, strtab_offset + value, runpaths); if (status < 0) return status; }) #undef FOREACH_DYNAMIC_ENTRY return 0; } care-2.2.1/src/execve/auxv.c0000644000175000017500000002102212430615044015173 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* AT_*, */ #include /* assert(3), */ #include /* E*, */ #include /* write(3), close(3), */ #include /* open(2), */ #include /* open(2), */ #include /* open(2), */ #include "execve/auxv.h" #include "syscall/sysnum.h" #include "tracee/tracee.h" #include "tracee/mem.h" #include "path/binding.h" #include "path/temp.h" #include "tracee/abi.h" #include "arch.h" /** * Add the given vector [@type, @value] to @vectors. This function * returns -errno if an error occurred, otherwise 0. */ int add_elf_aux_vector(ElfAuxVector **vectors, word_t type, word_t value) { ElfAuxVector *tmp; size_t nb_vectors; assert(*vectors != NULL); nb_vectors = talloc_array_length(*vectors); /* Sanity checks. */ assert(nb_vectors > 0); assert((*vectors)[nb_vectors - 1].type == AT_NULL); tmp = talloc_realloc(talloc_parent(*vectors), *vectors, ElfAuxVector, nb_vectors + 1); if (tmp == NULL) return -ENOMEM; *vectors = tmp; /* Replace the sentinel with the new vector. */ (*vectors)[nb_vectors - 1].type = type; (*vectors)[nb_vectors - 1].value = value; /* Restore the sentinel. */ (*vectors)[nb_vectors].type = AT_NULL; (*vectors)[nb_vectors].value = 0; return 0; } /** * Find in @vectors the first occurrence of the vector @type. This * function returns the found vector or NULL. */ ElfAuxVector *find_elf_aux_vector(ElfAuxVector *vectors, word_t type) { int i; for (i = 0; vectors[i].type != AT_NULL; i++) { if (vectors[i].type == type) return &vectors[i]; } return NULL; } /** * Get the address of the the ELF auxiliary vectors table for the * given @tracee. This function returns 0 if an error occurred. */ word_t get_elf_aux_vectors_address(const Tracee *tracee) { word_t address; word_t data; /* Sanity check: this works only in execve sysexit. */ assert(IS_IN_SYSEXIT2(tracee, PR_execve)); /* Right after execve, the stack layout is: * * argc, argv[0], ..., 0, envp[0], ..., 0, auxv[0].type, auxv[0].value, ..., 0, 0 */ address = peek_reg(tracee, CURRENT, STACK_POINTER); /* Read: argc */ data = peek_word(tracee, address); if (errno != 0) return 0; /* Skip: argc, argv, 0 */ address += (1 + data + 1) * sizeof_word(tracee); /* Skip: envp, 0 */ do { data = peek_word(tracee, address); if (errno != 0) return 0; address += sizeof_word(tracee); } while (data != 0); return address; } /** * Fetch ELF auxiliary vectors stored at the given @address in * @tracee's memory. This function returns NULL if an error occurred, * otherwise it returns a pointer to the new vectors, in an ABI * independent form (the Talloc parent of this pointer is * @tracee->ctx). */ ElfAuxVector *fetch_elf_aux_vectors(const Tracee *tracee, word_t address) { ElfAuxVector *vectors = NULL; ElfAuxVector vector; int status; /* It is assumed the sentinel always exists. */ vectors = talloc_array(tracee->ctx, ElfAuxVector, 1); if (vectors == NULL) return NULL; vectors[0].type = AT_NULL; vectors[0].value = 0; while (1) { vector.type = peek_word(tracee, address); if (errno != 0) return NULL; address += sizeof_word(tracee); if (vector.type == AT_NULL) break; /* Already added. */ vector.value = peek_word(tracee, address); if (errno != 0) return NULL; address += sizeof_word(tracee); status = add_elf_aux_vector(&vectors, vector.type, vector.value); if (status < 0) return NULL; } return vectors; } /** * Push ELF auxiliary @vectors to the given @address in @tracee's * memory. This function returns -errno if an error occurred, * otherwise 0. */ int push_elf_aux_vectors(const Tracee* tracee, ElfAuxVector *vectors, word_t address) { size_t i; for (i = 0; vectors[i].type != AT_NULL; i++) { poke_word(tracee, address, vectors[i].type); if (errno != 0) return -errno; address += sizeof_word(tracee); poke_word(tracee, address, vectors[i].value); if (errno != 0) return -errno; address += sizeof_word(tracee); } return 0; } /********************************************************************** * Note: So far, the content of this file below is only required to * make GDB work correctly under PRoot. However, it deserves to be * used unconditionally in execve sysexit. **********************************************************************/ /** * Fill @path with the content of @vectors, formatted according to * @ptracee's current ABI. */ static int fill_file_with_auxv(const Tracee *ptracee, const char *path, const ElfAuxVector *vectors) { const ssize_t current_sizeof_word = sizeof_word(ptracee); ssize_t status; int fd = -1; int i; fd = open(path, O_WRONLY); if (fd < 0) return -1; i = 0; do { status = write(fd, &vectors[i].type, current_sizeof_word); if (status < current_sizeof_word) { status = -1; goto end; } status = write(fd, &vectors[i].value, current_sizeof_word); if (status < current_sizeof_word) { status = -1; goto end; } } while (vectors[i++].type != AT_NULL); status = 0; end: if (fd >= 0) (void) close(fd); return status; } /** * Fix @tracee's ELF auxiliary vectors in place, ie. in its memory. * This function returns NULL if an error occurred, otherwise it * returns a pointer to the new vectors, in an ABI independent form * (the Talloc parent of this pointer is @tracee->ctx). */ static ElfAuxVector *fix_elf_aux_vectors_in_mem(const Tracee *tracee) { ElfAuxVector *vector_phdr; ElfAuxVector *vector_base; ElfAuxVector *vectors; word_t address; int status; address = get_elf_aux_vectors_address(tracee); if (address == 0) return NULL; vectors = fetch_elf_aux_vectors(tracee, address); if (vectors == NULL) return NULL; vector_phdr = find_elf_aux_vector(vectors, AT_PHDR); if (vector_phdr == NULL) return vectors; vector_base = find_elf_aux_vector(vectors, AT_BASE); if (vector_base == NULL) return vectors; /* Hum... This trick always works but this should be done more * "scientifically". */ vector_base->value = vector_phdr->value & ~0xFFF; /* TODO: AT_PHDR and AT_ENTRY. */ status = push_elf_aux_vectors(tracee, vectors, address); if (status < 0) return NULL; return vectors; } /** * Fix ELF auxiliary vectors for the given @ptracee. For information, * ELF auxiliary vectors have to be fixed because some of them are set * to unexpected values when the ELF interpreter is used as a loader * (AT_BASE for instance). This function returns -1 if an error * occurred, otherwise 0. */ int fix_elf_aux_vectors(const Tracee *ptracee) { const ElfAuxVector *vectors; const char *guest_path; const char *host_path; Binding *binding; int status; vectors = fix_elf_aux_vectors_in_mem(ptracee); if (vectors == NULL) return -1; /* Path to these ELF auxiliary vectors. */ guest_path = talloc_asprintf(ptracee->ctx, "/proc/%d/auxv", ptracee->pid); if (guest_path == NULL) return -1; /* Remove binding to this path, if any. It contains ELF * auxiliary vectors of the previous execve(2). */ binding = get_binding(ptracee, GUEST, guest_path); if (binding != NULL && compare_paths(binding->guest.path, guest_path) == PATHS_ARE_EQUAL) { remove_binding_from_all_lists(ptracee, binding); TALLOC_FREE(binding); } host_path = create_temp_file(ptracee, "auxv"); if (host_path == NULL) return -1; status = fill_file_with_auxv(ptracee, host_path, vectors); if (status < 0) return -1; /* Note: this binding will be removed once ptracee gets freed. */ binding = insort_binding3(ptracee, ptracee->life_context, host_path, guest_path); if (binding == NULL) return -1; /* This temporary file (host_path) will be removed once the * binding is freed. */ talloc_reparent(ptracee->ctx, binding, host_path); return 0; } care-2.2.1/src/execve/elf.h0000644000175000017500000001017012430615044014765 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef ELF_H #define ELF_H #define EI_NIDENT 16 #include #include typedef struct { unsigned char e_ident[EI_NIDENT]; uint16_t e_type; uint16_t e_machine; uint32_t e_version; uint32_t e_entry; uint32_t e_phoff; uint32_t e_shoff; uint32_t e_flags; uint16_t e_ehsize; uint16_t e_phentsize; uint16_t e_phnum; uint16_t e_shentsize; uint16_t e_shnum; uint16_t e_shstrndx; } ElfHeader32; typedef struct { unsigned char e_ident[EI_NIDENT]; uint16_t e_type; uint16_t e_machine; uint32_t e_version; uint64_t e_entry; uint64_t e_phoff; uint64_t e_shoff; uint32_t e_flags; uint16_t e_ehsize; uint16_t e_phentsize; uint16_t e_phnum; uint16_t e_shentsize; uint16_t e_shnum; uint16_t e_shstrndx; } ElfHeader64; typedef union { ElfHeader32 class32; ElfHeader64 class64; } ElfHeader; typedef struct { uint32_t p_type; uint32_t p_offset; uint32_t p_vaddr; uint32_t p_paddr; uint32_t p_filesz; uint32_t p_memsz; uint32_t p_flags; uint32_t p_align; } ProgramHeader32; typedef struct { uint32_t p_type; uint32_t p_flags; uint64_t p_offset; uint64_t p_vaddr; uint64_t p_paddr; uint64_t p_filesz; uint64_t p_memsz; uint64_t p_align; } ProgramHeader64; typedef union { ProgramHeader32 class32; ProgramHeader64 class64; } ProgramHeader; typedef enum { PT_LOAD = 1, PT_DYNAMIC = 2, PT_INTERP = 3 } SegmentType; typedef struct { int32_t d_tag; uint32_t d_val; } DynamicEntry32; typedef struct { int64_t d_tag; uint64_t d_val; } DynamicEntry64; typedef union { DynamicEntry32 class32; DynamicEntry64 class64; } DynamicEntry; typedef enum { DT_STRTAB = 5, DT_RPATH = 15, DT_RUNPATH = 29 } DynamicType; /* The following macros are also compatible with ELF 64-bit. */ #define ELF_IDENT(header, index) (header).class32.e_ident[(index)] #define ELF_CLASS(header) ELF_IDENT(header, 4) #define IS_CLASS32(header) (ELF_CLASS(header) == 1) #define IS_CLASS64(header) (ELF_CLASS(header) == 2) /* Helper to access a @field of the structure ElfHeaderXX. */ #define ELF_FIELD(header, field) \ (IS_CLASS64(header) \ ? (header).class64. e_ ## field \ : (header).class32. e_ ## field) /* Helper to access a @field of the structure ProgramHeaderXX */ #define PROGRAM_FIELD(ehdr, phdr, field) \ (IS_CLASS64(ehdr) \ ? (phdr).class64. p_ ## field \ : (phdr).class32. p_ ## field) /* Helper to access a @field of the structure DynamicEntryXX */ #define DYNAMIC_FIELD(ehdr, dynent, field) \ (IS_CLASS64(ehdr) \ ? (dynent).class64. d_ ## field \ : (dynent).class32. d_ ## field) #define KNOWN_PHENTSIZE(header, size) \ ( (IS_CLASS32(header) && (size) == sizeof(ProgramHeader32)) \ || (IS_CLASS64(header) && (size) == sizeof(ProgramHeader64))) #include "tracee/tracee.h" extern int open_elf(const char *t_path, ElfHeader *elf_header); extern bool is_host_elf(const Tracee *tracee, const char *t_path); extern int find_program_header(const Tracee *tracee, int fd, const ElfHeader *elf_header, ProgramHeader *program_header, SegmentType type, uint64_t address); extern int read_ldso_rpaths(const Tracee *tracee, int fd, const ElfHeader *elf_header, char **rpath, char **runpath); #endif /* ELF_H */ care-2.2.1/src/execve/auxv.h0000644000175000017500000000267212430615044015212 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2013 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef AUXV #define AUXV #include "tracee/tracee.h" #include "arch.h" typedef struct { word_t type; word_t value; } ElfAuxVector; extern word_t get_elf_aux_vectors_address(const Tracee *tracee); extern ElfAuxVector *fetch_elf_aux_vectors(const Tracee *tracee, word_t address); extern ElfAuxVector *find_elf_aux_vector(ElfAuxVector *vectors, word_t type); extern int add_elf_aux_vector(ElfAuxVector **vectors, word_t type, word_t value); extern int push_elf_aux_vectors(const Tracee* tracee, ElfAuxVector *vectors, word_t address); extern int fix_elf_aux_vectors(const Tracee *tracee); #endif /* AUXV */ care-2.2.1/src/attribute.h0000644000175000017500000000221012430615044014737 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2014 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef ATTRIBUTE_H #define ATTRIBUTE_H #define UNUSED __attribute__((unused)) #define FORMAT(a, b, c) __attribute__ ((format (a, b, c))) #define DONT_INSTRUMENT __attribute__((no_instrument_function)) #define PACKED __attribute__((packed)) #define WEAK __attribute__((weak)) #endif /* ATTRIBUTE_H */ care-2.2.1/src/ptrace/0000755000175000017500000000000012430615044014046 5ustar ivoireivoirecare-2.2.1/src/ptrace/ptrace.h0000644000175000017500000000213712430615044015500 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2013 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef PTRACE_H #define PTRACE_H #include "tracee/tracee.h" extern int translate_ptrace_enter(Tracee *tracee); extern int translate_ptrace_exit(Tracee *tracee); #define PTRACEE (ptracee->as_ptracee) #define PTRACER (ptracer->as_ptracer) #endif /* PTRACE_H */ care-2.2.1/src/ptrace/user.c0000644000175000017500000001411212430615044015167 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2013 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include #include #include #include #include #include #include "ptrace/user.h" #include "cli/notice.h" #if defined(ARCH_X86_64) /** * Return the index in the "regs" field of a 64-bit "user" area that * corresponds to the specified @index in the "regs" field of a 32-bit * "user" area. */ static inline size_t convert_user_regs_index(size_t index) { static size_t mapping[USER32_NB_REGS] = { 05, /* ?bx */ 11, /* ?cx */ 12, /* ?dx */ 13, /* ?si */ 14, /* ?di */ 04, /* ?bp */ 10, /* ?ax */ 23, /* ds */ 24, /* es */ 25, /* fs */ 26, /* gs */ 15, /* orig_?ax */ 16, /* ?ip */ 17, /* cs */ 18, /* eflags */ 19, /* ?sp */ 20, /* ss */ }; /* Sanity check. */ assert(index < USER32_NB_REGS); return mapping[index]; } /* Layout of a 32-bit "user" area. */ #define USER32_REGS_OFFSET 0 #define USER32_REGS_SIZE (USER32_NB_REGS * sizeof(uint32_t)) #define USER32_FPVALID_OFFSET (USER32_REGS_OFFSET + USER32_REGS_SIZE) #define USER32_I387_OFFSET (USER32_FPVALID_OFFSET + sizeof(uint32_t)) #define USER32_I387_SIZE (USER32_NB_FPREGS * sizeof(uint32_t)) #define USER32_TSIZE_OFFSET (USER32_I387_OFFSET + USER32_I387_SIZE) #define USER32_DSIZE_OFFSET (USER32_TSIZE_OFFSET + sizeof(uint32_t)) #define USER32_SSIZE_OFFSET (USER32_DSIZE_OFFSET + sizeof(uint32_t)) #define USER32_START_CODE_OFFSET (USER32_SSIZE_OFFSET + sizeof(uint32_t)) #define USER32_START_STACK_OFFSET (USER32_START_CODE_OFFSET + sizeof(uint32_t)) #define USER32_SIGNAL_OFFSET (USER32_START_STACK_OFFSET + sizeof(uint32_t)) #define USER32_RESERVED_OFFSET (USER32_SIGNAL_OFFSET + sizeof(uint32_t)) #define USER32_AR0_OFFSET (USER32_RESERVED_OFFSET + sizeof(uint32_t)) #define USER32_FPSTATE_OFFSET (USER32_AR0_OFFSET + sizeof(uint32_t)) #define USER32_MAGIC_OFFSET (USER32_FPSTATE_OFFSET + sizeof(uint32_t)) #define USER32_COMM_OFFSET (USER32_MAGIC_OFFSET + sizeof(uint32_t)) #define USER32_COMM_SIZE (32 * sizeof(uint8_t)) #define USER32_DEBUGREG_OFFSET (USER32_COMM_OFFSET + USER32_COMM_SIZE) #define USER32_DEBUGREG_SIZE (8 * sizeof(uint32_t)) /** * Return the offset in the "debugreg" field of a 64-bit "user" area * that corresponds to the specified @offset in the "debugreg" field * of a 32-bit "user" area. */ static inline size_t convert_user_debugreg_offset(size_t offset) { size_t index; /* Sanity check. */ assert(offset >= USER32_DEBUGREG_OFFSET && offset < USER32_DEBUGREG_OFFSET + USER32_DEBUGREG_SIZE); index = (offset - USER32_DEBUGREG_OFFSET) / sizeof(uint32_t); return offsetof(struct user, u_debugreg) + index * sizeof(uint64_t); } /** * Return the offset in a 64-bit "user" area that corresponds to the * specified @offset in a 32-bit "user" area. This function returns * "(word_t) -1" if the specified @offset is invalid. */ word_t convert_user_offset(word_t offset) { const char *area_name = NULL; if (/* offset >= 0 && */ offset < USER32_REGS_OFFSET + USER32_REGS_SIZE) { /* Sanity checks. */ if ((offset % sizeof(uint32_t)) != 0) return (word_t) -1; return convert_user_regs_index(offset / sizeof(uint32_t)) * sizeof(uint64_t); } else if (offset == USER32_FPVALID_OFFSET) area_name = "fpvalid"; /* Not yet supported. */ else if (offset >= USER32_I387_OFFSET && offset < USER32_I387_OFFSET + USER32_I387_SIZE) area_name = "i387"; /* Not yet supported. */ else if (offset == USER32_TSIZE_OFFSET) area_name = "tsize"; /* Not yet supported. */ else if (offset == USER32_DSIZE_OFFSET) area_name = "dsize"; /* Not yet supported. */ else if (offset == USER32_SSIZE_OFFSET) area_name = "ssize"; /* Not yet supported. */ else if (offset == USER32_START_CODE_OFFSET) area_name = "start_code"; /* Not yet supported. */ else if (offset == USER32_START_STACK_OFFSET) area_name = "start_stack"; /* Not yet supported. */ else if (offset == USER32_SIGNAL_OFFSET) area_name = "signal"; /* Not yet supported. */ else if (offset == USER32_RESERVED_OFFSET) area_name = "reserved"; /* Not yet supported. */ else if (offset == USER32_AR0_OFFSET) area_name = "ar0"; /* Not yet supported. */ else if (offset == USER32_FPSTATE_OFFSET) area_name = "fpstate"; /* Not yet supported. */ else if (offset == USER32_MAGIC_OFFSET) area_name = "magic"; /* Not yet supported. */ else if (offset >= USER32_COMM_OFFSET && offset < USER32_COMM_OFFSET + USER32_COMM_SIZE) area_name = "comm"; /* Not yet supported. */ else if (offset >= USER32_DEBUGREG_OFFSET && offset < USER32_DEBUGREG_OFFSET + USER32_DEBUGREG_SIZE) return convert_user_debugreg_offset(offset); else area_name = ""; notice(NULL, WARNING, INTERNAL, "ptrace user area '%s' not supported yet", area_name); return (word_t) -1; /* Unknown offset. */ } /** * Convert the "regs" field from a 64-bit "user" area into a "regs" * field from a 32-bit "user" area, or vice versa according to * @reverse. */ void convert_user_regs_struct(bool reverse, uint64_t *user_regs64, uint32_t user_regs32[USER32_NB_REGS]) { size_t index32; for (index32 = 0; index32 < USER32_NB_REGS; index32++) { size_t index64 = convert_user_regs_index(index32); assert(index64 != (size_t) -1); if (reverse) user_regs64[index64] = (uint64_t) user_regs32[index32]; else user_regs32[index32] = (uint32_t) user_regs64[index64]; } } #endif /* ARCH_X86_64 */ care-2.2.1/src/ptrace/user.h0000644000175000017500000000276612430615044015210 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2013 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include #include #include #include "arch.h" #include "attribute.h" #if defined(ARCH_X86_64) #define USER32_NB_REGS 17 #define USER32_NB_FPREGS 27 extern word_t convert_user_offset(word_t offset); extern void convert_user_regs_struct(bool reverse, uint64_t *user_regs64, uint32_t user_regs32[USER32_NB_REGS]); #else #define USER32_NB_REGS 0 #define USER32_NB_FPREGS 0 static inline word_t convert_user_offset(word_t offset UNUSED) { assert(0); } static inline void convert_user_regs_struct(bool reverse UNUSED, uint64_t *user_regs64 UNUSED, uint32_t user_regs32[USER32_NB_REGS] UNUSED) { assert(0); } #endif care-2.2.1/src/ptrace/wait.h0000644000175000017500000000406612430615044015171 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2013 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef PTRACE_WAIT_H #define PTRACE_WAIT_H #include "tracee/tracee.h" extern int translate_wait_enter(Tracee *ptracer); extern int translate_wait_exit(Tracee *ptracer); extern bool handle_ptracee_event(Tracee *ptracee, int wait_status); extern int add_direct_ptracee(Tracee *ptracer, pid_t ptracee_pid); extern bool is_direct_ptracee(Tracee *ptracer, pid_t ptracee_pid); extern void set_exited_direct_ptracee(Tracee *ptracer, pid_t ptracee_pid); extern bool is_exited_direct_ptracee(Tracee *ptracer, pid_t ptracee_pid); extern void remove_exited_direct_ptracee(Tracee *ptracer, pid_t ptracee_pid); /* __WCLONE: Wait for "clone" children only. If omitted then wait for * "non-clone" children only. (A "clone" child is one which delivers * no signal, or a signal other than SIGCHLD to its parent upon * termination.) This option is ignored if __WALL is also specified. * * __WALL: Wait for all children, regardless of type ("clone" or * "non-clone"). * * -- wait(2) man-page */ #define EXPECTED_WAIT_CLONE(wait_options, tracee) \ ((((wait_options) & __WALL) != 0) \ || ((((wait_options) & __WCLONE) != 0) && (tracee)->clone) \ || ((((wait_options) & __WCLONE) == 0) && !(tracee)->clone)) #endif /* PTRACE_WAIT_H */ care-2.2.1/src/ptrace/ptrace.c0000644000175000017500000003661112430615044015477 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2013 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* PTRACE_*, */ #include /* E*, */ #include /* assert(3), */ #include /* bool, true, false, */ #include /* siginfo_t, */ #include /* struct iovec, */ #include /* MIN(), MAX(), */ #include /* memcpy(3), */ #include "ptrace/ptrace.h" #include "ptrace/user.h" #include "ptrace/wait.h" #include "tracee/tracee.h" #include "syscall/sysnum.h" #include "tracee/reg.h" #include "tracee/mem.h" #include "tracee/abi.h" #include "tracee/event.h" #include "cli/notice.h" #include "arch.h" #include "compat.h" #if defined(ARCH_X86_64) || defined(ARCH_X86) #include /* struct user_desc, */ #endif #if defined(ARCH_ARM_EABI) #define user_fpregs_struct user_fpregs #endif #if defined(ARCH_ARM64) #define user_fpregs_struct user_fpsimd_struct #endif static const char *stringify_ptrace(enum __ptrace_request request) { #define CASE_STR(a) case a: return #a; break; switch ((int) request) { CASE_STR(PTRACE_TRACEME) CASE_STR(PTRACE_PEEKTEXT) CASE_STR(PTRACE_PEEKDATA) CASE_STR(PTRACE_PEEKUSER) CASE_STR(PTRACE_POKETEXT) CASE_STR(PTRACE_POKEDATA) CASE_STR(PTRACE_POKEUSER) CASE_STR(PTRACE_CONT) CASE_STR(PTRACE_KILL) CASE_STR(PTRACE_SINGLESTEP) CASE_STR(PTRACE_GETREGS) CASE_STR(PTRACE_SETREGS) CASE_STR(PTRACE_GETFPREGS) CASE_STR(PTRACE_SETFPREGS) CASE_STR(PTRACE_ATTACH) CASE_STR(PTRACE_DETACH) CASE_STR(PTRACE_GETFPXREGS) CASE_STR(PTRACE_SETFPXREGS) CASE_STR(PTRACE_SYSCALL) CASE_STR(PTRACE_SETOPTIONS) CASE_STR(PTRACE_GETEVENTMSG) CASE_STR(PTRACE_GETSIGINFO) CASE_STR(PTRACE_SETSIGINFO) CASE_STR(PTRACE_GETREGSET) CASE_STR(PTRACE_SETREGSET) CASE_STR(PTRACE_SEIZE) CASE_STR(PTRACE_INTERRUPT) CASE_STR(PTRACE_LISTEN) CASE_STR(PTRACE_SET_SYSCALL) CASE_STR(PTRACE_GET_THREAD_AREA) CASE_STR(PTRACE_SET_THREAD_AREA) CASE_STR(PTRACE_GETVFPREGS) CASE_STR(PTRACE_SINGLEBLOCK) default: return "PTRACE_???"; } } /** * Translate the ptrace syscall made by @tracee into a "void" syscall * in order to emulate the ptrace mechanism within PRoot. This * function returns -errno if an error occured (unsupported request), * otherwise 0. */ int translate_ptrace_enter(Tracee *tracee) { /* The ptrace syscall have to be emulated since it can't be nested. */ set_sysnum(tracee, PR_void); return 0; } /** * Emulate the ptrace syscall made by @tracee. This function returns * -errno if an error occured (unsupported request), otherwise 0. */ int translate_ptrace_exit(Tracee *tracee) { word_t request, pid, address, data, result; Tracee *ptracee, *ptracer; int forced_signal = -1; int signal; int status; /* Read ptrace parameters. */ request = peek_reg(tracee, ORIGINAL, SYSARG_1); pid = peek_reg(tracee, ORIGINAL, SYSARG_2); address = peek_reg(tracee, ORIGINAL, SYSARG_3); data = peek_reg(tracee, ORIGINAL, SYSARG_4); /* The TRACEME request is the only one used by a tracee. */ if (request == PTRACE_TRACEME) { ptracer = tracee->parent; ptracee = tracee; /* The emulated ptrace in PRoot has the same * limitation as the real ptrace in the Linux kernel: * only one tracer per process. */ if (PTRACEE.ptracer != NULL || ptracee == ptracer) return -EPERM; PTRACEE.ptracer = ptracer; PTRACER.nb_ptracees++; add_direct_ptracee(ptracer, ptracee->pid); /* Detect when the ptracer has gone to wait before the * ptracee has did the ptrace(ATTACHME) request. */ if (PTRACER.waits_in == WAITS_IN_KERNEL) { status = kill(ptracer->pid, SIGSTOP); if (status < 0) notice(tracee, WARNING, INTERNAL, "can't wake ptracer %d", ptracer->pid); else { ptracer->sigstop = SIGSTOP_IGNORED; PTRACER.waits_in = WAITS_IN_PROOT; } } /* Disable seccomp acceleration for this tracee and * all its children since we can't assume what are the * syscalls its tracer is interested with. */ if (tracee->seccomp == ENABLED) tracee->seccomp = DISABLING; return 0; } /* The ATTACH, SEIZE, and INTERRUPT requests are the only ones * where the ptracee is in an unknown state. */ if (request == PTRACE_ATTACH) { ptracer = tracee; ptracee = get_tracee(ptracer, pid, false); if (ptracee == NULL) return -ESRCH; /* The emulated ptrace in PRoot has the same * limitation as the real ptrace in the Linux kernel: * only one tracer per process. */ if (PTRACEE.ptracer != NULL || ptracee == ptracer) return -EPERM; PTRACEE.ptracer = ptracer; PTRACER.nb_ptracees++; /* The tracee is sent a SIGSTOP, but will not * necessarily have stopped by the completion of this * call. * * -- man 2 ptrace. */ kill(pid, SIGSTOP); return 0; } /* Here, the tracee is a ptracer. Also, the requested ptracee * has to be in the "stopped for ptracer" state. */ ptracer = tracee; ptracee = get_stopped_ptracee(ptracer, pid, false, __WALL); if (ptracee == NULL) { static bool warned = false; /* Ensure we didn't get there only because inheritance * mechanism has missed this one. */ ptracee = get_tracee(tracee, pid, false); if (ptracee != NULL && ptracee->exe == NULL && !warned) { warned = true; notice(ptracer, WARNING, INTERNAL, "ptrace request to an unexpected ptracee"); } return -ESRCH; } /* Sanity checks. */ if ( PTRACEE.is_zombie || PTRACEE.ptracer != ptracer || pid == (word_t) -1) return -ESRCH; switch (request) { case PTRACE_SYSCALL: PTRACEE.ignore_syscall = false; forced_signal = (int) data; status = 0; break; /* Restart the ptracee. */ case PTRACE_CONT: PTRACEE.ignore_syscall = true; forced_signal = (int) data; status = 0; break; /* Restart the ptracee. */ case PTRACE_SINGLESTEP: ptracee->restart_how = PTRACE_SINGLESTEP; forced_signal = (int) data; status = 0; break; /* Restart the ptracee. */ case PTRACE_SINGLEBLOCK: ptracee->restart_how = PTRACE_SINGLEBLOCK; forced_signal = (int) data; status = 0; break; /* Restart the ptracee. */ case PTRACE_DETACH: assert(PTRACER.nb_ptracees > 0); PTRACER.nb_ptracees--; PTRACEE.ptracer = NULL; status = 0; break; /* Restart the ptracee. */ case PTRACE_KILL: status = ptrace(request, pid, NULL, NULL); break; /* Restart the ptracee. */ case PTRACE_SETOPTIONS: PTRACEE.options = data; return 0; /* Don't restart the ptracee. */ case PTRACE_GETEVENTMSG: { status = ptrace(request, pid, NULL, &result); if (status < 0) return -errno; poke_word(ptracer, data, result); if (errno != 0) return -errno; return 0; /* Don't restart the ptracee. */ } case PTRACE_PEEKUSER: if (is_32on64_mode(ptracer)) { address = convert_user_offset(address); if (address == (word_t) -1) return -EIO; } /* Fall through. */ case PTRACE_PEEKTEXT: case PTRACE_PEEKDATA: errno = 0; result = (word_t) ptrace(request, pid, address, NULL); if (errno != 0) return -errno; poke_word(ptracer, data, result); if (errno != 0) return -errno; return 0; /* Don't restart the ptracee. */ case PTRACE_POKEUSER: if (is_32on64_mode(ptracer)) { address = convert_user_offset(address); if (address == (word_t) -1) return -EIO; } status = ptrace(request, pid, address, data); if (status < 0) return -errno; return 0; /* Don't restart the ptracee. */ case PTRACE_POKETEXT: case PTRACE_POKEDATA: if (is_32on64_mode(ptracer)) { word_t tmp; errno = 0; tmp = (word_t) ptrace(PTRACE_PEEKDATA, ptracee->pid, address, NULL); if (errno != 0) return -errno; data |= (tmp & 0xFFFFFFFF00000000ULL); } status = ptrace(request, pid, address, data); if (status < 0) return -errno; return 0; /* Don't restart the ptracee. */ case PTRACE_GETSIGINFO: { siginfo_t siginfo; status = ptrace(request, pid, NULL, &siginfo); if (status < 0) return -errno; status = write_data(ptracer, data, &siginfo, sizeof(siginfo)); if (status < 0) return status; return 0; /* Don't restart the ptracee. */ } case PTRACE_SETSIGINFO: { siginfo_t siginfo; status = read_data(ptracer, &siginfo, data, sizeof(siginfo)); if (status < 0) return status; status = ptrace(request, pid, NULL, &siginfo); if (status < 0) return -errno; return 0; /* Don't restart the ptracee. */ } case PTRACE_GETREGS: { size_t size; union { struct user_regs_struct regs; uint32_t regs32[USER32_NB_REGS]; } buffer; status = ptrace(request, pid, NULL, &buffer); if (status < 0) return -errno; if (is_32on64_mode(tracee)) { struct user_regs_struct regs64; memcpy(®s64, &buffer.regs, sizeof(struct user_regs_struct)); convert_user_regs_struct(false, (uint64_t *) ®s64, buffer.regs32); size = sizeof(buffer.regs32); } else size = sizeof(buffer.regs); status = write_data(ptracer, data, &buffer, size); if (status < 0) return status; return 0; /* Don't restart the ptracee. */ } case PTRACE_SETREGS: { size_t size; union { struct user_regs_struct regs; uint32_t regs32[USER32_NB_REGS]; } buffer; size = (is_32on64_mode(ptracer) ? sizeof(buffer.regs32) : sizeof(buffer.regs)); status = read_data(ptracer, &buffer, data, size); if (status < 0) return status; if (is_32on64_mode(ptracer)) { uint32_t regs32[USER32_NB_REGS]; memcpy(regs32, buffer.regs32, sizeof(regs32)); convert_user_regs_struct(true, (uint64_t *) &buffer.regs, regs32); } status = ptrace(request, pid, NULL, &buffer); if (status < 0) return -errno; return 0; /* Don't restart the ptracee. */ } case PTRACE_GETFPREGS: { size_t size; union { struct user_fpregs_struct fpregs; uint32_t fpregs32[USER32_NB_FPREGS]; } buffer; status = ptrace(request, pid, NULL, &buffer); if (status < 0) return -errno; if (is_32on64_mode(tracee)) { #if 0 /* TODO */ struct user_fpregs_struct fpregs64; memcpy(&fpregs64, &buffer.fpregs, sizeof(struct user_fpregs_struct)); convert_user_fpregs_struct(false, (uint64_t *) &fpregs64, buffer.fpregs32); #else static bool warned = false; if (!warned) notice(ptracer, WARNING, INTERNAL, "ptrace 32-bit request '%s' not supported on 64-bit yet", stringify_ptrace(request)); warned = true; bzero(&buffer, sizeof(buffer)); #endif size = sizeof(buffer.fpregs32); } else size = sizeof(buffer.fpregs); status = write_data(ptracer, data, &buffer, size); if (status < 0) return status; return 0; /* Don't restart the ptracee. */ } case PTRACE_SETFPREGS: { size_t size; union { struct user_fpregs_struct fpregs; uint32_t fpregs32[USER32_NB_FPREGS]; } buffer; size = (is_32on64_mode(ptracer) ? sizeof(buffer.fpregs32) : sizeof(buffer.fpregs)); status = read_data(ptracer, &buffer, data, size); if (status < 0) return status; if (is_32on64_mode(ptracer)) { #if 0 /* TODO */ uint32_t fpregs32[USER32_NB_FPREGS]; memcpy(fpregs32, buffer.fpregs32, sizeof(fpregs32)); convert_user_fpregs_struct(true, (uint64_t *) &buffer.fpregs, fpregs32); #else static bool warned = false; if (!warned) notice(ptracer, WARNING, INTERNAL, "ptrace 32-bit request '%s' not supported on 64-bit yet", stringify_ptrace(request)); warned = true; return -ENOTSUP; #endif } status = ptrace(request, pid, NULL, &buffer); if (status < 0) return -errno; return 0; /* Don't restart the ptracee. */ } #if defined(ARCH_X86_64) || defined(ARCH_X86) case PTRACE_GET_THREAD_AREA: { struct user_desc user_desc; status = ptrace(request, pid, address, &user_desc); if (status < 0) return -errno; status = write_data(ptracer, data, &user_desc, sizeof(user_desc)); if (status < 0) return status; return 0; /* Don't restart the ptracee. */ } case PTRACE_SET_THREAD_AREA: { struct user_desc user_desc; status = read_data(ptracer, &user_desc, data, sizeof(user_desc)); if (status < 0) return status; status = ptrace(request, pid, address, &user_desc); if (status < 0) return -errno; return 0; /* Don't restart the ptracee. */ } #endif case PTRACE_GETREGSET: { struct iovec local_iovec; word_t remote_iovec_base; word_t remote_iovec_len; remote_iovec_base = peek_word(ptracer, data); if (errno != 0) return -errno; remote_iovec_len = peek_word(ptracer, data + sizeof_word(ptracer)); if (errno != 0) return -errno; /* Sanity check. */ assert(sizeof(local_iovec.iov_len) == sizeof(word_t)); local_iovec.iov_len = remote_iovec_len; local_iovec.iov_base = talloc_zero_size(ptracer->ctx, remote_iovec_len); if (local_iovec.iov_base == NULL) return -ENOMEM; status = ptrace(PTRACE_GETREGSET, pid, address, &local_iovec); if (status < 0) return status; remote_iovec_len = local_iovec.iov_len = MIN(remote_iovec_len, local_iovec.iov_len); /* Update remote vector content. */ status = writev_data(ptracer, remote_iovec_base, &local_iovec, 1); if (status < 0) return status; /* Update remote vector length. */ poke_word(ptracer, data + sizeof_word(ptracer), remote_iovec_len); if (errno != 0) return -errno; return 0; /* Don't restart the ptracee. */ } case PTRACE_SETREGSET: { struct iovec local_iovec; word_t remote_iovec_base; word_t remote_iovec_len; remote_iovec_base = peek_word(ptracer, data); if (errno != 0) return -errno; remote_iovec_len = peek_word(ptracer, data + sizeof_word(ptracer)); if (errno != 0) return -errno; /* Sanity check. */ assert(sizeof(local_iovec.iov_len) == sizeof(word_t)); local_iovec.iov_len = remote_iovec_len; local_iovec.iov_base = talloc_zero_size(ptracer->ctx, remote_iovec_len); if (local_iovec.iov_base == NULL) return -ENOMEM; /* Copy remote content into the local vector. */ status = read_data(ptracer, local_iovec.iov_base, remote_iovec_base, local_iovec.iov_len); if (status < 0) return status; status = ptrace(PTRACE_SETREGSET, pid, address, &local_iovec); if (status < 0) return status; return 0; /* Don't restart the ptracee. */ } case PTRACE_GETVFPREGS: case PTRACE_GETFPXREGS: { static bool warned = false; if (!warned) notice(ptracer, WARNING, INTERNAL, "ptrace request '%s' not supported yet", stringify_ptrace(request)); warned = true; return -ENOTSUP; } default: notice(ptracer, WARNING, INTERNAL, "ptrace request '%s' not supported yet", stringify_ptrace(request)); return -ENOTSUP; } /* Now, the initial tracee's event can be handled. */ signal = PTRACEE.event4.proot.pending ? handle_tracee_event(ptracee, PTRACEE.event4.proot.value) : PTRACEE.event4.proot.value; /* The restarting signal from the ptracer overrides the * restarting signal from PRoot. */ if (forced_signal != -1) signal = forced_signal; (void) restart_tracee(ptracee, signal); return status; } care-2.2.1/src/ptrace/wait.c0000644000175000017500000003561212430615044015165 0ustar ivoireivoire/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2013 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* PTRACE_*, */ #include /* __WALL, */ #include /* LIST_*, */ #include /* E*, */ #include /* assert(3), */ #include /* bool, true, false, */ #include /* siginfo_t, TRAP_*, */ #include /* getpid(2), */ #include /* talloc*, */ #include "ptrace/wait.h" #include "ptrace/ptrace.h" #include "execve/auxv.h" #include "syscall/sysnum.h" #include "tracee/tracee.h" #include "tracee/event.h" #include "tracee/reg.h" #include "tracee/mem.h" #include "cli/notice.h" #include "attribute.h" static const char *stringify_event(int event) UNUSED; static const char *stringify_event(int event) { if (WIFEXITED(event)) return "exited"; else if (WIFSIGNALED(event)) return "signaled"; else if (WIFCONTINUED(event)) return "continued"; else if (WIFSTOPPED(event)) { switch ((event & 0xfff00) >> 8) { case SIGTRAP: return "stopped: SIGTRAP"; case SIGTRAP | 0x80: return "stopped: SIGTRAP: 0x80"; case SIGTRAP | PTRACE_EVENT_VFORK << 8: return "stopped: SIGTRAP: PTRACE_EVENT_VFORK"; case SIGTRAP | PTRACE_EVENT_FORK << 8: return "stopped: SIGTRAP: PTRACE_EVENT_FORK"; case SIGTRAP | PTRACE_EVENT_VFORK_DONE << 8: return "stopped: SIGTRAP: PTRACE_EVENT_VFORK_DONE"; case SIGTRAP | PTRACE_EVENT_CLONE << 8: return "stopped: SIGTRAP: PTRACE_EVENT_CLONE"; case SIGTRAP | PTRACE_EVENT_EXEC << 8: return "stopped: SIGTRAP: PTRACE_EVENT_EXEC"; case SIGTRAP | PTRACE_EVENT_EXIT << 8: return "stopped: SIGTRAP: PTRACE_EVENT_EXIT"; case SIGTRAP | PTRACE_EVENT_SECCOMP2 << 8: return "stopped: SIGTRAP: PTRACE_EVENT_SECCOMP2"; case SIGTRAP | PTRACE_EVENT_SECCOMP << 8: return "stopped: SIGTRAP: PTRACE_EVENT_SECCOMP"; case SIGSTOP: return "stopped: SIGSTOP"; default: return "stopped: unknown"; } } return "unknown"; } /** * Translate the wait syscall made by @ptracer into a "void" syscall * if the expected pid is one of its ptracees, in order to emulate the * ptrace mechanism within PRoot. This function returns -errno if an * error occured (unsupported request), otherwise 0. */ int translate_wait_enter(Tracee *ptracer) { Tracee *ptracee; pid_t pid; PTRACER.waits_in = WAITS_IN_KERNEL; /* Don't emulate the ptrace mechanism if it's not a ptracer. */ if (PTRACER.nb_ptracees == 0) return 0; /* Don't emulate the ptrace mechanism if the requested pid is * not a ptracee. */ pid = (pid_t) peek_reg(ptracer, ORIGINAL, SYSARG_1); if (pid != -1) { ptracee = get_tracee(ptracer, pid, false); if (ptracee == NULL || PTRACEE.ptracer != ptracer) return 0; } /* This syscall is canceled at the enter stage in order to be * handled at the exit stage. */ set_sysnum(ptracer, PR_void); PTRACER.waits_in = WAITS_IN_PROOT; return 0; } /** * Update pid & wait status of @ptracer's wait(2) for the given * @ptracee. */ static int update_wait_status(Tracee *ptracer, Tracee *ptracee) { const int event = PTRACEE.event4.ptracer.value; word_t address; address = peek_reg(ptracer, ORIGINAL, SYSARG_2); if (address != 0) { poke_int32(ptracer, address, event); if (errno != 0) return -errno; } PTRACEE.event4.ptracer.pending = false; /* Under PRoot, the kernel will report its termination once * again to its parent since "ptracer != parent" from kernel's * point-of-view. PRoot has to mask this second notification * not to make the parent/ptracer confused. */ if ( (WIFEXITED(event) || WIFSIGNALED(event)) && is_direct_ptracee(ptracer, ptracee->pid)) { set_exited_direct_ptracee(ptracer, ptracee->pid); } return ptracee->pid; } /** * Emulate the wait* syscall made by @ptracer if it was in the context * of the ptrace mechanism. This function returns -errno if an error * occured, otherwise the pid of the expected tracee. */ int translate_wait_exit(Tracee *ptracer) { Tracee *ptracee; word_t options; int status; pid_t pid; assert(PTRACER.waits_in == WAITS_IN_PROOT); PTRACER.waits_in = DOESNT_WAIT; pid = (pid_t) peek_reg(ptracer, ORIGINAL, SYSARG_1); options = peek_reg(ptracer, ORIGINAL, SYSARG_3); /* Is there such a stopped ptracee with an event not yet * passed to its ptracer? */ ptracee = get_stopped_ptracee(ptracer, pid, true, options); if (ptracee == NULL) { /* Is there still living ptracees? */ if (PTRACER.nb_ptracees == 0) return -ECHILD; /* Non blocking wait(2) ? */ if ((options & WNOHANG) != 0) { /* if WNOHANG was specified and one or more * child(ren) specified by pid exist, but have * not yet changed state, then 0 is returned. * On error, -1 is returned. * * -- man 2 waitpid */ return (has_ptracees(ptracer, pid, options) ? 0 : -ECHILD); } /* Otherwise put this ptracer in the "waiting for * ptracee" state, it will be woken up in * handle_ptracee_event() later. */ PTRACER.wait_pid = pid; PTRACER.wait_options = options; return 0; } status = update_wait_status(ptracer, ptracee); if (status < 0) return status; pid = ptracee->pid; /* Zombies can rest in peace once the ptracer is notified. */ if (PTRACEE.is_zombie) TALLOC_FREE(ptracee); return pid; } /** * For the given @ptracee, pass its current @event to its ptracer if * this latter is waiting for it, otherwise put the @ptracee in the * "waiting for ptracer" state. This function returns whether * @ptracee shall be kept in the stop state or not. */ bool handle_ptracee_event(Tracee *ptracee, int event) { bool handled_by_proot_first = false; Tracee *ptracer = PTRACEE.ptracer; bool keep_stopped; assert(ptracer != NULL); /* Remember what the event initially was, this will be * required by PRoot to handle this event later. */ PTRACEE.event4.proot.value = event; PTRACEE.event4.proot.pending = true; /* By default, this ptracee should be kept stopped until its * ptracer restarts it. */ keep_stopped = true; /* Not all events are expected for this ptracee. */ if (WIFSTOPPED(event)) { switch ((event & 0xfff00) >> 8) { case SIGTRAP | 0x80: /* Fix ELF auxiliary vectors. So far, this is * only required to make GDB work correctly * under PRoot. */ if (IS_IN_SYSEXIT2(ptracee, PR_execve) && fetch_regs(ptracee) >= 0 && (int) peek_reg(ptracee, CURRENT, SYSARG_RESULT) >= 0) { fix_elf_aux_vectors(ptracee); } /* If the PTRACE_O_TRACEEXEC option is *not* * in effect for the execing tracee, the * kernel delivers an extra SIGTRAP to the * tracee after execve(2) *returns*. This is * an ordinary signal (similar to one which * can be generated by "kill -TRAP"), not a * special kind of ptrace-stop. Employing * PTRACE_GETSIGINFO for this signal returns * si_code set to 0 (SI_USER). This signal * may be blocked by signal mask, and thus may * be delivered (much) later. * * -- man 2 ptrace * * Note about the is_loader/PR_close trick: it * is required to make GDB usable under PRoot. * GDB assumes the program is loaded in memory * once execve(2) has completed, however this * is not the case under PRoot since it * replaces the executed programs with a * loader (the ELF interpreter for now). As a * consequence, only the loader is in memory * and GDB will fail to set breakpoints on the * program. A workaround is to delay the * execve(2) notification until the ELF * interpreter has loaded the program in * memory, that is, until the first PR_close * has completed. */ if (IS_IN_SYSEXIT2(ptracee, (PTRACEE.is_load_pending ? PR_close : PR_execve)) && (PTRACEE.options & PTRACE_O_TRACEEXEC) == 0 && fetch_regs(ptracee) >= 0 && (int) peek_reg(ptracee, CURRENT, SYSARG_RESULT) >= 0) { kill(ptracee->pid, SIGTRAP); PTRACEE.is_load_pending = false; } if (PTRACEE.ignore_syscall) return false; if ((PTRACEE.options & PTRACE_O_TRACESYSGOOD) == 0) event &= ~(0x80 << 8); handled_by_proot_first = IS_IN_SYSEXIT(ptracee); break; #define PTRACE_EVENT_VFORKDONE PTRACE_EVENT_VFORK_DONE #define CASE_FILTER_EVENT(name) \ case SIGTRAP | PTRACE_EVENT_ ##name << 8: \ if ((PTRACEE.options & PTRACE_O_TRACE ##name) == 0) \ return false; \ PTRACEE.tracing_started = true; \ handled_by_proot_first = true; \ break; CASE_FILTER_EVENT(FORK); CASE_FILTER_EVENT(VFORK); CASE_FILTER_EVENT(VFORKDONE); CASE_FILTER_EVENT(CLONE); CASE_FILTER_EVENT(EXIT); CASE_FILTER_EVENT(EXEC); /* Never reached. */ assert(0); case SIGTRAP | PTRACE_EVENT_SECCOMP2 << 8: case SIGTRAP | PTRACE_EVENT_SECCOMP << 8: /* These events are not supported [yet?] under * ptrace emulation. */ return false; default: PTRACEE.tracing_started = true; break; } } /* In these cases, the ptracee isn't really alive anymore. To * ensure it will not be in limbo, PRoot restarts it whether * its ptracer is waiting for it or not. */ else if (WIFEXITED(event) || WIFSIGNALED(event)) { PTRACEE.tracing_started = true; keep_stopped = false; } /* A process is not traced right from the TRACEME request; it * is traced from the first received signal, whether it was * raised by the process itself (implicitly or explicitly), or * it was induced by a PTRACE_EVENT_*. */ if (!PTRACEE.tracing_started) return false; /* Under some circumstances, the event must be handled by * PRoot first. */ if (handled_by_proot_first) { int signal; signal = handle_tracee_event(ptracee, PTRACEE.event4.proot.value); PTRACEE.event4.proot.value = signal; /* The computed signal is always 0 since we can come * in this block only on sysexit and special events * (as for now). */ assert(signal == 0); } /* Remember what the new event is, this will be required by the ptracer in translate_ptrace_exit() in order to restart this ptracee. */ PTRACEE.event4.ptracer.value = event; PTRACEE.event4.ptracer.pending = true; /* Notify asynchronously the ptracer about this event, as the * kernel does. */ kill(ptracer->pid, SIGCHLD); /* Note: wait_pid is set in translate_wait_exit() if no * ptracee event was pending when the ptracer started to * wait. */ if ( (PTRACER.wait_pid == -1 || PTRACER.wait_pid == ptracee->pid) && EXPECTED_WAIT_CLONE(PTRACER.wait_options, ptracee)) { bool restarted; int status; status = update_wait_status(ptracer, ptracee); poke_reg(ptracer, SYSARG_RESULT, (word_t) status); /* Write ptracer's register cache back. */ (void) push_regs(ptracer); /* Restart the ptracer. */ PTRACER.wait_pid = 0; restarted = restart_tracee(ptracer, 0); if (!restarted) keep_stopped = false; return keep_stopped; } return keep_stopped; } struct direct_ptracee { pid_t pid; bool has_exited; LIST_ENTRY(direct_ptracee) link; }; LIST_HEAD(direct_ptracees, direct_ptracee); /** * Add @ptracee_pid to @ptracer's list of direct ptracees. This * function returns -errno if an error occured, otherwise 0. */ int add_direct_ptracee(Tracee *ptracer, pid_t ptracee_pid) { struct direct_ptracee *direct_ptracee; if (is_direct_ptracee(ptracer, ptracee_pid)) notice(ptracer, WARNING, INTERNAL, "%s: pid %d is already declared as direct ptracee.", __FUNCTION__, ptracee_pid); if (PTRACER.direct_ptracees == NULL) { PTRACER.direct_ptracees = talloc_zero(ptracer, struct direct_ptracees); if (PTRACER.direct_ptracees == NULL) return -ENOMEM; } direct_ptracee = talloc_zero(PTRACER.direct_ptracees, struct direct_ptracee); if (direct_ptracee == NULL) return -ENOMEM; direct_ptracee->pid = ptracee_pid; LIST_INSERT_HEAD(PTRACER.direct_ptracees, direct_ptracee, link); return 0; } /** * Return the entry from @ptracer's list of direct ptracees for the * given @ptracee_pid, or NULL if it does not exist. */ static struct direct_ptracee *get_direct_ptracee(Tracee *ptracer, pid_t ptracee_pid) { struct direct_ptracee *direct_ptracee; if (PTRACER.direct_ptracees == NULL) return NULL; LIST_FOREACH(direct_ptracee, PTRACER.direct_ptracees, link) { if (direct_ptracee->pid == ptracee_pid) return direct_ptracee; } return NULL; } /** * Check whether @ptracee_pid belongs to @ptracer's list of direct * ptracees. */ bool is_direct_ptracee(Tracee *ptracer, pid_t ptracee_pid) { struct direct_ptracee *direct_ptracee; direct_ptracee = get_direct_ptracee(ptracer, ptracee_pid); if (direct_ptracee == NULL) return false; if (direct_ptracee->has_exited) notice(ptracer, WARNING, INTERNAL, "%s: direct ptracee %d is expected to not be already exited.", __FUNCTION__, ptracee_pid); return true; } /** * Declare @ptracee_pid -- from @ptracer's list of direct ptracees -- * has exited. */ void set_exited_direct_ptracee(Tracee *ptracer, pid_t ptracee_pid) { struct direct_ptracee *direct_ptracee; direct_ptracee = get_direct_ptracee(ptracer, ptracee_pid); if (direct_ptracee == NULL) { notice(ptracer, WARNING, INTERNAL, "%s: pid %d is expected to be declared as direct ptracee.", __FUNCTION__, ptracee_pid); return; } if (direct_ptracee->has_exited) notice(ptracer, WARNING, INTERNAL, "%s: direct ptracee %d is expected to not be already exited.", __FUNCTION__, ptracee_pid); direct_ptracee->has_exited = true; } /** * Check whether @ptracee_pid belongs to @ptracer's list of direct * ptracees and is declared as exited. */ bool is_exited_direct_ptracee(Tracee *ptracer, pid_t ptracee_pid) { struct direct_ptracee *direct_ptracee; direct_ptracee = get_direct_ptracee(ptracer, ptracee_pid); if (direct_ptracee == NULL) return false; if (!direct_ptracee->has_exited) return false; return true; } /** * Remove @ptracee_pid from @ptracer's list of direct ptracees. */ void remove_exited_direct_ptracee(Tracee *ptracer, pid_t ptracee_pid) { struct direct_ptracee *direct_ptracee; direct_ptracee = get_direct_ptracee(ptracer, ptracee_pid); if (direct_ptracee == NULL) { notice(ptracer, WARNING, INTERNAL, "%s: pid %d is expected to be declared as direct ptracee.", __FUNCTION__, ptracee_pid); return; } LIST_REMOVE(direct_ptracee, link); /* No more direct ptracees? */ if (LIST_EMPTY(PTRACER.direct_ptracees)) TALLOC_FREE(PTRACER.direct_ptracees); } care-2.2.1/src/.check_process_vm.c0000644000175000017500000000024312430615044016326 0ustar ivoireivoire#include #include int main(void) { return process_vm_readv(0, NULL, 0, NULL, 0, 0) + process_vm_writev(0, NULL, 0, NULL, 0, 0); }