proot-5.1.0/0000755000175000017500000000000012443566643012251 5ustar ivoireivoireproot-5.1.0/.travis.yml0000644000175000017500000000211412443566643014360 0ustar ivoireivoirelanguage: c compiler: gcc before_install: - sudo apt-get update -qq - sudo apt-get install -qq libtalloc-dev uthash-dev libarchive-dev gdb strace realpath - sudo pip install cpp-coveralls script: if [ ${COVERITY_SCAN_BRANCH} != 1 ]; then make -C src loader.exe loader-m32.exe build.h && env CFLAGS=--coverage LDFLAGS='--coverage' make -C src proot && env PATH=/bin:/usr/bin:/sbin:/usr/sbin:$PWD/src make -C tests; fi after_success: - coveralls --build-root src --exclude tests --gcov-options '\-lp' env: global: # The next declaration 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 proot-5.1.0/README.rst0000644000175000017500000000100612443566643013735 0ustar ivoireivoireManuals ======= - `PRoot `_ - `CARE `_ Build status ============ - .. image:: https://travis-ci.org/cedric-vincent/PRoot.png?branch=master :target: https://travis-ci.org/cedric-vincent/PRoot - .. image:: https://coveralls.io/repos/cedric-vincent/PRoot/badge.png?branch=master :target: https://coveralls.io/r/cedric-vincent/PRoot?branch=master - .. image:: https://scan.coverity.com/projects/602/badge.svg :target: https://scan.coverity.com/projects/602 proot-5.1.0/tests/0000755000175000017500000000000012443566643013413 5ustar ivoireivoireproot-5.1.0/tests/test-88888888.c0000644000175000017500000000245212443566643015516 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); } proot-5.1.0/tests/test-092c5e26.sh0000644000175000017500000000264012443566643016005 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); } proot-5.1.0/tests/test-8a83376a.sh0000644000175000017500000000013312443566643016005 0ustar ivoireivoireif [ ! -e /bin/true ] || [ -z `which ldd` ]; then exit 125; fi ${PROOT} ldd /bin/true proot-5.1.0/tests/test-1fedd9a3.sh0000644000175000017500000000217212443566643016226 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} proot-5.1.0/tests/test-de756935.sh0000644000175000017500000000041212443566643016014 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} proot-5.1.0/tests/test-25069c12.c0000644000175000017500000000044312443566643015530 0ustar ivoireivoire#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); } proot-5.1.0/tests/test-2401b850.sh0000644000175000017500000000533612443566643015720 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} proot-5.1.0/tests/test-b94dd86a.sh0000644000175000017500000000013612443566643016151 0ustar ivoireivoireif [ ! -x ${ROOTFS}/bin/true ]; then exit 125; fi ${PROOT} -w /bin -r ${ROOTFS} ./true proot-5.1.0/tests/test-aaaaaaaa.sh0000644000175000017500000000346612443566643016424 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} proot-5.1.0/tests/test-d2175fc3.sh0000644000175000017500000000061712443566643016066 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$' proot-5.1.0/tests/test-517e1d6a.sh0000644000175000017500000000241112443566643016057 0ustar ivoireivoireif [ ! -x ${ROOTFS}/bin/argv ] || [ -z `which mcookie` ] || [ -z `which mkdir` ] || [ -z `which chmod` ] || [ -z `which env` ] || [ -z `which rm` ] || [ -z `which grep` ] || [ -z `which env` ] || [ -z `which ln` ]; then exit 125; fi BIN_DIR=/tmp/$(mcookie) TMP=$(mcookie) TMP2=$(mcookie) mkdir ${BIN_DIR} echo "#! ${ROOTFS}/bin/argv -x" > ${BIN_DIR}/${TMP} chmod +x ${BIN_DIR}/${TMP} ln -s ${BIN_DIR}/${TMP} ${BIN_DIR}/${TMP2} ${PROOT} env ${BIN_DIR}/${TMP} | grep "${ROOTFS}/bin/argv -x ${BIN_DIR}/${TMP}" ${PROOT} env PATH=${BIN_DIR} ${TMP} | grep "${ROOTFS}/bin/argv -x ${BIN_DIR}/${TMP}" (cd ${BIN_DIR}; ${PROOT} env ./${TMP}) | grep "${ROOTFS}/bin/argv -x ./${TMP}" ${PROOT} env ${BIN_DIR}/${TMP2} | grep "${ROOTFS}/bin/argv -x ${BIN_DIR}/${TMP2}" ${PROOT} env PATH=${BIN_DIR} ${TMP2} | grep "${ROOTFS}/bin/argv -x ${BIN_DIR}/${TMP2}" (cd ${BIN_DIR}; ${PROOT} env ./${TMP2}) | grep "${ROOTFS}/bin/argv -x ./${TMP2}" ${PROOT} ${BIN_DIR}/${TMP} | grep "${ROOTFS}/bin/argv -x ${BIN_DIR}/${TMP}" env PATH=${BIN_DIR} ${PROOT} ${TMP} | grep "${ROOTFS}/bin/argv -x ${BIN_DIR}/${TMP}" # TODO: (cd ${BIN_DIR}; ${PROOT} ./${TMP}) | grep "${ROOTFS}/bin/argv -x ./${TMP}" (cd ${BIN_DIR}; ${PROOT} sh -c "true; ./${TMP}") | grep "${ROOTFS}/bin/argv -x ./${TMP}" rm -fr ${BIN_DIR} proot-5.1.0/tests/test-proocare.sh0000644000175000017500000000077112443566643016543 0ustar ivoireivoireif [ ! -e /boot ] || [ -z `which mcookie` ] || [ -z `which ln` ] || [ -z `which mkdir` ] || [ -z `which rm` ]; then exit 125; fi if [ ! -e $CARE ]; then exit 125; fi unset PROOT TMP_PROOT=/tmp/$(mcookie) TMP_OUTPUT=/tmp/$(mcookie)/ ln -s ${CARE} ${TMP_PROOT} mkdir ${TMP_OUTPUT} ${TMP_PROOT} -b /boot:/toob ${CARE} -o ${TMP_OUTPUT}/ ls /toob test -e ${TMP_OUTPUT}/rootfs/toob ! test -e ${TMP_OUTPUT}/rootfs/boot [ $? -eq 0 ] ${TMP_OUTPUT}/re-execute.sh rm -fr ${TMP_PROOT} ${TMP_OUTPUT} proot-5.1.0/tests/test-713b6910.sh0000644000175000017500000000226312443566643015723 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} proot-5.1.0/tests/test-ptrace00.c0000644000175000017500000000350412443566643016154 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); } proot-5.1.0/tests/test-5bed7141.c0000644000175000017500000000336012443566643015672 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); } proot-5.1.0/tests/false.c0000644000175000017500000000003512443566643014647 0ustar ivoireivoireint main(void) { return 1; } proot-5.1.0/tests/test-33333334.c0000644000175000017500000000073612443566643015452 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); } } proot-5.1.0/tests/test-af062114.c0000644000175000017500000000105512443566643015601 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); } proot-5.1.0/tests/test-c5a7a0f0.c0000644000175000017500000000303612443566643015744 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); } proot-5.1.0/tests/test-e940896f.sh0000644000175000017500000000110212443566643016014 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} proot-5.1.0/tests/test-e99993c8.sh0000644000175000017500000000047312443566643016036 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$ proot-5.1.0/tests/argv.c0000644000175000017500000000020412443566643014512 0ustar ivoireivoire#include int main(int argc, char *argv[]) { int i; for (i = 0; i < argc; i++) printf("%s ", argv[i]); return 0; } proot-5.1.0/tests/test-pppppppp.sh0000644000175000017500000000047112443566643016605 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} proot-5.1.0/tests/test-kkkkkkkk.c0000644000175000017500000000630012443566643016342 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); } proot-5.1.0/tests/test-3dec4597.sh0000644000175000017500000000017112443566643016072 0ustar ivoireivoireif [ ! -x ${ROOTFS}/bin/pwd ]; then exit 125; fi ${PROOT} -m /tmp:/longer-tmp -w /longer-tmp -r ${ROOTFS} /bin/pwd proot-5.1.0/tests/test-fa205b56.c0000644000175000017500000000106712443566643015672 0ustar ivoireivoire#include #include #include #include #define NUM_THREADS 5 void *exec(void *id) { char *const argv[] = { "true", NULL }; if ((long) id == NUM_THREADS - 1) execve("/usr/bin/true", argv, NULL); else sleep(50); pthread_exit(NULL); } int main() { pthread_t threads[NUM_THREADS]; int status; long i; exit(125); /* NYI */ for(i = 0; i < NUM_THREADS ; i++) { status = pthread_create(&threads[i], NULL, exec, (void *) i); if (status) exit(EXIT_FAILURE); sleep(1); } sleep(50); exit(EXIT_FAILURE); } proot-5.1.0/tests/test-79cf6614.c0000644000175000017500000000120712443566643015625 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); } proot-5.1.0/tests/test-c47aeb7d.c0000644000175000017500000000111212443566643016027 0ustar ivoireivoire#include #include #include #include void *print_hello(void *id) { pthread_exit(id); } int main(void) { const int nb_threads = 10; pthread_t threads[nb_threads]; int status; long i; for(i = 0; i < nb_threads; i++) { status = pthread_create(&threads[i], NULL, print_hello, (void *) i); if (status != 0) exit(EXIT_FAILURE); } for(i = 0; i < nb_threads; i++) { intptr_t result; status = pthread_join(threads[i], (void **) &result); if (status != 0 || (int) result != i) exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } proot-5.1.0/tests/test-0228fbe7.sh0000644000175000017500000000144312443566643016065 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) ${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} proot-5.1.0/tests/ptrace-2.c0000644000175000017500000002570612443566643015206 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) { perror("waitpid()"); if (errno != ECHILD) 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; fprintf(stderr, "sid %d -> pid %d\n", sid, pid); } 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): %s\n", sid, signal, strerror(errno)); } return last_exit_status; } proot-5.1.0/tests/symlink.c0000644000175000017500000000071012443566643015243 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); } proot-5.1.0/tests/test-ffffffff.sh0000644000175000017500000000043712443566643016467 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} proot-5.1.0/tests/test-99999999.sh0000644000175000017500000000327012443566643015715 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} proot-5.1.0/tests/test-0238c7f1.sh0000644000175000017500000000043112443566643015777 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 '^/$' proot-5.1.0/tests/test-d1da0d8d.sh0000644000175000017500000000046512443566643016222 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$' proot-5.1.0/tests/test-fdf487a0.c0000644000175000017500000000107612443566643015763 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); } proot-5.1.0/tests/test-c6b77b77.sh0000644000175000017500000000013012443566643016066 0ustar ivoireivoireif [ -z `which make` ]; then exit 125; fi ${PROOT} make -f ${PWD}/test-c6b77b77.mk proot-5.1.0/tests/test-xxxxxxxx.c0000644000175000017500000000171412443566643016516 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); } proot-5.1.0/tests/test-d2175fc4.c0000644000175000017500000000117612443566643015700 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); } proot-5.1.0/tests/test-9f5eeb72.sh0000644000175000017500000000745412443566643016166 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} proot-5.1.0/tests/test-c6b77b77.mk0000644000175000017500000000013512443566643016070 0ustar ivoireivoireSHELL=/bin/bash FOO:=$(shell test -e /dev/null && echo OK) all: @/usr/bin/test -n "$(FOO)" proot-5.1.0/tests/test-2db65cd2.sh0000644000175000017500000000101712443566643016136 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} proot-5.1.0/tests/test-9c07fad8.c0000644000175000017500000000025112443566643015757 0ustar ivoireivoire#include int check = 0; static void __attribute__((constructor)) init(void) { if (check > 0) _exit(1); check++; } int main(void) { return 0; } proot-5.1.0/tests/test-5bed7142.sh0000644000175000017500000000051712443566643016064 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 ] proot-5.1.0/tests/test-hhhhhhhh.sh0000644000175000017500000000112712443566643016504 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} proot-5.1.0/tests/test-c68d18dc.sh0000644000175000017500000000104512443566643016150 0ustar ivoireivoireif [ -z `which mkdir` ] || [ -z `which rm` ] || [ -z `which mcookie` ] || [ -z `which chmod` ] || [ -z `which ln` ]; then exit 125; fi if [ ! -e $CARE ]; then exit 125; fi unset PROOT SYMLINK=/tmp/$(mcookie) FOLDER=/tmp/$(mcookie) SCRIPT=${FOLDER}/script.sh ARCHIVE=/tmp/$(mcookie)/ mkdir ${FOLDER} echo "true" > ${SCRIPT} chmod +x ${SCRIPT} ln -s ${FOLDER} ${SYMLINK} cd ${SYMLINK} ${CARE} -r ${FOLDER} -o ${ARCHIVE} sh ./script.sh test -e ${ARCHIVE}/rootfs${SCRIPT} ${ARCHIVE}/re-execute.sh rm -fr ${SYMLINK} ${FOLDER} ${ARCHIVE} proot-5.1.0/tests/test-careauth.sh0000644000175000017500000000132612443566643016522 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} proot-5.1.0/tests/test-305ae31d.c0000644000175000017500000000251112443566643015660 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); } proot-5.1.0/tests/test-230f47cg.sh.deprecated0000644000175000017500000000176412443566643020172 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} proot-5.1.0/tests/test-6b5a254a.sh0000644000175000017500000000150112443566643016052 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} proot-5.1.0/tests/test-16573e73.c0000644000175000017500000000101112443566643015531 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); } proot-5.1.0/tests/test-8e5fa256.sh0000644000175000017500000000443212443566643016074 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} proot-5.1.0/tests/test-82ba4ba1.sh0000644000175000017500000000252112443566643016130 0ustar ivoireivoireif [ ! -x /bin/true ] || [ -z `which id` ] || [ -z `which grep` ] || [ -z `which env` ] || [ -z `which chown` ] || [ -z `which chroot` ]; 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$' proot-5.1.0/tests/test-071599da.sh0000644000175000017500000000114312443566643016006 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 ] proot-5.1.0/tests/test-22222222.sh0000644000175000017500000000122212443566643015620 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} proot-5.1.0/tests/test-b161bc0a.sh0000644000175000017500000000017612443566643016127 0ustar ivoireivoireif [ -z `which pwd` ] || [ -z `which grep` ]; then exit 125; fi ${PROOT} -w /tmp/a -m /etc:/tmp/a pwd | grep '^/tmp/a$' proot-5.1.0/tests/test-7601199b.sh0000644000175000017500000000017312443566643015727 0ustar ivoireivoireif [ ! -x /bin/sh ] || [ -z `which grep` ]; then exit 125; fi ${PROOT} -w /tmp /bin/sh -c 'echo $PWD' | grep '^/tmp$' proot-5.1.0/tests/test-44444444.c0000644000175000017500000000053112443566643015452 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); } proot-5.1.0/tests/cat.c0000644000175000017500000000112412443566643014324 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); } proot-5.1.0/tests/test-311b7a95.sh0000644000175000017500000000055612443566643016006 0ustar ivoireivoireif [ -z `which mcookie` ] || [ -z `which rm` ] || [ -z `which chmod` ] || [ -z `which echo` ]; then exit 125; fi TMP1=/tmp/$(mcookie) TMP2=/tmp/$(mcookie) echo "#! ${TMP2} -a" > ${TMP1} echo "#! $(which echo) -b" > ${TMP2} chmod +x ${TMP1} ${TMP2} RESULT=$(${PROOT} ${TMP1}) EXPECTED=$(${TMP1}) test "${RESULT}" = "${EXPECTED}" rm -f ${TMP1} ${TMP2} proot-5.1.0/tests/chdir_getcwd.c0000644000175000017500000000107312443566643016206 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); } proot-5.1.0/tests/test-fbca9cc2.sh0000644000175000017500000000043312443566643016300 0ustar ivoireivoireif [ -z `which strace` ] || [ -z `which true` ] || [ -z `which grep` ] || [ -z `which wc` ]; then exit 125; fi ${PROOT} strace -e trace=execve true 2>&1 | grep '^execve.*= 0$' RESULT=$(${PROOT} strace -e trace=execve true 2>&1 | grep '^execve' | wc -l) test "${RESULT}" = "1" proot-5.1.0/tests/test-55fd1da5.sh0000644000175000017500000000011312443566643016135 0ustar ivoireivoireif [ -z `which ls` ]; then exit 125; fi ${PROOT} -b /etc:/x ls -la /x proot-5.1.0/tests/test-53355a5b.sh0000644000175000017500000000036212443566643016001 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} proot-5.1.0/tests/test-a8e69d6f.c0000644000175000017500000000076712443566643016002 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); } proot-5.1.0/tests/test-carequot.sh0000644000175000017500000000051712443566643016552 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} proot-5.1.0/tests/exec.c0000644000175000017500000000045312443566643014505 0ustar ivoireivoire#include #include #include int main(int argc, char *argv[], char *envp[]) { if (argc < 2) { puts("not enough parameter: filename argv[0] argv[1] ... argv[n]"); exit(EXIT_FAILURE); } execve(argv[1], &argv[2], envp); perror("execve"); exit(EXIT_FAILURE); } proot-5.1.0/tests/test-1cd9d8f9.sh0000644000175000017500000000015412443566643016157 0ustar ivoireivoireif ! `which pwd` -P || [ -z `which grep` ] ; then exit 125; fi ${PROOT} -w /tmp pwd -P | grep '^/tmp$' proot-5.1.0/tests/test-mmmmmmmm.sh0000644000175000017500000000041412443566643016552 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}/ proot-5.1.0/tests/test-03969e70.sh0000644000175000017500000000173012443566643015733 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 ] proot-5.1.0/tests/test-f7089d4f.sh0000644000175000017500000000022712443566643016100 0ustar ivoireivoireif [ -z `which timeout` ] || [ -z `which msgmerge` ] || [ ! -e /dev/null ]; then exit 125; fi timeout 5s ${PROOT} msgmerge -q /dev/null /dev/null proot-5.1.0/tests/test-691786c8.sh0000644000175000017500000000255012443566643015745 0ustar ivoireivoireif [ ! -x /usr/bin/echo ] || [ -z `which mcookie` ] || [ -z `which chmod` ] || [ -z `which env` ] || [ -z `which rm` ]; then exit 125; fi TMP=/tmp/$(mcookie) echo '#!/usr/bin/echo XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' > ${TMP} chmod +x ${TMP} RESULT=$(${PROOT} ${TMP}) EXPECTED=$(${TMP}) [ "${RESULT}" = "${EXPECTED}" ] echo '#!//../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../usr/bin/echo XXXXXXXXX' > ${TMP} RESULT=$(${PROOT} ${TMP}) EXPECTED=$(${TMP}) [ "${RESULT}" = "${EXPECTED}" ] [ "${RESULT}" = "${TMP}" ] echo '#!/../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../usr/bin/echo XXXXXXXXX' > ${TMP} ! ${PROOT} ${TMP} [ $? -eq 0 ] ! ${TMP} [ $? -eq 0 ] echo '#! ' > ${TMP} ${PROOT} ${TMP} echo '#!' > ${TMP} ${PROOT} ${TMP} /usr/bin/echo "#!${TMP}" > ${TMP} env LANG=C ${PROOT} ${TMP} 2>&1 | grep 'Too many levels of symbolic links' [ $? -eq 0 ] rm -f ${TMP} proot-5.1.0/tests/test-1ffc8309.sh0000644000175000017500000000035612443566643016073 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} proot-5.1.0/tests/pwd.c0000644000175000017500000000061312443566643014351 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); } proot-5.1.0/tests/test-5467b986.sh0000644000175000017500000000352612443566643015750 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$') proot-5.1.0/tests/test-654decce.sh0000644000175000017500000000717112443566643016234 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} proot-5.1.0/tests/test-1c68c218.c0000644000175000017500000000070412443566643015614 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); } proot-5.1.0/tests/test-wwwwwwww.sh0000644000175000017500000000041012443566643016666 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} proot-5.1.0/tests/true.c0000644000175000017500000000003512443566643014534 0ustar ivoireivoireint main(void) { return 0; } proot-5.1.0/tests/test-0830d8a8.sh0000644000175000017500000000021712443566643016003 0ustar ivoireivoireif [ ! -x ${ROOTFS}/bin/exec-m32 ] || [ ! -x ${ROOTFS}/bin/true ]; then exit 125; fi ${PROOT} ${ROOTFS}/bin/exec-m32 ${ROOTFS}/bin/true proot-5.1.0/tests/test-ptrace01.c0000644000175000017500000000162112443566643016153 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); } proot-5.1.0/tests/validation.mk0000644000175000017500000000775512443566643016114 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 glibc-version = 2.17 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) glibc = glibc-$(glibc-version) testsuites = $(libuv) $(perl) $(ltp) $(opt) $(gdb) $(proot) $(coreutils) # $(glibc) too long. 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/attach-twice.exp # kills PRoot explicitly ($(MAKE) -C $(gdb)/gdb/testsuite check-gdb.base1 check-gdb.base2 check-gdb.server || true) | tee $@ ###################################################################### $(glibc).tar.xz: wget http://ftp.gnu.org/gnu/glibc/$(glibc).tar.xz -O $@ $(glibc).log: $(glibc).tar.xz rm -fr $(glibc) tar -xf $< mkdir -p $(glibc)/build/prefix cd $(glibc)/build && ../configure --prefix=$(PWD)/prefix $(MAKE) -C $(glibc)/build cp /usr/lib*/libgcc_s.so.1 $(glibc)/build cp /usr/lib*/libstdc++.so.6 $(glibc)/build sed -i s/tst-atexit3//g $(glibc)/dlfcn/Makefile # fails natively on Slack64-14.1 sed -i s/tst-cputimer1//g $(glibc)/rt/Makefile # fails natively on Slack64-14.1 sed -i 's/tests: check-abi/tests: /g' $(glibc)/Makerules # fails natively on Slack64-14.1 ($(MAKE) -j 1 -C $(glibc)/build check || true) | tee $@ # has broken // build ###################################################################### $(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 $@ proot-5.1.0/tests/readdir.c0000644000175000017500000000171612443566643015176 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); } proot-5.1.0/tests/puts_proc_self_exe.c0000644000175000017500000000047312443566643017453 0ustar ivoireivoire#include #include #include #include int main(void) { char path[PATH_MAX]; ssize_t status; status = readlink("/proc/self/exe", path, PATH_MAX - 1); if (status < 0 || status >= PATH_MAX) exit(EXIT_FAILURE); path[status] = '\0'; puts(path); exit(EXIT_SUCCESS); } proot-5.1.0/tests/test-230f47ch.sh0000644000175000017500000000162612443566643016071 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$ proot-5.1.0/tests/test-82ba4ba1.c0000644000175000017500000000313612443566643015743 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); } proot-5.1.0/tests/test-rrrrrrrr.sh0000644000175000017500000000032712443566643016625 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}$ proot-5.1.0/tests/test-commmmmm.sh0000644000175000017500000000136112443566643016544 0ustar ivoireivoireif [ -z `which mcookie` ] || [ -z `which cat` ] || [ -z `which grep` ] || [ -z `which chmod` ] || [ -z `which cut` ] || [ -z `which rm` ] || [ -z `which ln` ] || [ -z `which env` ]; then exit 125; fi TMP=$(mcookie) TMP2=$(echo ${TMP} | cut -b 1-15) TMP3=$(mcookie) TMP4=$(echo ${TMP3} | cut -b 1-15) ${PROOT} cat /proc/self/comm | grep cat ${PROOT} $(which cat) /proc/self/comm | grep cat echo '#!/bin/sh' > /tmp/${TMP} chmod +x /tmp/${TMP} # TODO: (cd /tmp; ${PROOT} env LD_SHOW_AUXV=1 ./${TMP}) | grep ^AT_EXECFN:[[:space:]]*./${TMP}$ echo 'cat /proc/$$/comm' >> /tmp/${TMP} ${PROOT} /tmp/${TMP} | grep ^${TMP2}$ ln -s /tmp/${TMP} /tmp/${TMP3} ${PROOT} /tmp/${TMP3} /proc/self/comm | grep ^${TMP4}$ rm -f /tmp/${TMP} /tmp/${TMP3} proot-5.1.0/tests/test-25069c13.c0000644000175000017500000000036612443566643015535 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); } proot-5.1.0/tests/test-11111111.sh0000644000175000017500000000267112443566643015621 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 proot-5.1.0/tests/test-230f47cf.sh0000644000175000017500000000016612443566643016065 0ustar ivoireivoire! ${PROOT} ${PROOT_RAW} /bin/true if [ $? -eq 0 ]; then exit 125; fi echo exit | ${PROOT} -v 0 ${PROOT_RAW} -v 0 proot-5.1.0/tests/test-517e1d6b.sh0000644000175000017500000000045412443566643016065 0ustar ivoireivoireif [ -z `which true` ] || [ -z `which realpath` ] || [ -z `which grep` ] || [ -z `which env` ] || [ ! -x ${ROOTFS}/bin/puts_proc_self_exe ]; then exit 125; fi TRUE=$(realpath $(which true)) env PROOT_FORCE_FOREIGN_BINARY=1 ${PROOT} -q ${ROOTFS}/bin/puts_proc_self_exe ${TRUE} | grep ^${TRUE}$ proot-5.1.0/tests/test-cccccccc.sh0000644000175000017500000000035312443566643016434 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} proot-5.1.0/tests/fork-wait.c0000644000175000017500000000102612443566643015461 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)); } } proot-5.1.0/tests/test-a4d7ed70.sh0000644000175000017500000000063612443566643016150 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} proot-5.1.0/tests/test-cea75343.sh0000644000175000017500000000173112443566643016064 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} proot-5.1.0/tests/test-b6df3cbe.sh0000644000175000017500000000055112443566643016303 0ustar ivoireivoireif [ -z `which mcookie` ] || [ -z `which cat` ] || [ -z `which tr` ] || [ -z `which grep` ] || [ -z `which grep` ] || [ -z `which chmod` ]; then exit 125; fi TMP=$(mcookie) cat > /tmp/${TMP} < * 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; } } proot-5.1.0/tests/test-cb1143ab.sh0000644000175000017500000000253612443566643016132 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} proot-5.1.0/tests/argv0.c0000644000175000017500000000012312443566643014572 0ustar ivoireivoire#include int main(int argc, char **argv) { puts(argv[0]); return 0; } proot-5.1.0/tests/readlink.c0000644000175000017500000000104612443566643015351 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); } proot-5.1.0/tests/test-a3e68988.c0000644000175000017500000000427212443566643015640 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); } proot-5.1.0/tests/GNUmakefile0000644000175000017500000001311612443566643015467 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 . ./$<) # Not supported anymore. # 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-c47aeb7d.c: test-c47aeb7d @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-test-9c07fad8.c: test-9c07fad8 $(call check_c,$<,$(PROOT) ./$<) check-test-fa205b56.c: test-fa205b56 $(call check_c,$<,$(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 ROOTFS_BIN = $(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)/bin/chdir_getcwd $(ROOTFS)/bin/fchdir_getcwd $(ROOTFS)/bin/argv \ $(ROOTFS)/bin/fork-wait $(ROOTFS)/bin/ptrace $(ROOTFS)/bin/ptrace-2 \ $(ROOTFS)/bin/puts_proc_self_exe $(ROOTFS)/bin/exec $(ROOTFS)/bin/exec-m32 ROOTFS_DIR = $(ROOTFS)/bin $(ROOTFS)/tmp $(ROOTFS_BIN): | $(ROOTFS_DIR) $(ROOTFS_DIR): @mkdir -p $@ setup: $(ROOTFS_BIN) $(ROOTFS)/bin/abs-true: @ln -fs /bin/true $@ $(ROOTFS)/bin/rel-true: @ln -fs ./true $@ $(ROOTFS)/bin/exec-m32: exec.c $(Q)$(CC) -m32 -static $^ -o $@ $(silently) || true .SECONDARY: $(patsubst %.c,$(ROOTFS)/bin/%, $(wildcard test-*.c)) $(ROOTFS)/bin/%: %.c $(Q)$(CC) -static $*.c -o $@ $(silently) || true # Special cases. test-bdc90417: test-bdc90417.c $(Q)$(CC) $< -o $@ $(silently) || true # Not supported anymore. # 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 test-9c07fad8: test-9c07fad8.c $(Q)$(CC) -fPIE -pie $< -o $@ $(silently) || true test-fa205b56: test-fa205b56.c $(Q)$(CC) $< -pthread -o $@ $(silently) || true test-c47aeb7d: test-c47aeb7d.c $(Q)$(CC) $< -pthread -o $@ $(silently) || true ###################################################################### # Beautified output V = 0 ifeq ($(V), 0) quiet = quiet_ Q = @ silently = >/dev/null 2>&1 else quiet = Q = silently = endif proot-5.1.0/tests/test-careexit.sh0000644000175000017500000000055512443566643016535 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 proot-5.1.0/tests/test-dfb0c3b6.sh0000644000175000017500000000147112443566643016220 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} proot-5.1.0/tests/test-6fb08ce1.sh0000644000175000017500000000033712443566643016145 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} proot-5.1.0/tests/test-gggggggg.sh0000644000175000017500000000102112443566643016465 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} proot-5.1.0/tests/test-e87b34ae.c0000644000175000017500000000120312443566643015752 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); } proot-5.1.0/tests/test-bdc90417.c0000644000175000017500000000167312443566643015700 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); } } proot-5.1.0/tests/test-5bed7143.c0000644000175000017500000000255012443566643015674 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); } proot-5.1.0/tests/test-oooooooo.c0000755000175000017500000000115512443566643016410 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); } proot-5.1.0/tests/test-bbbbbbbb.sh0000644000175000017500000000111012443566643016414 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} proot-5.1.0/tests/test-cdd39012.sh0000644000175000017500000000050312443566643016053 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 proot-5.1.0/tests/echo.c0000644000175000017500000000017212443566643014475 0ustar ivoireivoire#include int main(int argc, char **argv) { int i; for (i = 1; i < argc; i++) puts(argv[i]); return 0; } proot-5.1.0/tests/test-ssssssss.c0000644000175000017500000000220512443566643016442 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); } proot-5.1.0/tests/test-305ae31d.sh0000644000175000017500000000030712443566643016051 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} proot-5.1.0/tests/fchdir_getcwd.c0000644000175000017500000000122712443566643016355 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); } proot-5.1.0/tests/test-d92b57ca.sh0000644000175000017500000000017212443566643016144 0ustar ivoireivoireif [ -z `which env` ] || [ -z `which true` ]; then exit 125; fi env PROOT_NO_SUBRECONF=1 ${PROOT} ${PROOT} -v 1 true proot-5.1.0/tests/test-55b731d3.sh0000644000175000017500000000007412443566643016002 0ustar ivoireivoireif ! `which pwd` -P; then exit 125; fi ${PROOT} pwd -P proot-5.1.0/tests/ptrace.c0000644000175000017500000000160112443566643015033 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; } proot-5.1.0/tests/test-1ffc8309.c0000644000175000017500000000071512443566643015702 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); } proot-5.1.0/tests/test-nnnnnnnn.c0000644000175000017500000000325112443566643016374 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); } proot-5.1.0/tests/test-00000000.sh0000644000175000017500000000012712443566643015603 0ustar ivoireivoireif [ ! -x ${ROOTFS}/bin/true ]; then exit 125; fi ${PROOT} -r ${ROOTFS} /bin/true proot-5.1.0/tests/test-0cf405b0.c0000644000175000017500000000043212443566643015656 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); } proot-5.1.0/tests/test-e87ca6ca.sh0000644000175000017500000000053512443566643016230 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} proot-5.1.0/tests/test-5996858d.sh0000644000175000017500000000151012443566643015746 0ustar ivoireivoireif [ -z `which uname` ] || [ -z `which grep` ] || [ -z `which domainname` ] || [ -z `which hostname` ]|| [ -z `which env` ] || [ -z `which true`]; then exit 125; fi UTSNAME="\\sysname\\nodename\\$(uname -r)\\version\\machine\\domainname\\0\\" ${PROOT} -k ${UTSNAME} uname -s | grep ^sysname$ ${PROOT} -k ${UTSNAME} uname -n | grep ^nodename$ ${PROOT} -k ${UTSNAME} uname -v | grep ^version$ ${PROOT} -k ${UTSNAME} uname -m | grep ^machine$ ${PROOT} -k ${UTSNAME} domainname | grep ^domainname$ ${PROOT} -k ${UTSNAME} env LD_SHOW_AUXV=1 true | grep -E '^AT_HWCAP:[[:space:]]*0?$' ${PROOT} -0 -k ${UTSNAME} sh -c 'domainname domainname2; domainname' | grep ^domainname2$ ${PROOT} -0 -k ${UTSNAME} sh -c 'hostname hostname2; hostname' | grep ^hostname2$ ${PROOT} -0 -k ${UTSNAME} sh -c 'hostname hostname2; uname -n' | grep ^hostname2$ proot-5.1.0/tests/test-6d1e2650.sh0000644000175000017500000000026012443566643015776 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 proot-5.1.0/tests/test-1743dd3d.sh0000644000175000017500000000061212443566643016060 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} proot-5.1.0/tests/test-iiiiiiii.c0000644000175000017500000000220212443566643016317 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); } proot-5.1.0/tests/test-c10e2073.c0000644000175000017500000000160312443566643015600 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); } proot-5.1.0/tests/test-dddddddd.sh0000644000175000017500000000146012443566643016444 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 proot-5.1.0/tests/test-3624be91.sh0000644000175000017500000000027712443566643016011 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 :)' proot-5.1.0/tests/test-c15999f9.sh0000644000175000017500000000041512443566643016026 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} proot-5.1.0/tests/test-77777777.c.unreliable0000644000175000017500000000177212443566643017633 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); } } proot-5.1.0/tests/test-51943658.c0000644000175000017500000000240512443566643015465 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); } proot-5.1.0/doc/0000755000175000017500000000000012443566643013016 5ustar ivoireivoireproot-5.1.0/doc/ecosystem/0000755000175000017500000000000012443566643015031 5ustar ivoireivoireproot-5.1.0/doc/ecosystem/care-ecosystem.svg0000644000175000017500000006450112443566643020503 0ustar ivoireivoire CARE Gogo STMicroelectronics is used by is presented at Linaro LAVA is shipped in Arch Linux Gentoo Linaro Connect 2014 TRUST 2014 OpenMole Fosdem 2012 many web pages is used in Gogo uses CARE to reproduce -- bit accurately -- the build of an embedded Linux distribution. Copyright holder and unique sponsor of CARE. Linaro LAVA uses CARE to boot some ST boards and to archives failures during validation Lightning talk About 100 domains references http://reproducible.io according to Google proot-5.1.0/doc/ecosystem/proot-ecosystem.graphml0000644000175000017500000013240012443566643021561 0ustar ivoireivoire PRoot BrickStrap Debian Portable Pypy GNURoot Debian noroot is core for is used in is shipped in is presented at STMicroelectronics Fosdem 2012 CARE Gentoo Arch Linux Ubuntu 0install NixOS NixOS xampp AUR hammerwatch AUR Enea OPAM2Debian SIO Workers QEMU workshop 2011 Linaro Connect 2014 many web pages Sony is used by Cisco Ericsson proot-5.1.0/doc/ecosystem/proot-ecosystem.svg0000644000175000017500000014502612443566643020736 0ustar ivoireivoire PRoot BrickStrap Debian Portable Pypy GNURoot Debian noroot is core for is used in is shipped in is presented at STMicroelectronics Fosdem 2012 CARE Gentoo Arch Linux Ubuntu 0install NixOS NixOS xampp AUR hammerwatch AUR Enea OPAM2Debian SIO Workers QEMU workshop 2011 Linaro Connect 2014 many web pages Sony is used by Cisco Ericsson Copyright holder and unique sponsor of PRoot. Lightning talk Enea uses PRoot in an internal tool (host environment for API testing of target software) at a customer. About 175 domains references http://proot.me according to Google Sony has an internal wiki page about CE Linux that references http://proot.me Cisco has an internal wiki page that references http://proot.me Ericsson has an internal Bugzilla ticket that references http://proot.me proot-5.1.0/doc/ecosystem/care-ecosystem.graphml0000644000175000017500000005462612443566643021345 0ustar ivoireivoire CARE Gogo STMicroelectronics is used by is presented at Linaro LAVA is shipped in Arch Linux Gentoo Linaro Connect 2014 TRUST 2014 OpenMole Fosdem 2012 many web pages is used in proot-5.1.0/doc/stylesheets/0000755000175000017500000000000012443566643015372 5ustar ivoireivoireproot-5.1.0/doc/stylesheets/website.css0000644000175000017500000000470112443566643017550 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; } }proot-5.1.0/doc/stylesheets/website.xsl0000644000175000017500000000402312443566643017563 0ustar ivoireivoire

      
    
  • []

    []
    proot-5.1.0/doc/proot/0000755000175000017500000000000012443566643014161 5ustar ivoireivoireproot-5.1.0/doc/proot/man.10000644000175000017500000005112612443566643015023 0ustar ivoireivoire.\" Man page generated from reStructuredText. . .TH PROOT 1 "2014-12-12" "5.1.0" "" .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 /run/ .IP \(bu 2 /var/run/dbus/system_bus_socket .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 /run/shm .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/ ? \- ? 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 \- pts/6 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. . proot-5.1.0/doc/proot/changelog.txt0000644000175000017500000011201112443566643016645 0ustar ivoireivoireRelease v5.1.0 ============== New features ------------ + Processes under PRoot now appear with their real names, that is, they are not renamed "ld-linux.so" or "prooted-..." anymore: before: $ proot-v4.0.3 ps PID TTY TIME CMD 7885 pts/11 00:00:00 bash 8131 pts/11 00:00:00 proot-v4.0.3 8132 pts/11 00:00:00 ld-2.17.so $ proot-v5.0.0 ps PID TTY TIME CMD 7885 pts/11 00:00:00 bash 7916 pts/11 00:00:00 proot-v5.0.0 7917 pts/11 00:00:00 prooted-7916-Jb now: $ proot-v5.1.0 ps PID TTY TIME CMD 7885 pts/11 00:00:00 bash 8585 pts/11 00:00:00 proot-v5.1.0 8586 pts/11 00:00:00 ps Fixes ----- + It is now possible to use GDB against multi-threaded programs under PRoot x86_64 and x86. + It is possible to execute x86_64 programs from x86 programs again. + It is possible to use x86 ptrace-based programs (strace, gdb, ...) under PRoot x86_64 again. + The loader is now built with the "build-id" linker option explicitly disabled. This special section might interfere with loaded programs. + The loader can now load relocatable objects that have a predefined base address. Acknowledgements ---------------- Thanks to Erwan Gouriou, Sébastien Gandon, Christian milkylainen, Henrik Wallin, and Frank Teo for their bug reports and tests. Thanks to Jérôme Audu, Yann Droneaud, and Christophe Monat for their precious help. Release v5.0.0 ============== Highlight --------- PRoot used to rely on the ELF loader embedded in the ELF interpreter from the GNU libc. Sadly this latter suffers from many issues: + programs that use constructors or destructors might crash: a typical example is C++ programs. + programs that rely on the "rpath" mechanism and that are invoked through a symlink might not start: a typical example is the JVM on Debian. + programs that read processes command-line migth be confused because initial argv[0] is replaced: typical examples are ps and top. Moreover not all ELF interpreters provide this feature. For instance, ELF interpreters shipped with Bionic (Android) and some versions of the uClibC can't be used as ELF loaders. As a consequence it was not possible to proot into a rootfs that uses such C library. Now PRoot has its own loader, that means all the limitations above doesn't exist anymore. Fixes ----- + Most bugs related to shebang support -- ie. "#!" at the beginning of a program -- were fixed. Command-line interface changes ------------------------------ + PRoot now starts a login shell when no command is specified; this makes the shell read profile files from the guest rootfs, as expected by some guest programs. To get the old behavior, launch "/bin/sh" explicitly: proot -r whatever /bin/sh + The -R option now binds "/run" and "/var/run/dbus/system_bus_socket" too. This is useful for guest programs that need to communicate with host services. 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. proot-5.1.0/doc/proot/stylesheets/0000755000175000017500000000000012443566643016535 5ustar ivoireivoireproot-5.1.0/doc/proot/stylesheets/website.css0000644000175000017500000000023712443566643020713 0ustar ivoireivoire@import url("website.css"); h1 { color: orange; } #contents a:hover { border-bottom: 2px solid orange; } a { border-bottom: 1px solid orange; } proot-5.1.0/doc/proot/stylesheets/cli.xsl0000644000175000017500000001167312443566643020044 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, const char *value); proot-5.1.0/doc/proot/stylesheets/website.xsl0000644000175000017500000000637212443566643020737 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.

    proot-5.1.0/doc/proot/stylesheets/rpm-spec.xsl0000644000175000017500000000236212443566643021016 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 proot-5.1.0/doc/proot/roadmap.txt0000644000175000017500000001220212443566643016342 0ustar ivoireivoire========= Roadmap ========= PRoot v5.1.1 ============ * Fix ptrace emulation: on ARM. * Fix ``proot-x86_64 -k ... -r rootfs-i686 uname -m`` * Fix TODO in test-517e1d6a * Understand why loader-wrapped.o is so big on x86_64? PRoot v5.2: leveraging on the loader ==================================== Highlight --------- * Position-independent programs are loaded to predefined addresses, even if ASLR is enabled. This might results in conflicts with mappings created implicitly by the kernel ("[vdso]", "[stack]", ...). A reliable solution is to let the kernel choose the position of position-independent programs. * The loader is loaded to a predefined address, si it might conflicts with programs that are loaded to the same address. A solution is to detect such situation and to make the loader relocate itself. * The loader stays in memory, even once it is not used anymore. This might create useless address-space pressure for programs that need large memory mappings (ex. JVM). * Some programs assumes the heap segment is right after the data segment (cf. issue 52 on Github). Misc. ----- * Write a loader for a.out programs, to be able to execute programs from very old Linux distros :) PRoot v6.0: VFS =============== 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. Not yet scheduled ================= Fixes ----- * 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". * Fix ``mkdir foo; cd foo; rmdir ../foo; readlink /proc/self/cwd``. * 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 -------- * 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 ------------- * Mention "container" in the 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 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) proot-5.1.0/doc/proot/manual.txt0000644000175000017500000004445712443566643016215 0ustar ivoireivoire======= PRoot ======= ------------------------------------------------------------------------- ``chroot``, ``mount --bind``, and ``binfmt_misc`` without privilege/setup ------------------------------------------------------------------------- :Date: 2014-12-12 :Version: 5.1.0 :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/ * /run/ * /var/run/dbus/system_bus_socket * $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/ * /run/shm * $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/ ? sh ? 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 sh pts/6 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. :: _____ _____ ___ | __ \ __ \_____ _____| |_ | __/ / _ \/ _ \ _| |__| |__|__\_____/\_____/\____| proot-5.1.0/doc/proot/rpm-spec0000644000175000017500000011476012443566643015643 0ustar ivoireivoire%define version v5.1.0 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 * Thu Dec 11 2014 Cédric VINCENT Release v5.1.0 ============== New features ------------ + Processes under PRoot now appear with their real names, that is, they are not renamed "ld-linux.so" or "prooted-..." anymore: before: $ proot-v4.0.3 ps PID TTY TIME CMD 7885 pts/11 00:00:00 bash 8131 pts/11 00:00:00 proot-v4.0.3 8132 pts/11 00:00:00 ld-2.17.so $ proot-v5.0.0 ps PID TTY TIME CMD 7885 pts/11 00:00:00 bash 7916 pts/11 00:00:00 proot-v5.0.0 7917 pts/11 00:00:00 prooted-7916-Jb now: $ proot-v5.1.0 PID TTY TIME CMD 7885 pts/11 00:00:00 bash 8585 pts/11 00:00:00 proot-v5.1.0 8586 pts/11 00:00:00 ps Fixes ----- + It is now possible to use GDB against multi-threaded programs under PRoot. + It is possible to execute 32-bit programs from 64-bit programs again. + It is possible to use 32-bit ptrace-based programs (strace, gdb, ...) under PRoot 64-bit again. + The loader is now built with the "build-id" linker option explicitly disabled. This special section might interfere with loaded programs. Thanks ------ Thanks to Erwan Gouriou, Sébastien Gandon, Christian "milkylainen", and Henrik Wallin for their bug reports and tests. Thanks to Jérôme Audu, Yann Droneaud and Christophe Monat for their precious help to fix bugs. Release v5.0.0 ============== Highlight --------- PRoot used to rely on the ELF loader embedded in the ELF interpreter from the GNU libc. Sadly this latter suffers from many issues: + programs that use constructors or destructors might crash: a typical example is C++ programs. + programs that rely on the "rpath" mechanism and that are invoked through a symlink might not start: a typical example is the JVM on Debian. + programs that read processes command-line migth be confused because initial argv[0] is replaced: typical examples are ps and top. Moreover not all ELF interpreters provide this feature. For instance, ELF interpreters shipped with Bionic (Android) and some versions of the uClibC can't be used as ELF loaders. As a consequence it was not possible to proot into a rootfs that uses such C library. Now PRoot has its own loader, that means all the limitations above doesn't exist anymore. Fixes ----- + Most bugs related to shebang support -- ie. "#!" at the beginning of a program -- were fixed. Command-line interface changes ------------------------------ + PRoot now starts a login shell when no command is specified; this makes the shell read profile files from the guest rootfs, as expected by some guest programs. To get the old behavior, launch "/bin/sh" explicitly: proot -r whatever /bin/sh + The -R option now binds "/run" and "/var/run/dbus/system_bus_socket" too. This is useful for guest programs that need to communicate with host services. 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. proot-5.1.0/doc/care/0000755000175000017500000000000012443566643013730 5ustar ivoireivoireproot-5.1.0/doc/care/changelog.txt0000644000175000017500000000616012443566643016423 0ustar ivoireivoireCARE 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 proot-5.1.0/doc/care/stylesheets/0000755000175000017500000000000012443566643016304 5ustar ivoireivoireproot-5.1.0/doc/care/stylesheets/website.css0000644000175000017500000000024212443566643020456 0ustar ivoireivoire@import url("website.css"); h1 { color: SkyBlue; } #contents a:hover { border-bottom: 2px solid SkyBlue; } a { border-bottom: 1px solid SkyBlue; } proot-5.1.0/doc/care/stylesheets/cli.xsl0000644000175000017500000001241212443566643017603 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, const char *value); proot-5.1.0/doc/care/stylesheets/website.xsl0000644000175000017500000000560712443566643020506 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.

    proot-5.1.0/doc/care/roadmap.txt0000644000175000017500000000144512443566643016120 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, ...) proot-5.1.0/doc/care/manual.txt0000644000175000017500000003767612443566643015771 0ustar ivoireivoire====== CARE ====== ------------------------------------------------- Comprehensive Archiver for Reproducible Execution ------------------------------------------------- :Date: 2014-09-15 :Version: 2.2 :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. :: _____ ____ _____ ____ / __/ __ | __ \ __| / /_/ | / __| \_____|__|__|__|__\____| proot-5.1.0/doc/howto-release.txt0000644000175000017500000000236612443566643016344 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` 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 proot-5.1.0/doc/GNUmakefile0000644000175000017500000000142212443566643015067 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 # %/changelog.txt 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.* proot-5.1.0/doc/articles/0000755000175000017500000000000012443566643014624 5ustar ivoireivoireproot-5.1.0/doc/articles/stylesheets/0000755000175000017500000000000012443566643017200 5ustar ivoireivoireproot-5.1.0/doc/articles/stylesheets/article-html.txt0000644000175000017500000000026612443566643022332 0ustar ivoireivoire.. raw:: html proot-5.1.0/doc/articles/howto_debian_rootfs.txt0000644000175000017500000001566212443566643021435 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. proot-5.1.0/doc/articles/extending_qemu.txt0000644000175000017500000002300012443566643020374 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 proot-5.1.0/doc/articles/extending_qemu-fig1.svg0000644000175000017500000012460012443566643021210 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 proot-5.1.0/doc/articles/extending_qemu-fig2.svg0000644000175000017500000002405312443566643021212 0ustar ivoireivoire image/svg+xml QEMU: PRoot: host kernel: translation kernel operation system call result & errno t proot-5.1.0/doc/articles/howto_migrate_from_scratchbox2.txt0000644000175000017500000000716612443566643023574 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. proot-5.1.0/AUTHORS0000644000175000017500000000176512443566643013332 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. proot-5.1.0/COPYING0000644000175000017500000004325412443566643013314 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. proot-5.1.0/src/0000755000175000017500000000000012443566643013040 5ustar ivoireivoireproot-5.1.0/src/extension/0000755000175000017500000000000012443566643015054 5ustar ivoireivoireproot-5.1.0/src/extension/extension.c0000644000175000017500000001022412443566643017233 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/note.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) { note(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) { note(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); } } } proot-5.1.0/src/extension/kompat/0000755000175000017500000000000012443566643016347 5ustar ivoireivoireproot-5.1.0/src/extension/kompat/kompat.c0000644000175000017500000006271112443566643020015 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 /* str*(3), memcpy(3), */ #include /* talloc_*, */ #include /* AT_*, */ #include /* linux.git:c0a3a20b */ #include /* errno, */ #include /* AT_, */ #include /* FUTEX_PRIVATE_FLAG */ #include /* MIN, */ #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/note.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 { int actual_release; int virtual_release; struct utsname utsname; word_t hwcap; } Config; /** * Return whether the @expected_release is newer than * @config->actual_release and older than @config->virtual_release. */ static bool needs_kompat(const Config *config, int expected_release) { return (expected_release > config->actual_release && expected_release <= config->virtual_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->virtual_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; note(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; for (vector = vectors; vector->type != AT_NULL; vector++) { switch (vector->type) { /* 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. */ case AT_SYSINFO_EHDR: case AT_SYSINFO: vector->type = AT_IGNORE; vector->value = 0; break; case AT_HWCAP: if (config->hwcap != (word_t) -1) vector->value = config->hwcap; break; case AT_RANDOM: /* Skip only if not in forced mode. */ if (config->actual_release != 0) goto end; break; default: break; } } /* Add the AT_RANDOM vector only if needed. */ if (!needs_kompat(config, KERNEL_VERSION(2,6,29))) goto end; status = add_elf_aux_vector(&vectors, AT_RANDOM, vectors_address); if (status < 0) goto end; /* Not fatal. */ /* 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; /* Allocate enough room in tracee's stack for the new ELF * auxiliary vector. */ stack_pointer -= 2 * sizeof_word(tracee); vectors_address -= 2 * sizeof_word(tracee); /* Note that it is safe to update the stack pointer manually * since we are in execve sysexit. However it should be done * before transfering data since the kernel might not allow * page faults below the stack pointer. */ poke_reg(tracee, STACK_POINTER, stack_pointer); status = write_data(tracee, stack_pointer, argv_envp, size); if (status < 0) return; 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_uname: { word_t address; address = peek_reg(tracee, ORIGINAL, SYSARG_1); /* The layout of struct utsname does not depend on the * architecture, it only depends on the kernel * version. In this regards, this structure is stable * since < 2.6.0. */ status = write_data(tracee, address, &config->utsname, sizeof(config->utsname)); if (status < 0) return status; return 0; } case PR_setdomainname: case PR_sethostname: { word_t address; word_t length; char *name; name = (sysnum == PR_setdomainname ? config->utsname.domainname : config->utsname.nodename); length = peek_reg(tracee, ORIGINAL, SYSARG_2); if (length > sizeof(config->utsname.domainname) - 1) return -EINVAL; /* Because of the test above. */ assert(sizeof(config->utsname.domainname) == sizeof(config->utsname.nodename)); address = peek_reg(tracee, ORIGINAL, SYSARG_1); status = read_data(tracee, name, address, length); if (status < 0) return status; /* "name does not require a terminating null byte." -- * man 2 set{domain,host}name. */ name[length] = '\0'; 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; } /** * Fill @config->utsname and @config->hwcap according to the content * of @string. This function returns -1 if there is a parsing error, * otherwise 0. */ static int parse_utsname(Config *config, const char *string) { struct utsname utsname; int status; assert(string != NULL); status = uname(&utsname); if (status < 0 || getenv("PROOT_FORCE_KOMPAT") != NULL) config->actual_release = 0; else config->actual_release = parse_kernel_release(utsname.release); /* Check whether it is the simple format (ie. release number), * or the complex one: * * '\sysname\nodename\release\version\machine\domainname\hwcap\' * * This complex format is ugly on purpose: it ain't to be used * directly by users. */ if (string[0] == '\\') { const char *start; const char *end; char *end2; /* Initial state of the parser. */ end = string; #define PARSE(field) do { \ size_t length; \ \ start = end + 1; \ end = strchr(start, '\\'); \ if (end == NULL) { \ note(NULL, ERROR, USER, \ "can't find %s field in '%s'", #field, string); \ return -1; \ } \ \ length = end - start; \ length = MIN(length, sizeof(config->utsname.field) - 1); \ strncpy(config->utsname.field, start, length); \ config->utsname.field[length] = '\0'; \ } while(0) PARSE(sysname); PARSE(nodename); PARSE(release); PARSE(version); PARSE(machine); PARSE(domainname); #undef PARSE /* The hwcap field is parsed as an hexadecimal value. */ errno = 0; config->hwcap = strtol(end + 1, &end2, 16); if (errno != 0 || end2[0] != '\\') { note(NULL, ERROR, USER, "can't find hwcap field in '%s'", string); return -1; } } else { size_t length; memcpy(&config->utsname, &utsname, sizeof(config->utsname)); length = MIN(strlen(string), sizeof(config->utsname.release) - 1); strncpy(config->utsname.release, string, length); config->utsname.release[length] = '\0'; config->hwcap = (word_t) -1; } config->virtual_release = parse_kernel_release(config->utsname.release); 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_setdomainname, FILTER_SYSEXIT }, { PR_sethostname, FILTER_SYSEXIT }, { 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: { Config *config; extension->config = talloc_zero(extension, Config); if (extension->config == NULL) return -1; config = extension->config; status = parse_utsname(config, (const char *) data1); if (status < 0) return -1; 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); } case SYSCALL_EXIT_START: { Tracee *tracee = TRACEE(extension); Config *config = talloc_get_type_abort(extension->config, Config); word_t result = peek_reg(tracee, CURRENT, SYSARG_RESULT);; word_t sysnum = get_sysnum(tracee, ORIGINAL); /* Note: this can be done only before PRoot pushes the * load script into tracee's stack. */ if ((int) result >= 0 && sysnum == PR_execve) adjust_elf_auxv(tracee, config); return 0; } default: return 0; } } proot-5.1.0/src/extension/fake_id0/0000755000175000017500000000000012443566643016516 5ustar ivoireivoireproot-5.1.0/src/extension/fake_id0/fake_id0.c0000644000175000017500000005541312443566643020334 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_setdomainname, FILTER_SYSEXIT }, { PR_sethostname, 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_setdomainname: case PR_sethostname: 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; } 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 /** * Adjust some ELF auxiliary vectors. 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 int adjust_elf_auxv(Tracee *tracee, Config *config) { ElfAuxVector *vectors; ElfAuxVector *vector; word_t vectors_address; 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; for (vector = vectors; vector->type != AT_NULL; vector++) { switch (vector->type) { case AT_UID: vector->value = config->ruid; break; case AT_EUID: vector->value = config->euid; break; case AT_GID: vector->value = config->rgid; break; case AT_EGID: vector->value = config->egid; break; default: break; } } push_elf_aux_vectors(tracee, vectors, vectors_address); return 0; } /** * 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); } case SYSCALL_EXIT_START: { Tracee *tracee = TRACEE(extension); Config *config = talloc_get_type_abort(extension->config, Config); word_t result = peek_reg(tracee, CURRENT, SYSARG_RESULT);; word_t sysnum = get_sysnum(tracee, ORIGINAL); /* Note: this can be done only before PRoot pushes the * load script into tracee's stack. */ if ((int) result >= 0 && sysnum == PR_execve) adjust_elf_auxv(tracee, config); return 0; } default: return 0; } } proot-5.1.0/src/extension/care/0000755000175000017500000000000012443566643015766 5ustar ivoireivoireproot-5.1.0/src/extension/care/care.c0000644000175000017500000003606312443566643017054 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 /* AT_*, */ #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 "execve/auxv.h" #include "path/canon.h" #include "path/path.h" #include "path/binding.h" #include "cli/note.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) { note(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) { note(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) { note(tracee, WARNING, INTERNAL, "can't get output name"); return -1; } care->initial_cwd = talloc_strdup(care, tracee->fs->cwd); if (care->initial_cwd == NULL) { note(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) { note(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); note(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) { note(tracee, WARNING, INTERNAL, "can't allocate entry for '%s'", path); return; } entry->path = talloc_strdup(entry, path); if (entry->path == NULL) { note(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 note(tracee, WARNING, USER, "can't declare '%s' (fifo or socket) as volatile", path); return; } else note(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) { note(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) { note(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) { note(tracee, WARNING, INTERNAL, "can't read dentry"); break; } status = read_string(tracee, component, address + name_offset, PATH_MAX); if (status < 0 || status >= PATH_MAX) { note(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) note(tracee, WARNING, INTERNAL, "dentry table out of sync."); } /** * Set AT_HWCAP to 0 to ensure no processor specific extensions will * be used, for the sake of reproducibility across different CPUs. * 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 int adjust_elf_auxv(Tracee *tracee) { ElfAuxVector *vectors; ElfAuxVector *vector; word_t vectors_address; 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; for (vector = vectors; vector->type != AT_NULL; vector++) { if (vector->type == AT_HWCAP) vector->value = 0; } push_elf_aux_vectors(tracee, vectors, vectors_address); return 0; } /* 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; case PR_execve: { word_t result = peek_reg(tracee, CURRENT, SYSARG_RESULT); /* Note: this can be done only before PRoot pushes the * load script into tracee's stack. */ if ((int) result >= 0) adjust_elf_auxv(tracee); break; } default: break; } return 0; default: return 0; } } proot-5.1.0/src/extension/care/care.h0000644000175000017500000000351112443566643017051 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 */ proot-5.1.0/src/extension/care/extract.h0000644000175000017500000000222712443566643017614 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 */ proot-5.1.0/src/extension/care/final.h0000644000175000017500000000172612443566643017236 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 */ proot-5.1.0/src/extension/care/extract.c0000644000175000017500000002151712443566643017612 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/note.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: note(NULL, INFO, USER, "extracted: %s", archive_entry_pathname(entry)); break; default: result = -1; note(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); note(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) { note(NULL, ERROR, INTERNAL, "can't initialize archive structure"); status = -1; goto end; } status = archive_read_support_format_cpio(archive); if (status != ARCHIVE_OK) { note(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) { note(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) { note(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) { note(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) { note(NULL, ERROR, INTERNAL, "can't allocate callback data"); status = -1; goto end; } data->path = talloc_strdup(data, path); if (data->path == NULL) { note(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) note(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) { note(NULL, WARNING, INTERNAL, "can't close archive: %s", archive_error_string(archive)); } status2 = archive_read_free(archive); if (status2 != ARCHIVE_OK) { note(NULL, WARNING, INTERNAL, "can't free archive: %s", archive_error_string(archive)); } } TALLOC_FREE(data); return status; } proot-5.1.0/src/extension/care/archive.c0000644000175000017500000003413412443566643017560 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/note.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 note(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) note(tracee, WARNING, USER, "unknown suffix, assuming self-extracting format."); #else format->special = RAW; if (no_wrapper_found) note(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) { note(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) { note(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) { note(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) { note(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) { note(tracee, ERROR, SYSTEM, "can't write '%s'", destination); goto end; } if (status != size) note(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) { note(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) { note(tracee, WARNING, INTERNAL, "can't initialize archive structure"); return NULL; } status = archive_write_disk_set_options(archive->handle, flags); if (status != ARCHIVE_OK) { note(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) { note(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) { note(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) { note(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) { note(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) { note(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) { note(tracee, ERROR, SYSTEM, "can't open/create '%s'", output); return NULL; } status = write(archive->fd, "RAW", strlen("RAW")); if (status != strlen("RAW")) { note(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) { note(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) { note(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) { note(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) { note(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) note(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) { note(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) { note(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; } proot-5.1.0/src/extension/care/final.c0000644000175000017500000003310212443566643017222 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), get*id(2), */ #include /* get*id(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/note.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) { note(NULL, ERROR, SYSTEM, "can't get '%s' status", name); goto end; } location = talloc_asprintf(care, "%s/%s", care->prefix, name); if (location == NULL) { note(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) { note(NULL, ERROR, INTERNAL, "can't readlink(/proc/self/fd/%d)", 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) { \ note(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) { note(NULL, ERROR, INTERNAL, "can't create temporary file for 're-execute.sh'"); return -1; } status = fchmod(fileno(file), 0755); if (status < 0) note(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) { note(NULL, WARNING, SYSTEM, "can't get kernel release"); C("-k 3.17.0"); } else { C("-k '\\%s\\%s\\%s\\%s\\%s\\%s\\0\\' ", utsname.sysname, utsname.nodename, utsname.release, utsname.version, utsname.machine, utsname.domainname); } C("-i %d:%d", getuid(), getgid()); 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) { note(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) { note(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) { note(NULL, ERROR, SYSTEM, "can't readlink '/proc/self/exe'"); return status; } path[status] = '\0'; status = lstat(path, &statl); if (status < 0) { note(NULL, ERROR, INTERNAL, "can't lstat '%s'", path); return status; } location = talloc_asprintf(care, "%s/proot", care->prefix); if (location == NULL) { note(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) note(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) note(NULL, WARNING, INTERNAL, "can't archive 'concealed-accesses.txt'"); /* Generate & archive the "README.txt" file. */ status = archive_readme_txt(care); if (status < 0) note(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) note(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)) note(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, "`%2$s%1$s` or `care -x %1$s`", care->output, care->output[0] == '/' ? "" : "./"); } else if (care->output[strlen(care->output) - 1] != '/') extractor = talloc_asprintf(care, "`care -x %s`", care->output); else extractor = NULL; note(NULL, INFO, USER, "----------------------------------------------------------------------"); note(NULL, INFO, USER, "Hints:"); note(NULL, INFO, USER, " - search for \"conceal\" in `care -h` if the execution didn't go as expected."); if (extractor != NULL) note(NULL, INFO, USER, " - run %s to extract the output archive correctly.", extractor); return 0; } proot-5.1.0/src/extension/care/archive.h0000644000175000017500000000274312443566643017566 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 */ proot-5.1.0/src/extension/extension.h0000644000175000017500000001454412443566643017251 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 */ proot-5.1.0/src/compat.h0000644000175000017500000001505112443566643014476 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_NULL # define AT_NULL 0 # endif # ifndef AT_PHDR # define AT_PHDR 3 # endif # ifndef AT_PHENT # define AT_PHENT 4 # endif # ifndef AT_PHNUM # define AT_PHNUM 5 # endif # ifndef AT_BASE # define AT_BASE 7 # endif # ifndef AT_ENTRY # define AT_ENTRY 9 # endif # ifndef AT_RANDOM # define AT_RANDOM 25 # endif # ifndef AT_EXECFN # define AT_EXECFN 31 # 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_ARCH_PRCTL # define PTRACE_ARCH_PRCTL 30 # endif # ifndef ARCH_SET_GS # define ARCH_SET_GS 0x1001 # endif # ifndef ARCH_SET_FS # define ARCH_SET_FS 0x1002 # endif # ifndef ARCH_GET_GS # define ARCH_GET_FS 0x1003 # endif # ifndef ARCH_GET_FS # define ARCH_GET_GS 0x1004 # 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_NAME # define PR_SET_NAME 15 # 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_RDONLY # define O_RDONLY 00000000 # endif # ifndef O_CLOEXEC # define O_CLOEXEC 02000000 # endif # ifndef MAP_PRIVATE # define MAP_PRIVATE 0x02 # endif # ifndef MAP_FIXED # define MAP_FIXED 0x10 # endif # ifndef MAP_ANONYMOUS # define MAP_ANONYMOUS 0x20 # endif #endif /* COMPAT_H */ proot-5.1.0/src/syscall/0000755000175000017500000000000012443566643014512 5ustar ivoireivoireproot-5.1.0/src/syscall/sysnums-arm64.h0000644000175000017500000001445412443566643017343 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, }; proot-5.1.0/src/syscall/chain.h0000644000175000017500000000247512443566643015755 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 */ proot-5.1.0/src/syscall/seccomp.h0000644000175000017500000000243212443566643016315 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 */ proot-5.1.0/src/syscall/sysnums-x86_64.h0000644000175000017500000001701112443566643017340 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, }; proot-5.1.0/src/syscall/exit.c0000644000175000017500000002573312443566643015641 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 "execve/execve.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: translate_execve_exit(tracee); 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) 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); } proot-5.1.0/src/syscall/sysnums-arm.h0000644000175000017500000002006512443566643017164 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, }; proot-5.1.0/src/syscall/heap.h0000644000175000017500000000261712443566643015606 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 */ proot-5.1.0/src/syscall/socket.c0000644000175000017500000001522212443566643016150 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->ctx, "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; } proot-5.1.0/src/syscall/heap.c0000644000175000017500000001623212443566643015577 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/note.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) note(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)); } proot-5.1.0/src/syscall/syscall.h0000644000175000017500000000245212443566643016340 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, const 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 */ proot-5.1.0/src/syscall/sysnums-sh4.h0000644000175000017500000002016212443566643017101 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, }; proot-5.1.0/src/syscall/seccomp.c0000644000175000017500000003314512443566643016315 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/note.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_setxattr, 0 }, { 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) */ proot-5.1.0/src/syscall/sysnum.h0000644000175000017500000000254312443566643016225 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 */ proot-5.1.0/src/syscall/enter.c0000644000175000017500000003302512443566643015776 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_enter(tracee); 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; } proot-5.1.0/src/syscall/sysnums-x32.h0000644000175000017500000001634212443566643017024 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, }; proot-5.1.0/src/syscall/sysnum.c0000644000175000017500000000676412443566643016231 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/note.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]; } proot-5.1.0/src/syscall/syscall.c0000644000175000017500000001232712443566643016335 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, const 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, const 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"); } proot-5.1.0/src/syscall/socket.h0000644000175000017500000000217512443566643016160 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 */ proot-5.1.0/src/syscall/sysnums-i386.h0000644000175000017500000002044112443566643017074 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, }; proot-5.1.0/src/syscall/chain.c0000644000175000017500000001152012443566643015737 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, CURRENT, 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)); } proot-5.1.0/src/syscall/sysnums.list0000644000175000017500000001704512443566643017137 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) proot-5.1.0/src/cli/0000755000175000017500000000000012443566643013607 5ustar ivoireivoireproot-5.1.0/src/cli/care.c0000644000175000017500000002577712443566643014707 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/note.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, const 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, const 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, const 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, const 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, const 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, const 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, const 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, const 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 unsigned char WEAK _binary_licenses_start; extern unsigned char WEAK _binary_licenses_end; static int handle_option_V(Tracee *tracee UNUSED, const Cli *cli, const 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, const char *value) { int status = extract_archive_from_file(value); exit_failure = (status < 0); return -1; } extern unsigned char WEAK _binary_manual_start; extern unsigned char WEAK _binary_manual_end; static int handle_option_h(Tracee *tracee UNUSED, const Cli *cli UNUSED, const 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) note(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) { note(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) { note(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) note(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) { note(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) { note(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; } proot-5.1.0/src/cli/care.h0000644000175000017500000001453712443566643014704 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" #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, const char *value); static int handle_option_c(Tracee *tracee, const Cli *cli, const char *value); static int handle_option_r(Tracee *tracee, const Cli *cli, const char *value); static int handle_option_p(Tracee *tracee, const Cli *cli, const char *value); static int handle_option_e(Tracee *tracee, const Cli *cli, const char *value); static int handle_option_m(Tracee *tracee, const Cli *cli, const char *value); static int handle_option_d(Tracee *tracee, const Cli *cli, const char *value); static int handle_option_v(Tracee *tracee, const Cli *cli, const char *value); static int handle_option_V(Tracee *tracee, const Cli *cli, const char *value); static int handle_option_x(Tracee *tracee, const Cli *cli, const char *value); static int handle_option_h(Tracee *tracee, const Cli *cli, const 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 */ proot-5.1.0/src/cli/note.c0000644000175000017500000000427512443566643014730 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/note.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 note(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; } proot-5.1.0/src/cli/cli.c0000644000175000017500000003505312443566643014530 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/note.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) { note(tracee, ERROR, SYSTEM, "execve(\"%s\")", argv0); /* Ubuntu kernel bug? */ if (status == -EPERM && getenv("PROOT_NO_SECCOMP") == NULL) { note(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; } note(tracee, INFO, USER, "possible causes:\n" " * the program is a script but its interpreter (eg. /bin/sh) was not found;\n" " * the program is an ELF but its interpreter (eg. ld-linux.so) was not found;\n" " * the program is a foreign binary but qemu was not specified;\n" " * qemu does not work correctly (if specified);\n" " * the loader was not found or doesn't work."); } static void print_error_separator(const Tracee *tracee, const Argument *argument) { if (argument->separator == '\0') note(tracee, ERROR, USER, "option '%s' expects no value.", argument->name); else note(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 *const 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 note(tracee, INFO, USER, "%s", string); } static void print_config(Tracee *tracee, char *const argv[]) { assert(tracee != NULL); if (tracee->verbose <= 0) return; if (tracee->qemu) note(tracee, INFO, USER, "host rootfs = %s", HOST_ROOTFS); if (tracee->glue) note(tracee, INFO, USER, "glue rootfs = %s", tracee->glue); note(tracee, INFO, USER, "exe = %s", tracee->exe); print_argv(tracee, "argv", argv); print_argv(tracee, "qemu", tracee->qemu); note(tracee, INFO, USER, "initial cwd = %s", tracee->fs->cwd); note(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) { note(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) { note(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) { note(tracee, WARNING, USER, "can't chdir(\"%s\") in the guest rootfs: %s", path2, strerror(-status)); note(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 from @exe, i.e. canonicalize it from a * guest point-of-view. */ static int initialize_exe(Tracee *tracee, const char *exe) { char path[PATH_MAX]; int status; status = which(tracee, tracee->reconf.paths, path, exe ?: "/bin/sh"); 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 the index in @argv[] of the command * to launch, otherwise -1 if an error occured. */ static int parse_config(Tracee *tracee, size_t argc, char *const argv[]) { option_handler_t handler = NULL; const Option *options; const Cli *cli = NULL; size_t argc_offset; 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++) { const 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 -1; 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; } } note(tracee, ERROR, USER, "unknown option '%s'.", arg); return -1; known_option: if (handler != NULL && i == argc - 1) { note(tracee, ERROR, USER, "missing value for option '%s'.", arg); return -1; } } argc_offset = i; #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_exe); /* Bindings are now installed and the current working * directory is canonicalized: resolve path to @tracee->exe * and configure @tracee->cmdline. */ status = initialize_exe(tracee, argv[argc_offset]); if (status < 0) return -1; HOOK_CONFIG(post_initialize_exe); #undef HOOK_CONFIG print_config(tracee, &argv[argc_offset]); return argc_offset; } bool exit_failure = true; int main(int argc, char *const 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, &argv[status]); 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) { note(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--; } proot-5.1.0/src/cli/proot.c0000644000175000017500000001652012443566643015122 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/note.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, const 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, const char *value) { char *host; char *guest; host = talloc_strdup(tracee->ctx, value); if (host == NULL) { note(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, const char *value) { const char *ptr; size_t nb_args; bool last; 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; do { const void *start; const void *end; last = true; /* Keep consecutive non-space characters. */ start = ptr; while (*ptr != ' ' && *ptr != '\0') ptr++; end = ptr; /* End-of-string ? */ if (*ptr == '\0') goto next; /* Remove consecutive space separators. */ while (*ptr == ' ' && *ptr != '\0') ptr++; /* End-of-string ? */ if (*ptr == '\0') goto next; last = false; next: tracee->qemu[i] = talloc_strndup(tracee->qemu, start, end - start); if (tracee->qemu[i] == NULL) return -1; i++; } while (!last); 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, const 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, const char *value) { int status; status = initialize_extension(tracee, kompat_callback, value); if (status < 0) note(tracee, WARNING, INTERNAL, "option \"-k %s\" discarded", value); return 0; } static int handle_option_i(Tracee *tracee, const Cli *cli UNUSED, const char *value) { (void) initialize_extension(tracee, fake_id0_callback, value); return 0; } static int handle_option_0(Tracee *tracee, const Cli *cli, const char *value UNUSED) { return handle_option_i(tracee, cli, "0:0"); } static int handle_option_v(Tracee *tracee, const Cli *cli UNUSED, const 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 unsigned char WEAK _binary_licenses_start; extern unsigned char WEAK _binary_licenses_end; static int handle_option_V(Tracee *tracee UNUSED, const Cli *cli, const 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, const 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, const 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, const 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_exe(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; 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; } proot-5.1.0/src/cli/note.h0000644000175000017500000000277312443566643014736 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 NOTE_H #define NOTE_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)) \ note(tracee, INFO, INTERNAL, (message), ## args); \ } while (0) extern void note(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 /* NOTE_H */ proot-5.1.0/src/cli/proot.h0000644000175000017500000002565412443566643015137 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 "5.1.0" #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/", "/run/", "/var/run/dbus/system_bus_socket", /* "/var/tmp/kdecache-$LOGNAME", */ "$HOME", "*path*", NULL, }; static const char *recommended_su_bindings[] = { "/etc/host.conf", "/etc/hosts", "/etc/nsswitch.conf", "/etc/resolv.conf", "/dev/", "/sys/", "/proc/", "/tmp/", "/run/shm", "$HOME", "*path*", NULL, }; static int handle_option_r(Tracee *tracee, const Cli *cli, const char *value); static int handle_option_b(Tracee *tracee, const Cli *cli, const char *value); static int handle_option_q(Tracee *tracee, const Cli *cli, const char *value); static int handle_option_w(Tracee *tracee, const Cli *cli, const char *value); static int handle_option_v(Tracee *tracee, const Cli *cli, const char *value); static int handle_option_V(Tracee *tracee, const Cli *cli, const char *value); static int handle_option_h(Tracee *tracee, const Cli *cli, const char *value); static int handle_option_k(Tracee *tracee, const Cli *cli, const char *value); static int handle_option_0(Tracee *tracee, const Cli *cli, const char *value); static int handle_option_i(Tracee *tracee, const Cli *cli, const char *value); static int handle_option_R(Tracee *tracee, const Cli *cli, const char *value); static int handle_option_S(Tracee *tracee, const Cli *cli, const char *value); static int pre_initialize_bindings(Tracee *, const Cli *, size_t, char *const *, size_t); static int post_initialize_exe(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_exe = post_initialize_exe, .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 */ proot-5.1.0/src/cli/cli.h0000644000175000017500000000342612443566643014534 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, const 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_exe; initialization_hook_t post_initialize_exe; 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 */ proot-5.1.0/src/tracee/0000755000175000017500000000000012443566643014303 5ustar ivoireivoireproot-5.1.0/src/tracee/reg.h0000644000175000017500000000277512443566643015244 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, RTLD_FINI, STATE_FLAGS, USERARG_1, } 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 */ proot-5.1.0/src/tracee/event.h0000644000175000017500000000225012443566643015574 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, char *const argv[]); 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 */ proot-5.1.0/src/tracee/mem.c0000644000175000017500000003665312443566643015242 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/note.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) { note(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) { note(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) { note(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) { note(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) { note(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; /* This function should be called in sysenter only since the * stack pointer is systematically restored at the end of * sysexit (except for execve, but in this case the stack * pointer should be handled with care since it is used by the * process to retrieve argc, argv, envp, and auxv). */ assert(IS_IN_SYSENTER(tracee)); /* 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)) { note(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; } proot-5.1.0/src/tracee/reg.c0000644000175000017500000002406012443566643015226 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/note.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), [RTLD_FINI] = USER_REGS_OFFSET(rdx), [STATE_FLAGS] = USER_REGS_OFFSET(eflags), [USERARG_1] = USER_REGS_OFFSET(rdi), }; 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), [RTLD_FINI] = USER_REGS_OFFSET(rdx), [STATE_FLAGS] = USER_REGS_OFFSET(eflags), [USERARG_1] = USER_REGS_OFFSET(rax), }; #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]), [USERARG_1] = USER_REGS_OFFSET(uregs[0]), }; #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), [RTLD_FINI] = USER_REGS_OFFSET(edx), [STATE_FLAGS] = USER_REGS_OFFSET(eflags), [USERARG_1] = USER_REGS_OFFSET(eax), }; #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), [RTLD_FINI] = USER_REGS_OFFSET(r4), }; #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; note(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) note(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; } proot-5.1.0/src/tracee/event.c0000644000175000017500000003757512443566643015611 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/note.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" /** * Start @tracee->exe with the given @argv[]. This function * returns -errno if an error occurred, otherwise 0. */ int launch_process(Tracee *tracee, char *const argv[]) { char *const default_argv[] = { "-sh", NULL }; 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: note(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) { note(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, argv[0] != NULL ? argv : default_argv); 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) { note(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; note(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; note(tracee, INFO, USER, "Get a 64-bit version that supports 32-bit binaries here: " "http://static.proot.me/proot-x86_64"); } /** * 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) note(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) note(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) note(NULL, WARNING, SYSTEM, "sigaction(%d)", signum); } while (1) { int tracee_status; Tracee *tracee; int signal; pid_t pid; /* This is the only safe place to free tracees. */ free_terminated_tracees(); /* Wait for the next tracee's stop. */ pid = waitpid(-1, &tracee_status, __WALL); if (pid < 0) { if (errno != ECHILD) { note(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); tracee->terminated = true; } 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)); tracee->terminated = true; } 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) { note(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) return false; /* The process likely died in a syscall. */ tracee->restart_how = 0; tracee->running = true; return true; } proot-5.1.0/src/tracee/tracee.h0000644000175000017500000002001512443566643015715 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 load_info; struct extensions; 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; /* Is this tracee ready to be freed? TODO: move to a list * dedicated to terminated tracees instead. */ bool terminated; /* 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; 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_loader_syscalls; bool ignore_syscalls; word_t options; bool is_zombie; } 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; /* Load info generated during execve sysenter and used during * execve sysexit. */ struct load_info *load_info; /********************************************************************** * 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; char *new_exe; /********************************************************************** * Shared or private resources, depending on the (re-)configuration * **********************************************************************/ /* Runner command-line. */ char **qemu; /* 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 void free_terminated_tracees(); extern int swap_config(Tracee *tracee1, Tracee *tracee2); extern void kill_all_tracees(); #endif /* TRACEE_H */ proot-5.1.0/src/tracee/tracee.c0000644000175000017500000004207512443566643015722 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/note.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) { 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 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; /* 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->parent = tracee->parent; zombie->clone = tracee->clone; zombie->pid = tracee->pid; detach_from_ptracer(tracee); attach_to_ptracer(zombie, ptracer); zombie->as_ptracee.event4.ptracer.pending = true; zombie->as_ptracee.event4.ptracer.value = event; zombie->as_ptracee.is_zombie = true; return 0; } /* Fallback to the common path. */ } detach_from_ptracer(tracee); /* Wake its ptracer if there's nothing else to wait for. */ 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. */ (void) push_regs(ptracer); 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); } /** * Free all tracees marked as terminated. */ void free_terminated_tracees() { Tracee *next; /* Items can't be deleted when using LIST_FOREACH. */ next = tracees.lh_first; while (next != NULL) { Tracee *tracee = next; next = tracee->link.le_next; if (tracee->terminated) TALLOC_FREE(tracee); } } /** * 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) { note(parent, WARNING, SYSTEM, "ptrace(GETEVENTMSG)"); return status; } child = get_tracee(parent, (pid_t) pid, true); if (child == NULL) { note(parent, WARNING, SYSTEM, "running out of memory"); return -ENOMEM; } /* Sanity checks. */ assert(child != NULL && child->exe == 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 */ if ((clone_flags & CLONE_PARENT) != 0) child->parent = parent->parent; else child->parent = parent; /* Remember if this child belongs to the same thread group as * its parent. This is currently useful for ptrace emulation * only but it deserves to be extended to support execve(2) * specificity (ie. when a thread calls execve(2), its pid * gets replaced by the pid of its thread group leader). */ child->clone = ((clone_flags & CLONE_THREAD) != 0); /* 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)) { attach_to_ptracer(child, parent->as_ptracee.ptracer); /* 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 is unshared only once the child * process does a call to execve(2). */ child->exe = talloc_reference(child, parent->exe); 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; #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(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); } proot-5.1.0/src/tracee/mem.h0000644000175000017500000000724012443566643015235 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 */ proot-5.1.0/src/tracee/abi.h0000644000175000017500000000614012443566643015210 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[CURRENT].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 */ proot-5.1.0/src/arch.h0000644000175000017500000001144112443566643014127 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 ARCH_H #define ARCH_H #ifndef NO_LIBC_HEADER #include /* linux.git:c0a3a20b */ #include /* AUDIT_ARCH_*, */ #endif typedef unsigned long word_t; typedef unsigned char byte_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 #define LOADER_ADDRESS 0x600000000000 #define HAS_LOADER_32BIT true #define EXEC_PIC_ADDRESS 0x500000000000 #define INTERP_PIC_ADDRESS 0x6f0000000000 #define EXEC_PIC_ADDRESS_32 0x0f000000 #define INTERP_PIC_ADDRESS_32 0xaf000000 #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 #define LOADER_ADDRESS 0x10000000 #define EXEC_PIC_ADDRESS 0x0f000000 #define INTERP_PIC_ADDRESS 0x1f000000 /* 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 #define LOADER_ADDRESS 0xa0000000 #define LOADER_ARCH_CFLAGS -mregparm=3 #define EXEC_PIC_ADDRESS 0x0f000000 #define INTERP_PIC_ADDRESS 0xaf000000 #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 */ proot-5.1.0/src/.check_seccomp_filter.c0000644000175000017500000000171612443566643017422 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; } proot-5.1.0/src/path/0000755000175000017500000000000012443566643013774 5ustar ivoireivoireproot-5.1.0/src/path/canon.h0000644000175000017500000000213712443566643015246 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 */ proot-5.1.0/src/path/canon.c0000644000175000017500000002423312443566643015242 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" /** * Put an end-of-string ('\0') right before the last component of @path. */ static inline 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 @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. */ static inline 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; } /** * 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; } proot-5.1.0/src/path/binding.h0000644000175000017500000000371512443566643015565 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 */ proot-5.1.0/src/path/path.h0000644000175000017500000000570712443566643015112 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], const char *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 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 */ proot-5.1.0/src/path/proc.h0000644000175000017500000000303212443566643015106 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 */ proot-5.1.0/src/path/temp.c0000644000175000017500000001516512443566643015115 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/note.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) { note(NULL, WARNING, SYSTEM, "can't readlink '/proc/self/cwd'"); return ++nb_errors; } if (strncmp(prefix, P_tmpdir, length_tmpdir) != 0) { note(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) { note(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) { note(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) { note(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) { note(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) { note(NULL, WARNING, SYSTEM, "can't remove '%s'", entry->d_name); nb_errors++; continue; } } if (errno != 0) { note(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) { note(NULL, ERROR, SYSTEM, "can't chmod '%s'", path); result = -1; goto end; } status = chdir(path); if (status < 0) { note(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) { note(NULL, ERROR, SYSTEM, "can't chdir to '..'"); result = -1; goto end; } status = rmdir(path); if (status < 0) { note(NULL, ERROR, SYSTEM, "cant remove '%s'", path); result = -1; goto end; } end: if (cwd != NULL) { status = chdir(cwd); if (status < 0) { result = -1; note(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) note(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 @context is NULL. This function returns NULL if an * error occurred. */ char *create_temp_name(TALLOC_CTX *context, const char *prefix) { char *name; if (context == NULL) context = talloc_autofree_context(); name = talloc_asprintf(context, "%s/%s-%d-XXXXXX", P_tmpdir, prefix, getpid()); if (name == NULL) { note(NULL, ERROR, INTERNAL, "can't allocate memory"); return NULL; } return name; } /** * Create a directory that will be automatically removed either on * PRoot termination if @context is NULL, or once its path name * (attached to @context) is freed. This function returns NULL on * error, otherwise the absolute path name to the created directory * (@prefix-ed). */ const char *create_temp_directory(TALLOC_CTX *context, const char *prefix) { char *name; name = create_temp_name(context, prefix); if (name == NULL) return NULL; name = mkdtemp(name); if (name == NULL) { note(NULL, 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 @context is NULL, or once its path name (attached to * @context) is freed. This function returns NULL on error, * otherwise the absolute path name to the created file (@prefix-ed). */ const char *create_temp_file(TALLOC_CTX *context, const char *prefix) { char *name; int fd; name = create_temp_name(context, prefix); if (name == NULL) return NULL; fd = mkstemp(name); if (fd < 0) { note(NULL, 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(TALLOC_CTX *context, const char *prefix) { char *name; FILE *file; int fd; name = create_temp_name(context, 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); note(NULL, ERROR, SYSTEM, "can't create temporary file"); return NULL; } proot-5.1.0/src/path/path.c0000644000175000017500000004571312443566643015106 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/note.h" #include "build.h" #include "compat.h" /** * 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], const char *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)) { note(tracee, ERROR, USER, "'%s' is not a regular file", command); return -EACCES; } if (is_explicit && (statr.st_mode & S_IXUSR) == 0) { note(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, ""); note(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) note(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; } proot-5.1.0/src/path/glue.c0000644000175000017500000001323112443566643015074 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/note.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) return 0; /* Not fatal. */ if (!S_ISDIR(statl.st_mode)) { if (statl.st_size != 0) return 0; /* Not fatal. */ status = unlink(path); } else status = rmdir(path); if (status) 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) { note(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) { note(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) { note(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; } proot-5.1.0/src/path/binding.c0000644000175000017500000005107612443566643015563 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/note.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) note(tracee, INFO, USER, "binding = %s", binding->host.path); else note(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) { note(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) note(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) { note(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) { note(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) { note(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) { note(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; } proot-5.1.0/src/path/temp.h0000644000175000017500000000231712443566643015115 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 extern char *create_temp_name(TALLOC_CTX *context, const char *prefix); extern const char *create_temp_directory(TALLOC_CTX *context, const char *prefix); extern const char *create_temp_file(TALLOC_CTX *context, const char *prefix); extern FILE* open_temp_file(TALLOC_CTX *context, const char *prefix); #endif /* TEMP_H */ proot-5.1.0/src/path/proc.c0000644000175000017500000001301212443566643015100 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); } proot-5.1.0/src/path/glue.h0000644000175000017500000000212012443566643015074 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 */ proot-5.1.0/src/GNUmakefile0000644000175000017500000001450112443566643015113 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))) SRC = $(dir $(firstword $(MAKEFILE_LIST))) GIT = git RM = rm INSTALL = install CC = $(CROSS_COMPILE)gcc LD = $(CC) STRIP = $(CROSS_COMPILE)strip OBJCOPY = $(CROSS_COMPILE)objcopy OBJDUMP = $(CROSS_COMPILE)objdump 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/note.o \ execve/enter.o \ execve/exit.o \ execve/shebang.o \ execve/elf.o \ execve/ldso.o \ execve/auxv.o \ execve/aoxp.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/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 \ loader/loader-wrapped.o define define_from_arch.h $2$1 := $(shell $(CC) $1 -E -dM -DNO_LIBC_HEADER $(SRC)/arch.h | grep -w $2 | cut -f 3 -d ' ') endef $(eval $(call define_from_arch.h,,HAS_LOADER_32BIT)) ifdef HAS_LOADER_32BIT OBJECTS += loader/loader-m32-wrapped.o endif 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) .check_%: .check_%.o -$(LINK:echo=false) $(silently) .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_ID_NONE := $(shell if ld --build-id=none --version >/dev/null 2>&1; then echo ',--build-id=none'; fi) ###################################################################### # Build rules COMPILE = $($(quiet)CC) $(CPPFLAGS) $(CFLAGS) -MD -c $(SRC)$< -o $@ LINK = $($(quiet)LD) -o $@ $^ $(LDFLAGS) OBJIFY = $($(quiet)GEN) \ $(OBJCOPY) \ --input binary \ --output `env LANG=C $(OBJDUMP) -f cli/cli.o | \ grep 'file format' | awk '{print $$4}'` \ --binary-architecture `env LANG=C $(OBJDUMP) -f cli/cli.o | \ grep architecture | cut -f 1 -d , | awk '{print $$2}'` \ $< $@ 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)%.c,%.o,$(shell egrep -sl 'include[[:space:]]+"build.h"' $(patsubst %.o,$(SRC)%.c,$(OBJECTS) $(CARE_OBJECTS)))) $(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) ###################################################################### # Build rules for the loader define build_loader LOADER$1_OBJECTS = loader/loader$1.o loader/assembly$1.o $(eval $(call define_from_arch.h,$1,LOADER_ARCH_CFLAGS)) $(eval $(call define_from_arch.h,$1,LOADER_ADDRESS)) LOADER_CFLAGS$1 += -fPIC -ffreestanding $(LOADER_ARCH_CFLAGS$1) LOADER_LDFLAGS$1 += -static -nostdlib -Wl$(BUILD_ID_NONE),-Ttext=$(LOADER_ADDRESS$1) loader/loader$1.o: loader/loader.c @mkdir -p $$(dir $$@) $$(COMPILE) $1 $$(LOADER_CFLAGS$1) loader/assembly$1.o: loader/assembly.S @mkdir -p $$(dir $$@) $$(COMPILE) $1 $$(LOADER_CFLAGS$1) loader/loader$1: $$(LOADER$1_OBJECTS) $$($$(quiet)LD) $1 -o $$@ $$^ $$(LOADER_LDFLAGS$1) .INTERMEDIATE: loader$1.exe loader$1.exe: loader/loader$1 $$(Q)cp $$< $$@ $$(Q)$(STRIP) $$@ loader/loader$1-wrapped.o: loader$1.exe cli/cli.o $$(OBJIFY) endef $(eval $(build_loader)) ifdef HAS_LOADER_32BIT $(eval $(call build_loader,-m32)) endif ###################################################################### # Dependencies .DELETE_ON_ERROR: $(OBJECTS) $(CARE_OBJECTS) $(LOADER_OBJECTS) $(LOADER-m32_OBJECTS): $(firstword $(MAKEFILE_LIST)) DEPS = $(OBJECTS:.o=.d) $(CARE_OBJECTS:.o=.d) $(LOADER_OBJECTS:.o=.d) $(LOADER-m32_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) $(LOADER_OBJECTS) $(LOADER-m32_OBJECTS) proot care loader/loader loader/loader-m32 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 proot-5.1.0/src/execve/0000755000175000017500000000000012443566643014317 5ustar ivoireivoireproot-5.1.0/src/execve/aoxp.h0000644000175000017500000000604312443566643015442 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 AOXP_H #define AOXP_H #include #include "tracee/reg.h" #include "arch.h" typedef struct array_of_xpointers ArrayOfXPointers; typedef int (*read_xpointee_t)(ArrayOfXPointers *array, size_t index, void **object); typedef int (*write_xpointee_t)(ArrayOfXPointers *array, size_t index, const void *object); typedef int (*compare_xpointee_t)(ArrayOfXPointers *array, size_t index, const void *reference); typedef int (*sizeof_xpointee_t)(ArrayOfXPointers *array, size_t index); typedef struct mixed_pointer XPointer; struct array_of_xpointers { XPointer *_xpointers; size_t length; read_xpointee_t read_xpointee; write_xpointee_t write_xpointee; compare_xpointee_t compare_xpointee; sizeof_xpointee_t sizeof_xpointee; }; static inline int read_xpointee(ArrayOfXPointers *array, size_t index, void **object) { return array->read_xpointee(array, index, object); } static inline int write_xpointee(ArrayOfXPointers *array, size_t index, const void *object) { return array->write_xpointee(array, index, object); } static inline int compare_xpointee(ArrayOfXPointers *array, size_t index, const void *reference) { return array->compare_xpointee(array, index, reference); } static inline int sizeof_xpointee(ArrayOfXPointers *array, size_t index) { return array->sizeof_xpointee(array, index); } extern int find_xpointee(ArrayOfXPointers *array, const void *reference); extern int resize_array_of_xpointers(ArrayOfXPointers *array, size_t index, ssize_t nb_delta_entries); extern int fetch_array_of_xpointers(Tracee *tracee, ArrayOfXPointers **array, Reg reg, size_t nb_entries); extern int push_array_of_xpointers(ArrayOfXPointers *array, Reg reg); extern int read_xpointee_as_object(ArrayOfXPointers *array, size_t index, void **object); extern int read_xpointee_as_string(ArrayOfXPointers *array, size_t index, char **string); extern int write_xpointee_as_string(ArrayOfXPointers *array, size_t index, const char *string); extern int write_xpointees(ArrayOfXPointers *array, size_t index, size_t nb_xpointees, ...); extern int compare_xpointee_generic(ArrayOfXPointers *array, size_t index, const void *reference); extern int sizeof_xpointee_as_string(ArrayOfXPointers *array, size_t index); #endif /* AOXP_H */ proot-5.1.0/src/execve/shebang.c0000644000175000017500000001723312443566643016100 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 /* open(2), */ #include /* open(2), */ #include /* PATH_MAX, */ #include /* BINPRM_BUF_SIZE, */ #include /* read(2), close(2), */ #include /* -E*, */ #include /* MAXSYMLINKS, */ #include /* bool, */ #include /* assert(3), */ #include "execve/shebang.h" #include "execve/execve.h" #include "execve/aoxp.h" #include "tracee/tracee.h" #include "attribute.h" /** * Extract into @user_path and @argument the shebang from @host_path. * 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. */ static int extract_shebang(const Tracee *tracee UNUSED, const char *host_path, char user_path[PATH_MAX], char argument[BINPRM_BUF_SIZE]) { char tmp2[2]; char tmp; size_t current_length; size_t i; int status; int fd; /* Assumption. */ assert(BINPRM_BUF_SIZE < PATH_MAX); argument[0] = '\0'; /* Inspect the executable. */ fd = open(host_path, O_RDONLY); if (fd < 0) return -errno; status = read(fd, tmp2, 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 (tmp2[0] != '#' || tmp2[1] != '!') { status = 0; goto end; } current_length = 2; user_path[0] = '\0'; /* 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 = -ENOEXEC; goto end; } current_length++; } while ((tmp == ' ' || tmp == '\t') && current_length < BINPRM_BUF_SIZE); /* Slurp the interpreter path until the first space or end-of-line. */ for (i = 0; current_length < BINPRM_BUF_SIZE; current_length++, i++) { switch (tmp) { case ' ': case '\t': /* Remove spaces in between the interpreter * and the hypothetical argument. */ user_path[i] = '\0'; break; case '\n': case '\r': /* There is no argument. */ user_path[i] = '\0'; argument[0] = '\0'; status = 1; goto end; default: /* There is an argument if the previous * character in user_path[] is '\0'. */ if (i > 1 && user_path[i - 1] == '\0') goto argument; else user_path[i] = tmp; break; } status = read(fd, &tmp, sizeof(char)); if (status < 0) { status = -errno; goto end; } if ((size_t) status < sizeof(char)) { /* EOF */ user_path[i] = '\0'; argument[0] = '\0'; status = 1; goto end; } } /* The interpreter path is too long, truncate it. */ user_path[i] = '\0'; argument[0] = '\0'; status = 1; goto end; argument: /* Slurp the argument until the end-of-line. */ for (i = 0; current_length < BINPRM_BUF_SIZE; current_length++, 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, truncate it. */ argument[i] = '\0'; status = 1; end: close(fd); /* Did an error occur or isn't a script? */ if (status <= 0) return status; return 1; } /** * Expand in argv[] the shebang of @user_path, if any. This function * returns -errno if an error occurred, 1 if a shebang was found and * extracted, otherwise 0. On success, both @host_path and @user_path * point to the program to execute (respectively from host * point-of-view and as-is), and @tracee's argv[] (pointed to by * SYSARG_2) is correctly updated. */ int expand_shebang(Tracee *tracee, char host_path[PATH_MAX], char user_path[PATH_MAX]) { ArrayOfXPointers *argv = NULL; bool has_shebang = false; char argument[BINPRM_BUF_SIZE]; int status; size_t i; /* "The interpreter must be a valid pathname for an executable * which is not itself a script [1]. 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()." -- man 2 execve * * [1]: as of this writing (3.10.17) this is true only for the * ELF interpreter; ie. a script can use a script as * interpreter. */ for (i = 0; i < MAXSYMLINKS; i++) { char *old_user_path; /* Translate this path (user -> host), then check it is executable. */ status = translate_and_check_exec(tracee, host_path, user_path); if (status < 0) return status; /* Remember the initial user path. */ old_user_path = talloc_strdup(tracee->ctx, user_path); if (old_user_path == NULL) return -ENOMEM; /* Extract into user_path and argument the shebang from host_path. */ status = extract_shebang(tracee, host_path, user_path, argument); if (status < 0) return status; /* No more shebang. */ if (status == 0) break; has_shebang = true; /* Translate new path (user -> host), then check it is executable. */ status = translate_and_check_exec(tracee, host_path, user_path); if (status < 0) return status; /* Fetch argv[] only on demand. */ if (argv == NULL) { status = fetch_array_of_xpointers(tracee, &argv, SYSARG_2, 0); if (status < 0) return status; } /* Assuming the shebang of "script" is "#!/bin/sh -x", * a call to: * * execve("./script", { "script.sh", NULL }, ...) * * becomes: * * execve("/bin/sh", { "/bin/sh", "-x", "./script", NULL }, ...) * * See commit 8c8fbe85 about "argv->length == 1". */ if (argument[0] != '\0') { status = resize_array_of_xpointers(argv, 0, 2 + (argv->length == 1)); if (status < 0) return status; status = write_xpointees(argv, 0, 3, user_path, argument, old_user_path); if (status < 0) return status; } else { status = resize_array_of_xpointers(argv, 0, 1 + (argv->length == 1)); if (status < 0) return status; status = write_xpointees(argv, 0, 2, user_path, old_user_path); if (status < 0) return status; } } if (i == MAXSYMLINKS) return -ELOOP; /* Push argv[] only on demand. */ if (argv != NULL) { status = push_array_of_xpointers(argv, SYSARG_2); if (status < 0) return status; } return (has_shebang ? 1 : 0); } proot-5.1.0/src/execve/exit.c0000644000175000017500000003243612443566643015444 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 /* talloc*, */ #include /* MAP_*, */ #include /* assert(3), */ #include /* strlen(3), strerror(3), */ #include /* bzero(3), */ #include /* kill(2), SIG*, */ #include /* write(2), */ #include /* E*, */ #include "execve/execve.h" #include "execve/elf.h" #include "loader/script.h" #include "tracee/reg.h" #include "tracee/abi.h" #include "tracee/mem.h" #include "syscall/sysnum.h" #include "execve/auxv.h" #include "path/binding.h" #include "path/temp.h" #include "cli/note.h" /** * 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; } /** * Bind content of @vectors over /proc/{@ptracee->pid}/auxv. This * function returns -1 if an error occurred, otherwise 0. */ static int bind_proc_pid_auxv(const Tracee *ptracee) { word_t vectors_address; ElfAuxVector *vectors; const char *guest_path; const char *host_path; Binding *binding; int status; vectors_address = get_elf_aux_vectors_address(ptracee); if (vectors_address == 0) return -1; vectors = fetch_elf_aux_vectors(ptracee, vectors_address); 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->ctx, "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; } /** * Convert @mappings into load @script statements at the given @cursor * position. This function returns the new cursor position. */ static void *transcript_mappings(void *cursor, const Mapping *mappings) { size_t nb_mappings; size_t i; nb_mappings = talloc_array_length(mappings); for (i = 0; i < nb_mappings; i++) { LoadStatement *statement = cursor; if ((mappings[i].flags & MAP_ANONYMOUS) != 0) statement->action = LOAD_ACTION_MMAP_ANON; else statement->action = LOAD_ACTION_MMAP_FILE; statement->mmap.addr = mappings[i].addr; statement->mmap.length = mappings[i].length; statement->mmap.prot = mappings[i].prot; statement->mmap.offset = mappings[i].offset; statement->mmap.clear_length = mappings[i].clear_length; cursor += LOAD_STATEMENT_SIZE(*statement, mmap); } return cursor; } /** * Convert @tracee->load_info into a load script, then transfer this * latter into @tracee's memory. */ static int transfer_load_script(Tracee *tracee) { const word_t stack_pointer = peek_reg(tracee, CURRENT, STACK_POINTER); word_t entry_point; size_t script_size; size_t strings_size; size_t string1_size; size_t string2_size; size_t string3_size; size_t padding_size; word_t string1_address; word_t string2_address; word_t string3_address; void *buffer; size_t buffer_size; LoadStatement *statement; void *cursor; int status; /* Strings addresses are required to generate the load script, * for "open" actions. Since I want to generate it in one * pass, these strings will be put right below the current * stack pointer -- the only known adresses so far -- in the * "strings area". */ string1_size = strlen(tracee->load_info->user_path) + 1; string2_size = (tracee->load_info->interp == NULL ? 0 : strlen(tracee->load_info->interp->user_path) + 1); string3_size = (tracee->load_info->raw_path == tracee->load_info->user_path ? 0 : strlen(tracee->load_info->raw_path) + 1); /* A padding will be appended at the end of the load script * (a.k.a "strings area") to ensure this latter is aligned on * a word boundary, for sake of performance. */ padding_size = (stack_pointer - string1_size - string2_size - string3_size) % sizeof_word(tracee); strings_size = string1_size + string2_size + string3_size + padding_size; string1_address = stack_pointer - strings_size; string2_address = stack_pointer - strings_size + string1_size; string3_address = (string3_size == 0 ? string1_address : stack_pointer - strings_size + string1_size + string2_size); /* Compute the size of the load script. */ script_size = LOAD_STATEMENT_SIZE(*statement, open) + (LOAD_STATEMENT_SIZE(*statement, mmap) * talloc_array_length(tracee->load_info->mappings)) + (tracee->load_info->interp == NULL ? 0 : LOAD_STATEMENT_SIZE(*statement, open) + (LOAD_STATEMENT_SIZE(*statement, mmap) * talloc_array_length(tracee->load_info->interp->mappings))) + LOAD_STATEMENT_SIZE(*statement, start); /* Allocate enough room for both the load script and the * strings area. */ buffer_size = script_size + strings_size; buffer = talloc_zero_size(tracee->ctx, buffer_size); if (buffer == NULL) return -ENOMEM; cursor = buffer; /* Load script statement: open. */ statement = cursor; statement->action = LOAD_ACTION_OPEN; statement->open.string_address = string1_address; cursor += LOAD_STATEMENT_SIZE(*statement, open); /* Load script statements: mmap. */ cursor = transcript_mappings(cursor, tracee->load_info->mappings); if (tracee->load_info->interp != NULL) { /* Load script statement: open. */ statement = cursor; statement->action = LOAD_ACTION_OPEN_NEXT; statement->open.string_address = string2_address; cursor += LOAD_STATEMENT_SIZE(*statement, open); /* Load script statements: mmap. */ cursor = transcript_mappings(cursor, tracee->load_info->interp->mappings); entry_point = ELF_FIELD(tracee->load_info->interp->elf_header, entry); } else entry_point = ELF_FIELD(tracee->load_info->elf_header, entry); /* Load script statement: start. */ statement = cursor; /* Start of the program slightly differs when ptraced. */ if (tracee->as_ptracee.ptracer != NULL) statement->action = LOAD_ACTION_START_TRACED; else statement->action = LOAD_ACTION_START; statement->start.stack_pointer = stack_pointer; statement->start.entry_point = entry_point; statement->start.at_phent = ELF_FIELD(tracee->load_info->elf_header, phentsize); statement->start.at_phnum = ELF_FIELD(tracee->load_info->elf_header, phnum); statement->start.at_entry = ELF_FIELD(tracee->load_info->elf_header, entry); statement->start.at_phdr = ELF_FIELD(tracee->load_info->elf_header, phoff) + tracee->load_info->mappings[0].addr; statement->start.at_execfn = string3_address; cursor += LOAD_STATEMENT_SIZE(*statement, start); /* Sanity check. */ assert((uintptr_t) cursor - (uintptr_t) buffer == script_size); /* Convert the load script to the expected format. */ if (is_32on64_mode(tracee)) { int i; for (i = 0; buffer + i * sizeof(uint64_t) < cursor; i++) ((uint32_t *) buffer)[i] = ((uint64_t *) buffer)[i]; } /* Concatenate the load script and the strings. */ memcpy(cursor, tracee->load_info->user_path, string1_size); cursor += string1_size; if (string2_size != 0) { memcpy(cursor, tracee->load_info->interp->user_path, string2_size); cursor += string2_size; } if (string3_size != 0) { memcpy(cursor, tracee->load_info->raw_path, string3_size); cursor += string3_size; } /* Sanity check. */ cursor += padding_size; assert((uintptr_t) cursor - (uintptr_t) buffer == buffer_size); /* Allocate enough room in tracee's memory for the load * script, and make the first user argument points to this * location. Note that it is safe to update the stack pointer * manually since we are in execve sysexit. However it should * be done before transfering data since the kernel might not * allow page faults below the stack pointer. */ poke_reg(tracee, STACK_POINTER, stack_pointer - buffer_size); poke_reg(tracee, USERARG_1, stack_pointer - buffer_size); /* Copy everything in the tracee's memory at once. */ status = write_data(tracee, stack_pointer - buffer_size, buffer, buffer_size); if (status < 0) return status; /* Tracee's stack content is now as follow: * * +------------+ <- initial stack pointer (higher address) * | padding | * +------------+ * | string3 | * +------------+ * | string2 | * +------------+ * | string1 | * +------------+ * | start | * +------------+ * | mmap anon | * +------------+ * | mmap file | * +------------+ * | open next | * +------------+ * | mmap anon. | * +------------+ * | mmap file | * +------------+ * | open | * +------------+ <- stack pointer, sysarg1 (word aligned) */ /* Remember we are in the sysexit stage, so be sure the * current register values will be used as-is at the end. */ save_current_regs(tracee, ORIGINAL); tracee->_regs_were_changed = true; return 0; } /** * Start the loading of @tracee. This function returns no error since * it's either too late to do anything useful (the calling process is * already replaced) or the error reported by the kernel * (syscall_result < 0) will be propagated as-is. */ void translate_execve_exit(Tracee *tracee) { word_t syscall_result; int status; if (IS_NOTIFICATION_PTRACED_LOAD_DONE(tracee)) { /* Be sure not to confuse the ptracer with an * unexpected syscall/returned value. */ poke_reg(tracee, SYSARG_RESULT, 0); set_sysnum(tracee, PR_execve); /* According to most ABIs, all registers have * undefined values at program startup except: * * - the stack pointer * - the instruction pointer * - the rtld_fini pointer * - the state flags */ poke_reg(tracee, STACK_POINTER, peek_reg(tracee, ORIGINAL, SYSARG_2)); poke_reg(tracee, INSTR_POINTER, peek_reg(tracee, ORIGINAL, SYSARG_3)); poke_reg(tracee, RTLD_FINI, 0); poke_reg(tracee, STATE_FLAGS, 0); /* Restore registers with their current values. */ save_current_regs(tracee, ORIGINAL); tracee->_regs_were_changed = true; /* This is is required to make GDB work correctly * under PRoot, however it deserves to be used * unconditionally. */ (void) bind_proc_pid_auxv(tracee); /* 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 * * This signal is delayed so far since the program was * not fully loaded yet; GDB would get "invalid * adress" errors otherwise. */ if ((tracee->as_ptracee.options & PTRACE_O_TRACEEXEC) == 0) kill(tracee->pid, SIGTRAP); return; } syscall_result = peek_reg(tracee, CURRENT, SYSARG_RESULT); if ((int) syscall_result < 0) return; /* Execve happened; commit the new "/proc/self/exe". */ if (tracee->new_exe != NULL) { (void) talloc_unlink(tracee, tracee->exe); tracee->exe = talloc_reference(tracee, tracee->new_exe); talloc_set_name_const(tracee->exe, "$exe"); } /* New processes have no heap. */ bzero(tracee->heap, sizeof(Heap)); /* Transfer the load script to the loader. */ status = transfer_load_script(tracee); if (status < 0) note(tracee, ERROR, INTERNAL, "can't transfer load script: %s", strerror(-status)); return; } proot-5.1.0/src/execve/aoxp.c0000644000175000017500000002657312443566643015447 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" #include "tracee/tracee.h" #include "tracee/mem.h" #include "tracee/abi.h" #include "build.h" struct mixed_pointer { /* Pointer -- in tracee's address space -- to the current * object, if local == NULL. */ word_t remote; /* Pointer -- in tracer's address space -- to the current * object, if local != NULL. */ void *local; }; #include "execve/aoxp.h" /** * Read object pointed to by @array[@index] from tracee's memory, then * make @local_pointer points to the locally *cached* version. This * function returns -errno when an error occured, otherwise 0. */ int read_xpointee_as_object(ArrayOfXPointers *array, size_t index, void **local_pointer) { int status; int size; assert(index < array->length); /* Already cached locally? */ if (array->_xpointers[index].local != NULL) goto end; /* Remote NULL is mapped to local NULL. */ if (array->_xpointers[index].remote == 0) { array->_xpointers[index].local = NULL; goto end; } size = sizeof_xpointee(array, index); if (size < 0) return size; array->_xpointers[index].local = talloc_size(array, size); if (array->_xpointers[index].local == NULL) return -ENOMEM; /* Copy locally the remote object. */ status = read_data(TRACEE(array), array->_xpointers[index].local, array->_xpointers[index].remote, size); if (status < 0) { array->_xpointers[index].local = NULL; return status; } end: *local_pointer = array->_xpointers[index].local; return 0; } /** * Read string pointed to by @array[@index] from tracee's memory, then * make @local_pointer points to the locally *cached* version. This * function returns -errno when an error occured, otherwise 0. */ int read_xpointee_as_string(ArrayOfXPointers *array, size_t index, char **local_pointer) { char tmp[ARG_MAX]; int status; assert(index < array->length); /* Already cached locally? */ if (array->_xpointers[index].local != NULL) goto end; /* Remote NULL is mapped to local NULL. */ if (array->_xpointers[index].remote == 0) { array->_xpointers[index].local = NULL; goto end; } /* Copy locally the remote string into a temporary buffer. */ status = read_string(TRACEE(array), tmp, array->_xpointers[index].remote, ARG_MAX); if (status < 0) return status; if (status >= ARG_MAX) return -ENOMEM; /* Save the local string in a "persistent" buffer. */ array->_xpointers[index].local = talloc_strdup(array, tmp); if (array->_xpointers[index].local == NULL) return -ENOMEM; end: *local_pointer = array->_xpointers[index].local; return 0; } /** * This function returns the number of bytes of the string pointed to * by @array[@index], otherwise -errno if an error occured. */ int sizeof_xpointee_as_string(ArrayOfXPointers *array, size_t index) { char *string; int status; assert(index < array->length); status = read_xpointee_as_string(array, index, &string); if (status < 0) return status; if (string == NULL) return 0; return strlen(string) + 1; } /** * Compare object pointed to by @array[@index] with object pointed to * by @local_reference. This function returns 1 if they are * equivalent, 0 otherwise. On error, -errno is returned. */ int compare_xpointee_generic(ArrayOfXPointers *array, size_t index, const void *local_reference) { void *object; int status; assert(index < array->length); status = read_xpointee(array, index, &object); if (status < 0) return status; if (object == NULL && local_reference == NULL) return 1; if (object == NULL && local_reference != NULL) return 0; if (object != NULL && local_reference == NULL) return 0; status = sizeof_xpointee(array, index); if (status < 0) return status; return (int) (memcmp(object, local_reference, status) == 0); } /** * This function returns the index in @array of the first pointee * equivalent to the @local_reference pointee, otherwise it returns * -errno if an error occured. */ int find_xpointee(ArrayOfXPointers *array, const void *local_reference) { size_t i; for (i = 0; i < array->length; i++) { int status; status = compare_xpointee(array, i, local_reference); if (status < 0) return status; if (status != 0) break; } return i; } /** * Make @array[@index] points to a copy of the string pointed to by * @string. This function returns -errno when an error occured, * otherwise 0. */ int write_xpointee_as_string(ArrayOfXPointers *array, size_t index, const char *string) { assert(index < array->length); array->_xpointers[index].local = talloc_strdup(array, string); if (array->_xpointers[index].local == NULL) return -ENOMEM; return 0; } /** * Make @array[@index ... @index + @nb_xpointees] points to a copy of * the variadic arguments. This function returns -errno when an error * occured, otherwise 0. */ int write_xpointees(ArrayOfXPointers *array, size_t index, size_t nb_xpointees, ...) { va_list va_xpointees; int status; size_t i; va_start(va_xpointees, nb_xpointees); for (i = 0; i < nb_xpointees; i++) { void *object = va_arg(va_xpointees, void *); status = write_xpointee(array, index + i, object); if (status < 0) goto end; } status = 0; end: va_end(va_xpointees); 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_of_xpointers(ArrayOfXPointers *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->_xpointers, XPointer, new_length); if (tmp == NULL) return -ENOMEM; array->_xpointers = tmp; memmove(array->_xpointers + index + delta_nb_entries, array->_xpointers + index, nb_moved_entries * sizeof(XPointer)); bzero(array->_xpointers + index, delta_nb_entries * sizeof(XPointer)); } else { assert(delta_nb_entries <= 0); assert(index >= (size_t) -delta_nb_entries); memmove(array->_xpointers + index + delta_nb_entries, array->_xpointers + index, nb_moved_entries * sizeof(XPointer)); tmp = talloc_realloc(array, array->_xpointers, XPointer, new_length); if (tmp == NULL) return -ENOMEM; array->_xpointers = tmp; } array->length = new_length; return 0; } /** * Copy into *@array_ the pointer array pointed to by @reg from * @tracee's memory space. 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_of_xpointers(Tracee *tracee, ArrayOfXPointers **array_, Reg reg, size_t nb_entries) { word_t pointer = 1; /* ie. != 0 */ word_t address; ArrayOfXPointers *array; size_t i; assert(array_ != NULL); *array_ = talloc_zero(tracee->ctx, ArrayOfXPointers); 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->_xpointers, XPointer, i + 1); if (tmp == NULL) return -ENOMEM; array->_xpointers = tmp; pointer = peek_word(tracee, address + i * sizeof_word(tracee)); if (errno != 0) return -errno; array->_xpointers[i].remote = pointer; array->_xpointers[i].local = NULL; } array->length = i; /* By default, assume it is an array of string pointers. */ array->read_xpointee = (read_xpointee_t) read_xpointee_as_string; array->sizeof_xpointee = sizeof_xpointee_as_string; array->write_xpointee = (write_xpointee_t) write_xpointee_as_string; /* By default, use generic callbacks: they rely on * array->read_xpointee() and array->sizeof_xpointee(). */ array->compare_xpointee = compare_xpointee_generic; return 0; } /** * Copy @array into tracee's memory space, then put in @reg the * address where it was copied. This function returns -errno if an * error occured, otherwise 0. */ int push_array_of_xpointers(ArrayOfXPointers *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 pointee + 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 pointee. */ for (i = 0; i < array->length; i++) { ssize_t size; if (array->_xpointers[i].local == NULL) continue; /* At this moment, we only know the offsets in the * tracee's memory block. */ array->_xpointers[i].remote = total_size; size = sizeof_xpointee(array, i); if (size < 0) return size; total_size += size; local[local_count].iov_base = array->_xpointers[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 pointees 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->_xpointers[i].local != NULL) array->_xpointers[i].remote += tracee_ptr; if (is_32on64_mode(tracee)) ((uint32_t *) pod_array)[i] = array->_xpointers[i].remote; else pod_array[i] = array->_xpointers[i].remote; } /* Write all the modified pointees 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; } proot-5.1.0/src/execve/ldso.h0000644000175000017500000000263212443566643015434 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 "execve/aoxp.h" #include "execve/elf.h" extern int ldso_env_passthru(const Tracee *tracee, ArrayOfXPointers *envp, ArrayOfXPointers *argv, const char *define, const char *undefine, size_t offset); extern int rebuild_host_ldso_paths(Tracee *tracee, const char t_program[PATH_MAX], ArrayOfXPointers *envp); extern int compare_xpointee_env(ArrayOfXPointers *envp, size_t index, const char *name); extern bool is_env_name(const char *variable, const char *name); #endif /* LDSO_H */ proot-5.1.0/src/execve/ldso.c0000644000175000017500000003667712443566643015447 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/elf.h" #include "execve/aoxp.h" #include "tracee/tracee.h" #include "cli/note.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 @envp at the given @index, otherwise it returns -errno when an * error occured. */ int compare_xpointee_env(ArrayOfXPointers *envp, size_t index, const char *reference) { char *value; int status; assert(index < envp->length); status = read_xpointee_as_string(envp, 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, ArrayOfXPointers *envp, ArrayOfXPointers *argv, const char *define, const char *undefine, size_t offset) { 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_xpointee_as_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_of_xpointers(argv, offset, 2); \ if (status >= 0) { \ status = write_xpointees(argv, offset, 2, define, env); \ if (status < 0) \ return status; \ } \ write_xpointee(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_of_xpointers(argv, offset, 2); if (status >= 0) { status = write_xpointees(argv, offset, 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; } struct find_program_header_data { ProgramHeader *program_header; SegmentType type; uint64_t address; }; /** * This function is a program header iterator. It stops the iteration * (by returning 1) once it has found a program header that matches * @data. This function returns -errno if an error occurred, * otherwise 0 or 1. */ static int find_program_header(const ElfHeader *elf_header, const ProgramHeader *program_header, void *data_) { struct find_program_header_data *data = data_; if (PROGRAM_FIELD(*elf_header, *program_header, type) == data->type) { uint64_t start; uint64_t end; memcpy(data->program_header, program_header, sizeof(ProgramHeader)); if (data->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 && data->address >= start && data->address <= end) return 1; } return 0; } /** * 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. */ static int read_ldso_rpaths(const Tracee* tracee, int fd, const ElfHeader *elf_header, char **rpaths, char **runpaths) { ProgramHeader dynamic_segment; ProgramHeader strtab_segment; struct find_program_header_data data; 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; data.program_header = &dynamic_segment; data.type = PT_DYNAMIC; data.address = (uint64_t) -1; status = iterate_program_headers(tracee, fd, elf_header, find_program_header, &data); 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; \ \ /* embedded_code 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; data.program_header = &strtab_segment; data.type = PT_LOAD; data.address = strtab_address; /* Search the program header that contains the given string table. */ status = iterate_program_headers(tracee, fd, elf_header, find_program_header, &data); 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; } /** * Rebuild the variable LD_LIBRARY_PATH in @envp for the program * @host_path 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 host_path[PATH_MAX], ArrayOfXPointers *envp) { static char *initial_ldso_paths = NULL; ElfHeader elf_header; char host_ldso_paths[ARG_MAX] = ""; bool rpath_found = false; char *rpaths = NULL; char *runpaths = NULL; size_t length1; size_t length2; size_t index; int status; int fd; fd = open_elf(host_path, &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. */ rpath_found = 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. */ rpath_found = 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_xpointee(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_of_xpointers(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_xpointee_as_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_xpointee(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) rpath_found; } proot-5.1.0/src/execve/execve.h0000644000175000017500000000352612443566643015755 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 /* PATH_MAX, */ #include "tracee/tracee.h" #include "execve/elf.h" #include "arch.h" extern int translate_execve_enter(Tracee *tracee); extern void translate_execve_exit(Tracee *tracee); extern int translate_and_check_exec(Tracee *tracee, char host_path[PATH_MAX], const char *user_path); typedef struct mapping { word_t addr; word_t length; word_t clear_length; word_t prot; word_t flags; word_t fd; word_t offset; } Mapping; typedef struct load_info { char *host_path; char *user_path; char *raw_path; Mapping *mappings; ElfHeader elf_header; struct load_info *interp; } LoadInfo; #define IS_NOTIFICATION_PTRACED_LOAD_DONE(tracee) ( \ (tracee)->as_ptracee.ptracer != NULL \ && peek_reg((tracee), CURRENT, SYSARG_1) == (word_t) 1 \ && peek_reg((tracee), CURRENT, SYSARG_4) == (word_t) 2 \ && peek_reg((tracee), CURRENT, SYSARG_5) == (word_t) 3 \ && peek_reg((tracee), CURRENT, SYSARG_6) == (word_t) 4) #endif /* EXECVE_H */ proot-5.1.0/src/execve/shebang.h0000644000175000017500000000210412443566643016074 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 SHEBANG_H #define SHEBANG_H #include /* PATH_MAX, ARG_MAX, */ #include "tracee/tracee.h" extern int expand_shebang(Tracee *tracee, char host_path[PATH_MAX], char user_path[PATH_MAX]); #endif /* SHEBANG_H */ proot-5.1.0/src/execve/enter.c0000644000175000017500000004377312443566643015616 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), lseek(2), */ #include /* lstat(2), lseek(2), fchmod(2), */ #include /* access(2), lstat(2), close(2), read(2), */ #include /* E*, */ #include /* assert(3), */ #include /* talloc*, */ #include /* PROT_*, */ #include /* strlen(3), strcpy(3), */ #include /* getenv(3), */ #include /* fwrite(3), */ #include /* assert(3), */ #include "execve/execve.h" #include "execve/shebang.h" #include "execve/aoxp.h" #include "execve/ldso.h" #include "execve/elf.h" #include "path/path.h" #include "path/temp.h" #include "tracee/tracee.h" #include "syscall/syscall.h" #include "syscall/sysnum.h" #include "arch.h" #include "cli/note.h" #define P(a) PROGRAM_FIELD(load_info->elf_header, *program_header, a) /** * Add @program_header (type PT_LOAD) to @load_info->mappings. This * function returns -errno if an error occured, otherwise it returns * 0. */ static int add_mapping(const Tracee *tracee UNUSED, LoadInfo *load_info, const ProgramHeader *program_header) { size_t index; word_t start_address; word_t end_address; static word_t page_size = 0; static word_t page_mask = 0; if (page_size == 0) { page_size = sysconf(_SC_PAGE_SIZE); if ((int) page_size <= 0) page_size = 0x1000; page_mask = ~(page_size - 1); } if (load_info->mappings == NULL) index = 0; else index = talloc_array_length(load_info->mappings); load_info->mappings = talloc_realloc(load_info, load_info->mappings, Mapping, index + 1); if (load_info->mappings == NULL) return -ENOMEM; start_address = P(vaddr) & page_mask; end_address = (P(vaddr) + P(filesz) + page_size) & page_mask; load_info->mappings[index].fd = -1; /* Unknown yet. */ load_info->mappings[index].offset = P(offset) & page_mask; load_info->mappings[index].addr = start_address; load_info->mappings[index].length = end_address - start_address; load_info->mappings[index].flags = MAP_PRIVATE | MAP_FIXED; load_info->mappings[index].prot = ( (P(flags) & PF_R ? PROT_READ : 0) | (P(flags) & PF_W ? PROT_WRITE : 0) | (P(flags) & PF_X ? PROT_EXEC : 0)); /* "If the segment's memory size p_memsz is larger than the * file size p_filesz, the "extra" bytes are defined to hold * the value 0 and to follow the segment's initialized area." * -- man 7 elf. */ if (P(memsz) > P(filesz)) { /* How many extra bytes in the current page? */ load_info->mappings[index].clear_length = end_address - P(vaddr) - P(filesz); /* Create new pages for the remaining extra bytes. */ start_address = end_address; end_address = (P(vaddr) + P(memsz) + page_size) & page_mask; if (end_address > start_address) { index++; load_info->mappings = talloc_realloc(load_info, load_info->mappings, Mapping, index + 1); if (load_info->mappings == NULL) return -ENOMEM; load_info->mappings[index].fd = -1; /* Anonymous. */ load_info->mappings[index].offset = 0; load_info->mappings[index].addr = start_address; load_info->mappings[index].length = end_address - start_address; load_info->mappings[index].clear_length = 0; load_info->mappings[index].flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED; load_info->mappings[index].prot = load_info->mappings[index - 1].prot; } } else load_info->mappings[index].clear_length = 0; return 0; } /** * Translate @user_path into @host_path and check if this latter exists, is * executable and is a regular file. This function returns -errno if * an error occured, 0 otherwise. */ int translate_and_check_exec(Tracee *tracee, char host_path[PATH_MAX], const char *user_path) { struct stat statl; int status; if (user_path[0] == '\0') return -ENOEXEC; status = translate_path(tracee, host_path, AT_FDCWD, user_path, true); if (status < 0) return status; status = access(host_path, F_OK); if (status < 0) return -ENOENT; status = access(host_path, X_OK); if (status < 0) return -EACCES; status = lstat(host_path, &statl); if (status < 0) return -EPERM; return 0; } /** * Add @program_header (type PT_INTERP) to @load_info->interp. This * function returns -errno if an error occured, otherwise it returns * 0. */ static int add_interp(Tracee *tracee, int fd, LoadInfo *load_info, const ProgramHeader *program_header) { char host_path[PATH_MAX]; char *user_path; int status; /* Only one PT_INTERP segment is allowed. */ if (load_info->interp != NULL) return -EINVAL; load_info->interp = talloc_zero(load_info, LoadInfo); if (load_info->interp == NULL) return -ENOMEM; user_path = talloc_size(tracee->ctx, P(filesz) + 1); if (user_path == NULL) return -ENOMEM; /* Remember pread(2) doesn't change the * current position in the file. */ status = pread(fd, user_path, P(filesz), P(offset)); if ((size_t) status != P(filesz)) /* Unexpected size. */ status = -EACCES; if (status < 0) return status; user_path[P(filesz)] = '\0'; /* When a QEMU command was specified: * * - if it's a foreign binary we are reading the ELF * interpreter of QEMU instead. * * - if it's a host binary, we are reading its ELF * interpreter. * * In both case, it lies in "/host-rootfs" from a guest * point-of-view. */ if (tracee->qemu != NULL && user_path[0] == '/') { user_path = talloc_asprintf(tracee->ctx, "%s%s", HOST_ROOTFS, user_path); if (user_path == NULL) return -ENOMEM; } status = translate_and_check_exec(tracee, host_path, user_path); if (status < 0) return status; load_info->interp->host_path = talloc_strdup(load_info->interp, host_path); if (load_info->interp->host_path == NULL) return -ENOMEM; load_info->interp->user_path = talloc_strdup(load_info->interp, user_path); if (load_info->interp->user_path == NULL) return -ENOMEM; return 0; } #undef P struct add_load_info_data { LoadInfo *load_info; Tracee *tracee; int fd; }; /** * This function is a program header iterator. It invokes * add_mapping() or add_interp(), according to the type of * @program_header. This function returns -errno if an error * occurred, otherwise 0. */ static int add_load_info(const ElfHeader *elf_header, const ProgramHeader *program_header, void *data_) { struct add_load_info_data *data = data_; int status; switch (PROGRAM_FIELD(*elf_header, *program_header, type)) { case PT_LOAD: status = add_mapping(data->tracee, data->load_info, program_header); if (status < 0) return status; break; case PT_INTERP: status = add_interp(data->tracee, data->fd, data->load_info, program_header); if (status < 0) return status; break; default: break; } return 0; } /** * Extract the load info from @load->host_path. This function returns * -errno if an error occured, otherwise it returns 0. */ static int extract_load_info(Tracee *tracee, LoadInfo *load_info) { struct add_load_info_data data; int fd = -1; int status; assert(load_info != NULL); assert(load_info->host_path != NULL); fd = open_elf(load_info->host_path, &load_info->elf_header); if (fd < 0) return fd; /* Sanity check. */ switch (ELF_FIELD(load_info->elf_header, type)) { case ET_EXEC: case ET_DYN: break; default: status = -EINVAL; goto end; } data.load_info = load_info; data.tracee = tracee; data.fd = fd; status = iterate_program_headers(tracee, fd, &load_info->elf_header, add_load_info, &data); end: if (fd >= 0) close(fd); return status; } /** * Add @load_base to each adresses of @load_info. */ static void add_load_base(LoadInfo *load_info, word_t load_base) { size_t nb_mappings; size_t i; nb_mappings = talloc_array_length(load_info->mappings); for (i = 0; i < nb_mappings; i++) load_info->mappings[i].addr += load_base; if (IS_CLASS64(load_info->elf_header)) load_info->elf_header.class64.e_entry += load_base; else load_info->elf_header.class32.e_entry += load_base; } /** * Compute the final load address for each position independant * objects of @tracee. * * TODO: support for ASLR. */ static void compute_load_addresses(Tracee *tracee) { if (IS_POSITION_INDENPENDANT(tracee->load_info->elf_header) && tracee->load_info->mappings[0].addr == 0) { #if defined(HAS_LOADER_32BIT) if (IS_CLASS32(tracee->load_info->elf_header)) add_load_base(tracee->load_info, EXEC_PIC_ADDRESS_32); else #endif add_load_base(tracee->load_info, EXEC_PIC_ADDRESS); } /* Nothing more to do? */ if (tracee->load_info->interp == NULL) return; if (IS_POSITION_INDENPENDANT(tracee->load_info->interp->elf_header) && tracee->load_info->interp->mappings[0].addr == 0) { #if defined(HAS_LOADER_32BIT) if (IS_CLASS32(tracee->load_info->elf_header)) add_load_base(tracee->load_info->interp, INTERP_PIC_ADDRESS_32); else #endif add_load_base(tracee->load_info->interp, INTERP_PIC_ADDRESS); } } /** * Expand in argv[] and envp[] the runner for @user_path, if needed. * This function returns -errno if an error occurred, otherwise 0. On * success, both @host_path and @user_path point to the program to * execute (respectively from host and guest point-of-views), and both * @tracee's argv[] (pointed to by SYSARG_2) @tracee's envp[] (pointed * to by SYSARG_3) are correctly updated. */ static int expand_runner(Tracee* tracee, char host_path[PATH_MAX], char user_path[PATH_MAX]) { ArrayOfXPointers *envp; char *argv0; int status; /* Execution of host programs when QEMU is in use relies on * LD_ environment variables. */ status = fetch_array_of_xpointers(tracee, &envp, SYSARG_3, 0); if (status < 0) return status; /* Environment variables should be compared with the "name" * part of the "name=value" string format. */ envp->compare_xpointee = (compare_xpointee_t) compare_xpointee_env; /* No need to adjust argv[] if it's a host binary (a.k.a * mixed-mode). */ if (!is_host_elf(tracee, host_path)) { ArrayOfXPointers *argv; size_t nb_qemu_args; size_t i; status = fetch_array_of_xpointers(tracee, &argv, SYSARG_2, 0); if (status < 0) return status; status = read_xpointee_as_string(argv, 0, &argv0); if (status < 0) return status; /* Assuming PRoot was invoked this way: * * proot -q 'qemu-arm -cpu cortex-a9' ... * * a call to: * * execve("/bin/true", { "true", NULL }, ...) * * becomes: * * execve("/usr/bin/qemu", * { "qemu", "-cpu", "cortex-a9", "-0", "true", "/bin/true", NULL }, ...) */ nb_qemu_args = talloc_array_length(tracee->qemu) - 1; status = resize_array_of_xpointers(argv, 1, nb_qemu_args + 2); if (status < 0) return status; for (i = 0; i < nb_qemu_args; i++) { status = write_xpointee(argv, i, tracee->qemu[i]); if (status < 0) return status; } status = write_xpointees(argv, i, 3, "-0", argv0, user_path); if (status < 0) return status; /* Ensure LD_ features should not be applied to QEMU * iteself. */ status = ldso_env_passthru(tracee, envp, argv, "-E", "-U", i); if (status < 0) return status; status = push_array_of_xpointers(argv, SYSARG_2); if (status < 0) return status; /* Launch the runner in lieu of the initial * program. */ assert(strlen(tracee->qemu[0]) + strlen(HOST_ROOTFS) < PATH_MAX); assert(tracee->qemu[0][0] == '/'); strcpy(host_path, tracee->qemu[0]); strcpy(user_path, HOST_ROOTFS); strcat(user_path, host_path); } /* 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, host_path, envp); if (status < 0) return status; status = push_array_of_xpointers(envp, SYSARG_3); if (status < 0) return status; return 0; } extern unsigned char _binary_loader_exe_start; extern unsigned char _binary_loader_exe_size; extern unsigned char WEAK _binary_loader_m32_exe_start; extern unsigned char WEAK _binary_loader_m32_exe_size; /** * Extract the built-in loader. This function returns NULL if an * error occurred, otherwise it returns the path to the extracted * loader. Note: @tracee is only used for notification purpose. */ static char *extract_loader(const Tracee *tracee, bool wants_32bit_version) { char path[PATH_MAX]; size_t status2; void *start; size_t size; int status; int fd; char *loader_path = NULL; FILE *file = NULL; file = open_temp_file(NULL, "prooted"); if (file == NULL) goto end; fd = fileno(file); if (wants_32bit_version) { start = (void *) &_binary_loader_m32_exe_start; size = (size_t) &_binary_loader_m32_exe_size; } else { start = (void *) &_binary_loader_exe_start; size = (size_t) &_binary_loader_exe_size; } status2 = write(fd, start, size); if (status2 != size) { note(tracee, ERROR, SYSTEM, "can't write the loader"); goto end; } status = fchmod(fd, S_IRUSR|S_IXUSR); if (status < 0) { note(tracee, ERROR, SYSTEM, "can't change loader permissions (u+rx)"); goto end; } status = readlink_proc_pid_fd(getpid(), fd, path); if (status < 0) { note(tracee, ERROR, INTERNAL, "can't retrieve loader path (/proc/self/fd/)"); goto end; } loader_path = talloc_strdup(talloc_autofree_context(), path); if (loader_path == NULL) { note(tracee, ERROR, INTERNAL, "can't allocate memory"); goto end; } if (tracee->verbose >= 2) note(tracee, INFO, INTERNAL, "loader: %s", loader_path); end: if (file != NULL) { status = fclose(file); if (status < 0) note(tracee, WARNING, SYSTEM, "can't close loader file"); } return loader_path; } /** * Get the path to the loader for the given @tracee. This function * returns NULL if an error occurred. */ static inline const char *get_loader_path(const Tracee *tracee) { static char *loader_path = NULL; #if defined(HAS_LOADER_32BIT) static char *loader32_path = NULL; if (IS_CLASS32(tracee->load_info->elf_header)) { loader32_path = loader32_path ?: getenv("PROOT_LOADER_32") ?: extract_loader(tracee, true); return loader32_path; } else #endif { loader_path = loader_path ?: getenv("PROOT_LOADER") ?: extract_loader(tracee, false); return loader_path; } } /** * Extract all the information that will be required by * translate_load_*(). This function returns -errno if an error * occured, otherwise 0. */ int translate_execve_enter(Tracee *tracee) { char user_path[PATH_MAX]; char host_path[PATH_MAX]; char new_exe[PATH_MAX]; char *raw_path; const char *loader_path; int status; if (IS_NOTIFICATION_PTRACED_LOAD_DONE(tracee)) { /* Syscalls can now be reported to its ptracer. */ tracee->as_ptracee.ignore_loader_syscalls = false; /* Cancel this spurious execve, it was only used as a * notification. */ set_sysnum(tracee, PR_void); return 0; } status = get_sysarg_path(tracee, user_path, SYSARG_1); if (status < 0) return status; /* Remember the user path before it is overwritten by * expand_shebang(). This "raw" path is useful to fix the * value of AT_EXECFN and /proc/{@tracee->pid}/comm. */ raw_path = talloc_strdup(tracee->ctx, user_path); if (raw_path == NULL) return -ENOMEM; status = expand_shebang(tracee, host_path, user_path); if (status < 0) /* The Linux kernel actually returns -EACCES when * trying to execute a directory. */ return status == -EISDIR ? -EACCES : status; /* user_path is modified only if there's an interpreter * (ie. for a script or with qemu). */ if (status == 0 && tracee->qemu == NULL) TALLOC_FREE(raw_path); /* Remember the new value for "/proc/self/exe". It points to * a canonicalized guest path, hence detranslate_path() * instead of using user_path directly. */ strcpy(new_exe, host_path); status = detranslate_path(tracee, new_exe, NULL); if (status >= 0) { talloc_unlink(tracee, tracee->new_exe); tracee->new_exe = talloc_strdup(tracee, new_exe); } else tracee->new_exe = NULL; if (tracee->qemu != NULL) { status = expand_runner(tracee, host_path, user_path); if (status < 0) return status; } TALLOC_FREE(tracee->load_info); tracee->load_info = talloc_zero(tracee, LoadInfo); if (tracee->load_info == NULL) return -ENOMEM; tracee->load_info->host_path = talloc_strdup(tracee->load_info, host_path); if (tracee->load_info->host_path == NULL) return -ENOMEM; tracee->load_info->user_path = talloc_strdup(tracee->load_info, user_path); if (tracee->load_info->user_path == NULL) return -ENOMEM; tracee->load_info->raw_path = (raw_path != NULL ? talloc_reparent(tracee->ctx, tracee->load_info, raw_path) : talloc_reference(tracee->load_info, tracee->load_info->user_path)); if (tracee->load_info->raw_path == NULL) return -ENOMEM; status = extract_load_info(tracee, tracee->load_info); if (status < 0) return status; if (tracee->load_info->interp != NULL) { status = extract_load_info(tracee, tracee->load_info->interp); if (status < 0) return status; /* An ELF interpreter is supposed to be * standalone. */ if (tracee->load_info->interp->interp != NULL) return -EINVAL; } compute_load_addresses(tracee); /* Execute the loader instead of the program. */ loader_path = get_loader_path(tracee); if (loader_path == NULL) return -ENOENT; status = set_sysarg_path(tracee, loader_path, SYSARG_1); if (status < 0) return status; /* Mask to its ptracer syscalls performed by the loader. */ tracee->as_ptracee.ignore_loader_syscalls = true; return 0; } proot-5.1.0/src/execve/elf.c0000644000175000017500000001073312443566643015235 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 /* str*(3), memcpy(3), */ #include /* assert(3), */ #include /* talloc_*, */ #include /* bool, true, false, */ #include "execve/elf.h" #include "tracee/tracee.h" #include "cli/note.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) { status = -errno; 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; } /** * Invoke @callback(..., @data) for each program headers from the * specified ELF file (referenced by @fd, with the given @elf_header). * This function returns -errno if an error occured, or it returns * immediately the value != 0 returned by @callback, otherwise 0. */ int iterate_program_headers(const Tracee *tracee, int fd, const ElfHeader *elf_header, program_headers_iterator_t callback, void *data) { ProgramHeader program_header; 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) { note(tracee, WARNING, INTERNAL, "%d: big PH tables are not yet supported.", fd); return -ENOTSUP; } if (!KNOWN_PHENTSIZE(*elf_header, elf_phentsize)) { note(tracee, WARNING, INTERNAL, "%d: unsupported size of program header.", fd); return -ENOTSUP; } 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); status = callback(elf_header, &program_header, data); if (status != 0) return status; } return 0; } /** * Check if @host_path is an ELF file for the host architecture. */ bool is_host_elf(const Tracee *tracee, const char *host_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(host_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", host_path); return true; } } return false; } proot-5.1.0/src/execve/auxv.c0000644000175000017500000001132512443566643015450 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 "tracee/reg.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; } /** * 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); } poke_word(tracee, address, AT_NULL); if (errno != 0) return -errno; address += sizeof_word(tracee); poke_word(tracee, address, 0); if (errno != 0) return -errno; address += sizeof_word(tracee); return 0; } proot-5.1.0/src/execve/elf.h0000644000175000017500000001061412443566643015240 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; /* Object type: */ #define ET_REL 1 #define ET_EXEC 2 #define ET_DYN 3 #define ET_CORE 4 /* Segment flags: */ #define PF_X 1 #define PF_W 2 #define PF_R 4 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))) #define IS_POSITION_INDENPENDANT(elf_header) \ (ELF_FIELD((elf_header), type) == ET_DYN) #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); typedef int (* program_headers_iterator_t)(const ElfHeader *elf_header, const ProgramHeader *program_header, void *data); extern int iterate_program_headers(const Tracee *tracee, int fd, const ElfHeader *elf_header, program_headers_iterator_t callback, void *data); #endif /* ELF_H */ proot-5.1.0/src/execve/auxv.h0000644000175000017500000000250412443566643015454 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 elf_aux_vector { 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 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); #endif /* AUXV */ proot-5.1.0/src/attribute.h0000644000175000017500000000221012443566643015207 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 */ proot-5.1.0/src/ptrace/0000755000175000017500000000000012443566643014316 5ustar ivoireivoireproot-5.1.0/src/ptrace/ptrace.h0000644000175000017500000000232212443566643015744 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); extern void attach_to_ptracer(Tracee *ptracee, Tracee *ptracer); extern void detach_from_ptracer(Tracee *ptracee); #define PTRACEE (ptracee->as_ptracee) #define PTRACER (ptracer->as_ptracer) #endif /* PTRACE_H */ proot-5.1.0/src/ptrace/user.c0000644000175000017500000001410612443566643015442 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/note.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 = ""; note(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 */ proot-5.1.0/src/ptrace/user.h0000644000175000017500000000276612443566643015460 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 proot-5.1.0/src/ptrace/wait.h0000644000175000017500000000331412443566643015434 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); /* __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 */ proot-5.1.0/src/ptrace/ptrace.c0000644000175000017500000004105712443566643015747 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 "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/note.h" #include "arch.h" #include "compat.h" #if defined(ARCH_X86_64) || defined(ARCH_X86) #include /* struct user_desc, */ #endif #if defined(ARCH_X86_64) #include /* ARCH_{G,S}ET_{F,G}S, */ #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) CASE_STR(PTRACE_ARCH_PRCTL) 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; } /** * Set @ptracee's tracer to @ptracer, and increment ptracees counter * of this later. */ void attach_to_ptracer(Tracee *ptracee, Tracee *ptracer) { bzero(&(PTRACEE), sizeof(PTRACEE)); PTRACEE.ptracer = ptracer; PTRACER.nb_ptracees++; } /** * Unset @ptracee's tracer, and decrement ptracees counter of this * later. */ void detach_from_ptracer(Tracee *ptracee) { Tracee *ptracer = PTRACEE.ptracer; PTRACEE.ptracer = NULL; assert(PTRACER.nb_ptracees > 0); PTRACER.nb_ptracees--; } /** * 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); /* Propagate signedness for this special value. */ if (is_32on64_mode(tracee) && pid == 0xFFFFFFFF) pid = (word_t) -1; /* 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; attach_to_ptracer(ptracee, ptracer); /* Detect when the ptracer has gone to wait before the * ptracee did the ptrace(ATTACHME) request. */ if (PTRACER.waits_in == WAITS_IN_KERNEL) { status = kill(ptracer->pid, SIGSTOP); if (status < 0) note(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; attach_to_ptracer(ptracee, ptracer); /* 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; note(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_syscalls = false; forced_signal = (int) data; status = 0; break; /* Restart the ptracee. */ case PTRACE_CONT: PTRACEE.ignore_syscalls = 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: detach_from_ptracer(ptracee); 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) note(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) note(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) note(ptracer, WARNING, INTERNAL, "ptrace request '%s' not supported yet", stringify_ptrace(request)); warned = true; return -ENOTSUP; } #if defined(ARCH_X86_64) case PTRACE_ARCH_PRCTL: switch (data) { case ARCH_GET_GS: case ARCH_GET_FS: status = ptrace(request, pid, &result, data); if (status < 0) return -errno; poke_word(ptracer, address, result); if (errno != 0) return -errno; break; case ARCH_SET_GS: case ARCH_SET_FS: { static bool warned = false; if (!warned) note(ptracer, WARNING, INTERNAL, "ptrace request '%s' ARCH_SET_{G,F}S not supported yet", stringify_ptrace(request)); return -ENOTSUP; } default: return -ENOTSUP; } return 0; /* Don't restart the ptracee. */ #endif default: note(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; } proot-5.1.0/src/ptrace/wait.c0000644000175000017500000002472112443566643015434 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 /* SIG*, */ #include /* talloc*, */ #include "ptrace/wait.h" #include "ptrace/ptrace.h" #include "syscall/sysnum.h" #include "syscall/chain.h" #include "tracee/tracee.h" #include "tracee/event.h" #include "tracee/reg.h" #include "tracee/mem.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. This function returns -errno if an error occurred, 0 if * the wait syscall will be restarted (ie. the event is discarded), * otherwise @ptracee's pid. */ static int update_wait_status(Tracee *ptracer, Tracee *ptracee) { word_t address; int result; /* Special case: the Linux kernel reports the terminating * event issued by a process to both its parent and its * tracer, except when they are the same. In this case the * Linux kernel reports the terminating event only once to the * tracing parent ... */ if (PTRACEE.ptracer == ptracee->parent && (WIFEXITED(PTRACEE.event4.ptracer.value) || WIFSIGNALED(PTRACEE.event4.ptracer.value))) { /* ... So hide this terminating event (toward its * tracer, ie. PRoot) and make the second one appear * (towards its parent, ie. the ptracer). This will * ensure its exit status is collected from a kernel * point-of-view (ie. it doesn't stay a zombie * forever). */ restart_original_syscall(ptracer); /* Detach this ptracee from its ptracer, PRoot doesn't * have anything else to emulate. */ detach_from_ptracer(ptracee); /* Zombies can rest in peace once the ptracer is * notified. */ if (PTRACEE.is_zombie) TALLOC_FREE(ptracee); return 0; } address = peek_reg(ptracer, ORIGINAL, SYSARG_2); if (address != 0) { poke_int32(ptracer, address, PTRACEE.event4.ptracer.value); if (errno != 0) return -errno; } PTRACEE.event4.ptracer.pending = false; /* Be careful; ptracee might get freed before its pid is * returned. */ result = ptracee->pid; /* Zombies can rest in peace once the ptracer is notified. */ if (PTRACEE.is_zombie) { detach_from_ptracer(ptracee); TALLOC_FREE(ptracee); } return result; } /** * 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; return status; } /** * 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: if (PTRACEE.ignore_syscalls || PTRACEE.ignore_loader_syscalls) 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); if (status == 0) chain_next_syscall(ptracer); else 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; } proot-5.1.0/src/.check_process_vm.c0000644000175000017500000000024312443566643016576 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); } proot-5.1.0/src/loader/0000755000175000017500000000000012443566643014306 5ustar ivoireivoireproot-5.1.0/src/loader/script.h0000644000175000017500000000356012443566643015767 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 SCRIPT #define SCRIPT #include "arch.h" #include "attribute.h" struct load_statement { word_t action; union { struct { word_t string_address; } open; struct { word_t addr; word_t length; word_t prot; word_t offset; word_t clear_length; } mmap; struct { word_t stack_pointer; word_t entry_point; word_t at_phdr; word_t at_phent; word_t at_phnum; word_t at_entry; word_t at_execfn; } start; }; } PACKED; typedef struct load_statement LoadStatement; #define LOAD_STATEMENT_SIZE(statement, type) \ (sizeof((statement).action) + sizeof((statement).type)) /* Don't use enum, since sizeof(enum) doesn't have to be equal to * sizeof(word_t). Keep values in the same order as their respective * actions appear in loader.c to get a change GCC produces a jump * table. */ #define LOAD_ACTION_OPEN_NEXT 0 #define LOAD_ACTION_OPEN 1 #define LOAD_ACTION_MMAP_FILE 2 #define LOAD_ACTION_MMAP_ANON 3 #define LOAD_ACTION_START_TRACED 4 #define LOAD_ACTION_START 5 #endif /* SCRIPT */ proot-5.1.0/src/loader/loader.c0000644000175000017500000001413712443566643015726 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, */ #define NO_LIBC_HEADER #include "loader/script.h" #include "compat.h" #include "arch.h" #define GCC_VERSION (__GNUC__ * 10000 \ + __GNUC_MINOR__ * 100 \ + __GNUC_PATCHLEVEL__) #if GCC_VERSION < 40500 #define __builtin_unreachable() #endif #if defined(ARCH_X86_64) # include "loader/assembly-x86_64.h" #elif defined(ARCH_ARM_EABI) # include "loader/assembly-arm.h" #elif defined(ARCH_X86) # include "loader/assembly-x86.h" #else # error "Unsupported architecture" #endif #if !defined(MMAP_OFFSET_SHIFT) # define MMAP_OFFSET_SHIFT 0 #endif #define FATAL() do { \ SYSCALL(EXIT, 1, 182); \ __builtin_unreachable(); \ } while (0) #define unlikely(expr) __builtin_expect(!!(expr), 0) /** * Clear the memory from @start (inclusive) to @end (exclusive). */ static inline void clear(word_t start, word_t end) { byte_t *start_misaligned; byte_t *end_misaligned; word_t *start_aligned; word_t *end_aligned; /* Compute the number of mis-aligned bytes. */ word_t start_bytes = start % sizeof(word_t); word_t end_bytes = end % sizeof(word_t); /* Compute aligned addresses. */ start_aligned = (word_t *) (start_bytes ? start + sizeof(word_t) - start_bytes : start); end_aligned = (word_t *) (end - end_bytes); /* Clear leading mis-aligned bytes. */ start_misaligned = (byte_t *) start; while (start_misaligned < (byte_t *) start_aligned) *start_misaligned++ = 0; /* Clear aligned bytes. */ while (start_aligned < end_aligned) *start_aligned++ = 0; /* Clear trailing mis-aligned bytes. */ end_misaligned = (byte_t *) end_aligned; while (end_misaligned < (byte_t *) end) *end_misaligned++ = 0; } /** * Return the address of the last path component of @string_. Note * that @string_ is not modified. */ static inline word_t basename(word_t string_) { byte_t *string = (byte_t *) string_; byte_t *cursor; for (cursor = string; *cursor != 0; cursor++) ; for (; *cursor != (byte_t) '/' && cursor > string; cursor--) ; if (cursor != string) cursor++; return (word_t) cursor; } /** * Interpret the load script pointed to by @cursor. */ void _start(void *cursor) { bool traced = false; bool reset_at_base = true; word_t at_base = 0; word_t fd = -1; word_t status; while(1) { LoadStatement *stmt = cursor; switch (stmt->action) { case LOAD_ACTION_OPEN_NEXT: status = SYSCALL(CLOSE, 1, fd); if (unlikely((int) status < 0)) FATAL(); /* Fall through. */ case LOAD_ACTION_OPEN: fd = SYSCALL(OPEN, 3, stmt->open.string_address, O_RDONLY, 0); if (unlikely((int) fd < 0)) FATAL(); reset_at_base = true; cursor += LOAD_STATEMENT_SIZE(*stmt, open); break; case LOAD_ACTION_MMAP_FILE: status = SYSCALL(MMAP, 6, stmt->mmap.addr, stmt->mmap.length, stmt->mmap.prot, MAP_PRIVATE | MAP_FIXED, fd, stmt->mmap.offset >> MMAP_OFFSET_SHIFT); if (unlikely(status != stmt->mmap.addr)) FATAL(); if (stmt->mmap.clear_length != 0) clear(stmt->mmap.addr + stmt->mmap.length - stmt->mmap.clear_length, stmt->mmap.addr + stmt->mmap.length); if (reset_at_base) { at_base = stmt->mmap.addr; reset_at_base = false; } cursor += LOAD_STATEMENT_SIZE(*stmt, mmap); break; case LOAD_ACTION_MMAP_ANON: status = SYSCALL(MMAP, 6, stmt->mmap.addr, stmt->mmap.length, stmt->mmap.prot, MAP_PRIVATE | MAP_FIXED | MAP_ANONYMOUS, -1, 0); if (unlikely(status != stmt->mmap.addr)) FATAL(); cursor += LOAD_STATEMENT_SIZE(*stmt, mmap); break; case LOAD_ACTION_START_TRACED: traced = true; /* Fall through. */ case LOAD_ACTION_START: { word_t *cursor2 = (word_t *) stmt->start.stack_pointer; const word_t argc = cursor2[0]; const word_t at_execfn = cursor2[1]; word_t name; status = SYSCALL(CLOSE, 1, fd); if (unlikely((int) status < 0)) FATAL(); /* Right after execve, the stack content is as follow: * * +------+--------+--------+--------+ * | argc | argv[] | envp[] | auxv[] | * +------+--------+--------+--------+ */ /* Skip argv[]. */ cursor2 += argc + 1; /* Skip envp[]. */ do cursor2++; while (cursor2[0] != 0); cursor2++; /* Adjust auxv[]. */ do { switch (cursor2[0]) { case AT_PHDR: cursor2[1] = stmt->start.at_phdr; break; case AT_PHENT: cursor2[1] = stmt->start.at_phent; break; case AT_PHNUM: cursor2[1] = stmt->start.at_phnum; break; case AT_ENTRY: cursor2[1] = stmt->start.at_entry; break; case AT_BASE: cursor2[1] = at_base; break; case AT_EXECFN: /* stmt->start.at_execfn can't be used for now since it is * currently stored in a location that will be scratched * by the process (below the final stack pointer). */ cursor2[1] = at_execfn; break; default: break; } cursor2 += 2; } while (cursor2[0] != AT_NULL); /* Note that only 2 arguments are actually necessary... */ name = basename(stmt->start.at_execfn); SYSCALL(PRCTL, 3, PR_SET_NAME, name, 0); if (unlikely(traced)) SYSCALL(EXECVE, 6, 1, stmt->start.stack_pointer, stmt->start.entry_point, 2, 3, 4); else BRANCH(stmt->start.stack_pointer, stmt->start.entry_point); FATAL(); } default: FATAL(); } } FATAL(); } proot-5.1.0/src/loader/assembly.S0000644000175000017500000000200712443566643016250 0ustar ivoireivoire#if defined(__i386__) .text /* ABI user-land kernel-land ====== ========= =========== number %eax %eax arg1 %edx %ebx arg2 %ecx %ecx arg3 16(%esp) %edx arg4 12(%esp) %esi arg5 8(%esp) %edi arg6 4(%esp) %ebp result N/A %eax */ .globl syscall_6 .type syscall_6, @function syscall_6: /* Callee-saved registers. */ pushl %ebp // %esp -= 0x04 pushl %edi // %esp -= 0x08 pushl %esi // %esp -= 0x0c pushl %ebx // %esp -= 0x10 // mov %eax, %eax // number mov %edx, %ebx // arg1 // mov %ecx, %ecx // arg2 mov 0x14(%esp), %edx // arg3 mov 0x18(%esp), %esi // arg4 mov 0x1c(%esp), %edi // arg5 mov 0x20(%esp), %ebp // arg6 int $0x80 popl %ebx popl %esi popl %edi popl %ebp // mov %eax, %eax // result ret .globl syscall_3 .type syscall_3, @function syscall_3: pushl %ebx mov %edx, %ebx mov 0x8(%esp), %edx int $0x80 popl %ebx ret .globl syscall_1 .type syscall_1, @function syscall_1: pushl %ebx mov %edx, %ebx int $0x80 popl %ebx ret #endif /* defined(__i386__) */ proot-5.1.0/src/loader/assembly-x86.h0000644000175000017500000000415712443566643016730 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. */ /* According to the x86 ABI, all registers have undefined values at * program startup except: * * - the instruction pointer (rip) * - the stack pointer (rsp) * - the rtld_fini pointer (rdx) * - the system flags (eflags) */ #define BRANCH(stack_pointer, destination) do { \ asm volatile ( \ "// Restore initial stack pointer. \n\t" \ "movl %0, %%esp \n\t" \ " \n\t" \ "// Clear state flags. \n\t" \ "pushl $0 \n\t" \ "popfl \n\t" \ " \n\t" \ "// Clear rtld_fini. \n\t" \ "movl $0, %%edx \n\t" \ " \n\t" \ "// Start the program. \n\t" \ "jmpl *%%eax \n" \ : /* no output */ \ : "irm" (stack_pointer), "a" (destination) \ : "memory", "cc", "esp", "edx"); \ __builtin_unreachable(); \ } while (0) extern word_t syscall_6(word_t number, word_t arg1, word_t arg2, word_t arg3, word_t arg4, word_t arg5, word_t arg6); extern word_t syscall_3(word_t number, word_t arg1, word_t arg2, word_t arg3); extern word_t syscall_1(word_t number, word_t arg1); #define SYSCALL(number, nb_args, args...) syscall_##nb_args(number, args) #define OPEN 5 #define CLOSE 6 #define MMAP 192 #define MMAP_OFFSET_SHIFT 12 #define EXECVE 11 #define EXIT 1 #define PRCTL 172 proot-5.1.0/src/loader/assembly-arm.h0000644000175000017500000000521212443566643017053 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. */ /* According to the ARM EABI, all registers have undefined values at * program startup except: * * - the instruction pointer (r15) * - the stack pointer (r13) * - the rtld_fini pointer (r0) */ #define BRANCH(stack_pointer, destination) do { \ asm volatile ( \ "// Restore initial stack pointer. \n\t" \ "mov sp, %0 \n\t" \ " \n\t" \ "// Clear rtld_fini. \n\t" \ "mov r0, #0 \n\t" \ " \n\t" \ "// Start the program. \n\t" \ "mov pc, %1 \n" \ : /* no output */ \ : "r" (stack_pointer), "r" (destination) \ : "memory", "sp", "r0", "pc"); \ __builtin_unreachable(); \ } while (0) #define PREPARE_ARGS_1(arg1_) \ register word_t arg1 asm("r0") = arg1_; \ #define PREPARE_ARGS_3(arg1_, arg2_, arg3_) \ PREPARE_ARGS_1(arg1_) \ register word_t arg2 asm("r1") = arg2_; \ register word_t arg3 asm("r2") = arg3_; \ #define PREPARE_ARGS_6(arg1_, arg2_, arg3_, arg4_, arg5_, arg6_) \ PREPARE_ARGS_3(arg1_, arg2_, arg3_) \ register word_t arg4 asm("r3") = arg4_; \ register word_t arg5 asm("r4") = arg5_; \ register word_t arg6 asm("r5") = arg6_; #define OUTPUT_CONTRAINTS_1 \ "r" (arg1) #define OUTPUT_CONTRAINTS_3 \ OUTPUT_CONTRAINTS_1, \ "r" (arg2), "r" (arg3) #define OUTPUT_CONTRAINTS_6 \ OUTPUT_CONTRAINTS_3, \ "r" (arg4), "r" (arg5), "r" (arg6) #define SYSCALL(number_, nb_args, args...) \ ({ \ register word_t number asm("r7") = number_; \ register word_t result asm("r0"); \ PREPARE_ARGS_##nb_args(args) \ asm volatile ( \ "svc #0x00000000 \n\t" \ : "=r" (result) \ : "r" (number), \ OUTPUT_CONTRAINTS_##nb_args \ : "memory"); \ result; \ }) #define OPEN 5 #define CLOSE 6 #define MMAP 192 #define MMAP_OFFSET_SHIFT 12 #define EXECVE 11 #define EXIT 1 #define PRCTL 172 proot-5.1.0/src/loader/assembly-x86_64.h0000644000175000017500000000541712443566643017241 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. */ /* According to the x86_64 ABI, all registers have undefined values at * program startup except: * * - the instruction pointer (rip) * - the stack pointer (rsp) * - the rtld_fini pointer (rdx) * - the system flags (rflags) */ #define BRANCH(stack_pointer, destination) do { \ asm volatile ( \ "// Restore initial stack pointer. \n\t" \ "movq %0, %%rsp \n\t" \ " \n\t" \ "// Clear state flags. \n\t" \ "pushq $0 \n\t" \ "popfq \n\t" \ " \n\t" \ "// Clear rtld_fini. \n\t" \ "movq $0, %%rdx \n\t" \ " \n\t" \ "// Start the program. \n\t" \ "jmpq *%%rax \n" \ : /* no output */ \ : "irm" (stack_pointer), "a" (destination) \ : "memory", "cc", "rsp", "rdx"); \ __builtin_unreachable(); \ } while (0) #define PREPARE_ARGS_1(arg1_) \ register word_t arg1 asm("rdi") = arg1_; \ #define PREPARE_ARGS_3(arg1_, arg2_, arg3_) \ PREPARE_ARGS_1(arg1_) \ register word_t arg2 asm("rsi") = arg2_; \ register word_t arg3 asm("rdx") = arg3_; \ #define PREPARE_ARGS_6(arg1_, arg2_, arg3_, arg4_, arg5_, arg6_) \ PREPARE_ARGS_3(arg1_, arg2_, arg3_) \ register word_t arg4 asm("r10") = arg4_; \ register word_t arg5 asm("r8") = arg5_; \ register word_t arg6 asm("r9") = arg6_; #define OUTPUT_CONTRAINTS_1 \ "r" (arg1) #define OUTPUT_CONTRAINTS_3 \ OUTPUT_CONTRAINTS_1, \ "r" (arg2), "r" (arg3) #define OUTPUT_CONTRAINTS_6 \ OUTPUT_CONTRAINTS_3, \ "r" (arg4), "r" (arg5), "r" (arg6) #define SYSCALL(number_, nb_args, args...) \ ({ \ register word_t number asm("rax") = number_; \ register word_t result asm("rax"); \ PREPARE_ARGS_##nb_args(args) \ asm volatile ( \ "syscall \n\t" \ : "=r" (result) \ : "r" (number), \ OUTPUT_CONTRAINTS_##nb_args \ : "memory", "cc", "rcx", "r11"); \ result; \ }) #define OPEN 2 #define CLOSE 3 #define MMAP 9 #define EXECVE 59 #define EXIT 60 #define PRCTL 157