Date: Sun, 4 Nov 2012 15:37:27 +0100 From: Jilles Tjoelker <jilles@stack.nl> To: freebsd-hackers@FreeBSD.org Subject: [patch] rtld: fix fd leak with parallel dlopen and fork/exec Message-ID: <20121104143727.GB35520@stack.nl>
next in thread | raw e-mail | index | archive | help
Rtld does not set FD_CLOEXEC on its internal file descriptors; therefore, such a file descriptor may be passed to a process created by another thread running in parallel to dlopen() or fdlopen(). The race is easy to trigger with the below dlopen_exec_race.c that performs the two in parallel repeatedly, for example ./dlopen_exec_race /lib/libedit.so.7 | grep lib No other threads are expected to be running during parsing of the hints and libmap files but the file descriptors need not be passed to child processes so I added O_CLOEXEC there as well. The O_CLOEXEC flag is ignored by older kernels so it will not cause breakage when people try the unsupported action of running new rtld on old kernel. However, the fcntl(F_DUPFD_CLOEXEC) will fail with [EINVAL] on an old kernel so this patch will break fdlopen() with old kernels. I suppose this is OK because fdlopen() is not used in the vital parts of buildworld and the like. Index: libexec/rtld-elf/libmap.c =================================================================== --- libexec/rtld-elf/libmap.c (revision 240561) +++ libexec/rtld-elf/libmap.c (working copy) @@ -121,7 +121,7 @@ } } - fd = open(rpath, O_RDONLY); + fd = open(rpath, O_RDONLY | O_CLOEXEC); if (fd == -1) { dbg("lm_parse_file: open(\"%s\") failed, %s", rpath, rtld_strerror(errno)); Index: libexec/rtld-elf/rtld.c =================================================================== --- libexec/rtld-elf/rtld.c (revision 240561) +++ libexec/rtld-elf/rtld.c (working copy) @@ -1598,7 +1598,7 @@ /* Keep from trying again in case the hints file is bad. */ hints = ""; - if ((fd = open(ld_elf_hints_path, O_RDONLY)) == -1) + if ((fd = open(ld_elf_hints_path, O_RDONLY | O_CLOEXEC)) == -1) return (NULL); if (read(fd, &hdr, sizeof hdr) != sizeof hdr || hdr.magic != ELFHINTS_MAGIC || @@ -2046,13 +2046,13 @@ */ fd = -1; if (fd_u == -1) { - if ((fd = open(path, O_RDONLY)) == -1) { + if ((fd = open(path, O_RDONLY | O_CLOEXEC)) == -1) { _rtld_error("Cannot open \"%s\"", path); free(path); return (NULL); } } else { - fd = dup(fd_u); + fd = fcntl(fd_u, F_DUPFD_CLOEXEC, 0); if (fd == -1) { _rtld_error("Cannot dup fd"); free(path); dlopen_exec_race.c: #include <sys/types.h> #include <sys/wait.h> #include <dlfcn.h> #include <err.h> #include <errno.h> #include <pthread.h> #include <spawn.h> #include <stdio.h> extern char **environ; static void * dlopen_thread(void *arg) { const char *path = arg; void *handle; for (;;) { handle = dlopen(path, RTLD_LOCAL | RTLD_NOW); if (handle == NULL) continue; dlclose(handle); } return NULL; } static void filestat_loop(void) { int error, status; pid_t pid, wpid; for (;;) { error = posix_spawnp(&pid, "sh", NULL, NULL, (char *[]){ "sh", "-c", "procstat -f $$", NULL }, environ); if (error != 0) errc(1, error, "posix_spawnp"); do wpid = waitpid(pid, &status, 0); while (wpid == -1 && errno == EINTR); if (wpid == -1) err(1, "waitpid"); if (status != 0) errx(1, "sh -c failed"); } } int main(int argc, char *argv[]) { pthread_t td; int error; if (argc != 2) { fprintf(stderr, "Usage: %s dso\n", argv[0]); return 1; } error = pthread_create(&td, NULL, dlopen_thread, argv[1]); if (error != 0) errc(1, error, "pthread_create"); filestat_loop(); return 0; } fdlopen_exec_race.c: #include <sys/types.h> #include <sys/wait.h> #include <dlfcn.h> #include <err.h> #include <errno.h> #include <fcntl.h> #include <pthread.h> #include <spawn.h> #include <stdio.h> #include <unistd.h> extern char **environ; static void * dlopen_thread(void *arg) { const char *path = arg; int fd; void *handle; for (;;) { fd = open(path, O_RDONLY | O_CLOEXEC); if (fd == -1) err(1, "open %s", path); handle = fdlopen(fd, RTLD_LOCAL | RTLD_NOW); close(fd); if (handle == NULL) continue; dlclose(handle); } return NULL; } static void filestat_loop(void) { int error, status; pid_t pid, wpid; for (;;) { error = posix_spawnp(&pid, "sh", NULL, NULL, (char *[]){ "sh", "-c", "procstat -f $$", NULL }, environ); if (error != 0) errc(1, error, "posix_spawnp"); do wpid = waitpid(pid, &status, 0); while (wpid == -1 && errno == EINTR); if (wpid == -1) err(1, "waitpid"); if (status != 0) errx(1, "sh -c failed"); } } int main(int argc, char *argv[]) { pthread_t td; int error; if (argc != 2) { fprintf(stderr, "Usage: %s dso\n", argv[0]); return 1; } error = pthread_create(&td, NULL, dlopen_thread, argv[1]); if (error != 0) errc(1, error, "pthread_create"); filestat_loop(); return 0; } -- Jilles Tjoelker
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?20121104143727.GB35520>