From 1529aa918d879951b9619c2e16a3b10f5bacf77d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20PORTAY?= Date: Sun, 17 Mar 2024 15:29:18 +0100 Subject: [PATCH] dso, execve{,at}, posix_spawn: add __can_exec() This adds to internal function __can_exec() to run exec.sh if the program is SUID or a statically linked ELF or an interpreter-script. --- CHANGELOG.md | 2 + dso.c | 279 ++++++++++++++++++++++++++++++++++++++++++++++++++ execve.c | 10 +- execveat.c | 10 +- iamroot.c | 58 +++++++++++ iamroot.h | 3 + posix_spawn.c | 10 +- 7 files changed, 348 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c78957f..0de4be03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). filename without a slash - Do not output for the dynamic loader command in `exec.sh` to not pollute its output on stderr +- Run `exec.sh` if program a statically linked ELF executable even if not + chroot'ed ### Fixed diff --git a/dso.c b/dso.c index 19b941a6..869d6f9d 100644 --- a/dso.c +++ b/dso.c @@ -17,6 +17,8 @@ #include #include #include +#include +#include #ifdef __NetBSD__ #include #endif @@ -37,6 +39,7 @@ typedef struct { } __regex_t; extern int next_faccessat(int, const char *, int, int); +extern int next_fstat(int, struct stat *); extern int next_open(const char *, int, mode_t); extern void *next_dlopen(const char *, int); @@ -3640,3 +3643,279 @@ hidden int __ldso_posix_spawn(pid_t *pid, __warning("%s: Function not implemented", __func__); return __set_errno(ENOSYS, -1); } + +/* New API with addr and addrsiz from mmap() */ +static int __elf_header(void *addr, size_t addrsiz, Elf64_Ehdr *ehdr) +{ + if (sizeof(*ehdr) > addrsiz) + return __set_errno(ENOSPC, -1); + + memcpy(ehdr, addr, sizeof(*ehdr)); + + /* Not an ELF */ + if (memcmp(ehdr->e_ident, ELFMAG, SELFMAG) != 0) + return __set_errno(ENOEXEC, -1); + + /* It is a 32-bit ELF */ + if (ehdr->e_ident[EI_CLASS] == ELFCLASS32) { + Elf32_Ehdr *ehdr32 = (Elf32_Ehdr *)ehdr; + + ehdr32->e_type = __elf32_half(ehdr32, ehdr32->e_type); + ehdr32->e_machine = __elf32_half(ehdr32, ehdr32->e_machine); + ehdr32->e_version = __elf32_word(ehdr32, ehdr32->e_version); + ehdr32->e_entry = __elf32_address(ehdr32, ehdr32->e_entry); + ehdr32->e_phoff = __elf32_offset(ehdr32, ehdr32->e_phoff); + ehdr32->e_shoff = __elf32_offset(ehdr32, ehdr32->e_shoff); + ehdr32->e_flags = __elf32_word(ehdr32, ehdr32->e_flags); + ehdr32->e_ehsize = __elf32_half(ehdr32, ehdr32->e_ehsize); + ehdr32->e_phentsize = __elf32_half(ehdr32, ehdr32->e_phentsize); + ehdr32->e_phnum = __elf32_half(ehdr32, ehdr32->e_phnum); + ehdr32->e_shentsize = __elf32_half(ehdr32, ehdr32->e_phentsize); + ehdr32->e_shnum = __elf32_half(ehdr32, ehdr32->e_shnum); + ehdr32->e_shstrndx = __elf32_half(ehdr32, ehdr32->e_shstrndx); + + return 0; + } + + /* It is a 64-bit ELF */ + if (ehdr->e_ident[EI_CLASS] == ELFCLASS64) { + Elf64_Ehdr *ehdr64 = ehdr; + + ehdr64->e_type = __elf64_half(ehdr64, ehdr64->e_type); + ehdr64->e_machine = __elf64_half(ehdr64, ehdr64->e_machine); + ehdr64->e_version = __elf64_word(ehdr64, ehdr64->e_version); + ehdr64->e_entry = __elf64_address(ehdr64, ehdr64->e_entry); + ehdr64->e_phoff = __elf64_offset(ehdr64, ehdr64->e_phoff); + ehdr64->e_shoff = __elf64_offset(ehdr64, ehdr64->e_shoff); + ehdr64->e_flags = __elf64_word(ehdr64, ehdr64->e_flags); + ehdr64->e_ehsize = __elf64_half(ehdr64, ehdr64->e_ehsize); + ehdr64->e_phentsize = __elf64_half(ehdr64, ehdr64->e_phentsize); + ehdr64->e_phnum = __elf64_half(ehdr64, ehdr64->e_phnum); + ehdr64->e_shentsize = __elf64_half(ehdr64, ehdr64->e_phentsize); + ehdr64->e_shnum = __elf64_half(ehdr64, ehdr64->e_shnum); + ehdr64->e_shstrndx = __elf64_half(ehdr64, ehdr64->e_shstrndx); + + return 0; + } + + /* Unsupported yet! */ + return __set_errno(ENOTSUP, -1); +} + +static int __elf32_program_header(void *addr, size_t addrsiz, Elf32_Ehdr *ehdr, + Elf32_Phdr *phdr, off_t offset) +{ + if ((size_t)offset + sizeof(*phdr) > addrsiz) + return __set_errno(ENOSPC, -1); + + memcpy(phdr, addr + offset, sizeof(*phdr)); + + if (!__elf32_swap(ehdr)) + return 0; + + phdr->p_type = __bswap_32__(phdr->p_type); + phdr->p_offset = __bswap_32__(phdr->p_offset); + phdr->p_vaddr = __bswap_32__(phdr->p_vaddr); + phdr->p_paddr = __bswap_32__(phdr->p_paddr); + phdr->p_filesz = __bswap_32__(phdr->p_filesz); + phdr->p_memsz = __bswap_32__(phdr->p_memsz); + phdr->p_flags = __bswap_32__(phdr->p_flags); + phdr->p_align = __bswap_32__(phdr->p_align); + + return 0; +} + +static int __elf64_program_header(void *addr, size_t addrsiz, Elf64_Ehdr *ehdr, + Elf64_Phdr *phdr, off_t offset) +{ + if ((size_t)offset + sizeof(*phdr) > addrsiz) + return __set_errno(ENOSPC, -1); + + memcpy(phdr, addr + offset, sizeof(*phdr)); + + if (!__elf64_swap(ehdr)) + return 0; + + phdr->p_type = __bswap_32__(phdr->p_type); + phdr->p_flags = __bswap_32__(phdr->p_flags); + phdr->p_offset = __bswap_64__(phdr->p_offset); + phdr->p_vaddr = __bswap_64__(phdr->p_vaddr); + phdr->p_paddr = __bswap_64__(phdr->p_paddr); + phdr->p_filesz = __bswap_64__(phdr->p_filesz); + phdr->p_memsz = __bswap_64__(phdr->p_memsz); + phdr->p_align = __bswap_64__(phdr->p_align); + + return 0; +} + +static int __elf32_iterate_for_pt(void *addr, + size_t addrsiz, + Elf32_Ehdr *ehdr, + unsigned int p_type, + int (*callback)(const void *, size_t, void *), + void *data) +{ + const int errno_save = errno; + int i, num; + off_t off; + + if (!callback) + return __set_errno(EINVAL, -1); + + /* Look for the segment */ + off = ehdr->e_phoff; + num = ehdr->e_phnum; + for (i = 0; i < num; i++) { + Elf32_Phdr phdr; + int err; + + err = __elf32_program_header(addr, addrsiz, ehdr, &phdr, off); + if (err == -1) + return -1; + + off += sizeof(phdr); + + /* Not the segment */ + if (phdr.p_type != p_type) + continue; + + if (phdr.p_offset > addrsiz) + return __set_errno(ENOSPC, -1); + + err = callback(addr + phdr.p_offset, phdr.p_filesz, data); + if (err == -1) + return -1; + + if (err == 0) + return __set_errno(errno_save, err); + } + + return __set_errno(ENOENT, -1); +} + +static int __elf64_iterate_for_pt(void *addr, + size_t addrsiz, + Elf64_Ehdr *ehdr, + unsigned int p_type, + int (*callback)(const void *, size_t, void *), + void *data) +{ + const int errno_save = errno; + int i, num; + off_t off; + + if (!callback) + return __set_errno(EINVAL, -1); + + /* Look for the segment */ + off = ehdr->e_phoff; + num = ehdr->e_phnum; + for (i = 0; i < num; i++) { + Elf64_Phdr phdr; + int err; + + err = __elf64_program_header(addr, addrsiz, ehdr, &phdr, off); + if (err == -1) + return -1; + + off += sizeof(phdr); + + /* Not the segment */ + if (phdr.p_type != p_type) + continue; + + if (phdr.p_offset > addrsiz) + return __set_errno(ENOSPC, -1); + + err = callback(addr + phdr.p_offset, phdr.p_filesz, data); + if (err == -1) + return -1; + + if (err == 0) + return __set_errno(errno_save, err); + } + + return __set_errno(ENOENT, -1); +} + +static int __elf_iterate_shared_object_for_pt(int fd, unsigned int p_type, + int (*callback)(const void *, size_t, void *), void *data) +{ + struct stat statbuf; + Elf64_Ehdr ehdr; + size_t addrsiz; + void *addr; + int err; + + err = next_fstat(fd, &statbuf); + if (err == -1) + return -1; + + addrsiz = statbuf.st_size; + addr = mmap(NULL, addrsiz, PROT_READ, MAP_PRIVATE, fd, 0); + if (addr == MAP_FAILED) + return -1; + + err = __elf_header(addr, addrsiz, &ehdr); + if (err == -1) + return -1; + + /* Not a linked program or shared object */ + if ((ehdr.e_type != ET_EXEC) && (ehdr.e_type != ET_DYN)) + return __set_errno(ENOEXEC, -1); + + /* It is a 32-bit ELF */ + if (ehdr.e_ident[EI_CLASS] == ELFCLASS32) + return __elf32_iterate_for_pt(addr, + addrsiz, + (Elf32_Ehdr *)&ehdr, + p_type, + callback, + data); + + /* It is a 64-bit ELF */ + if (ehdr.e_ident[EI_CLASS] == ELFCLASS64) + return __elf64_iterate_for_pt(addr, + addrsiz, + (Elf64_Ehdr *)&ehdr, + p_type, + callback, + data); + + /* Unsupported yet! */ + return __set_errno(ENOTSUP, -1); +} + +static int __elf_has_interp_callback(const void *data, size_t datasiz, + void *user) +{ + const char *interp = (const char *)data; + int *fd = (int *)user; + (void)datasiz; + (void)interp; + (void)fd; + + if (!data || !user) + return __set_errno(EINVAL, -1); + + __notice("%s: has interpreter '%s'\n", __fpath(*fd), interp); + + return 0; +} + +hidden int __elf_has_interp(int fd) +{ + const int errno_save = errno; + int err; + + err = __elf_iterate_shared_object_for_pt(fd, + PT_INTERP, + __elf_has_interp_callback, + &fd); + if (err == -1 && errno != ENOENT) + return -1; + if (err == -1) + return __set_errno(errno_save, 0); + + return __set_errno(errno_save, 1); +} diff --git a/execve.c b/execve.c index 2493b2d2..8f32f6b4 100644 --- a/execve.c +++ b/execve.c @@ -164,16 +164,10 @@ int execve(const char *path, char * const argv[], char * const envp[]) interparg[0] = *argv; /* original argv0 as argv0 */ - /* - * In secure-execution mode, preload pathnames containing slashes are - * ignored. Furthermore, shared objects are preloaded only from the - * standard search directories and only if they have set-user-ID mode - * bit enabled (which is not typical). - */ - ret = __is_suid(program); + ret = __can_exec(program); if (ret == -1) return -1; - else if (ret != 0) + else if (ret == 0) goto exec_sh; /* Do not proceed to any hack if not in chroot */ diff --git a/execveat.c b/execveat.c index c097d9e4..3483ca57 100644 --- a/execveat.c +++ b/execveat.c @@ -86,16 +86,10 @@ int execveat(int dfd, const char *path, char * const argv[], interparg[0] = *argv; /* original argv0 as argv0 */ - /* - * In secure-execution mode, preload pathnames containing slashes are - * ignored. Furthermore, shared objects are preloaded only from the - * standard search directories and only if they have set-user-ID mode - * bit enabled (which is not typical). - */ - ret = __is_suid(program); + ret = __can_exec(program); if (ret == -1) return -1; - else if (ret != 0) + else if (ret == 0) goto exec_sh; /* Do not proceed to any hack if not in chroot */ diff --git a/iamroot.c b/iamroot.c index a8e57a0d..eb6fe1e6 100644 --- a/iamroot.c +++ b/iamroot.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #if defined __linux__ || defined __FreeBSD__ #include @@ -23,11 +24,15 @@ #include "iamroot.h" +#define SCRIPTMAG "#!" +#define SSCRIPTMAG 2 + extern char *next_getcwd(char *, size_t); #ifndef __NetBSD__ extern int next_fstat(int, struct stat *); #endif extern int next_fstatat(int, const char *, struct stat *, int); +extern int next_open(const char *, int, mode_t); extern ssize_t next_readlinkat(int, const char *, char *, size_t); extern int next_scandir(const char *, struct dirent ***, int (*)(const struct dirent *), @@ -219,6 +224,59 @@ hidden const char *__basename(const char *path) return s+1; /* trailing-slash */ } +static int __fcan_exec(int fd) +{ + char magic[4]; + ssize_t siz; + + siz = read(fd, magic, sizeof(magic)); + if (siz == -1) + return -1; + else if ((size_t)siz < sizeof(magic)) + return __set_errno(EIO, -1); + + /* It is an interpreter-script */ + if (memcmp(magic, SCRIPTMAG, SSCRIPTMAG) == 0) + return 1; + + /* It is an ELF */ + if (memcmp(magic, ELFMAG, SELFMAG) == 0) + return __elf_has_interp(fd); + + /* Unsupported yet! */ + return __set_errno(ENOTSUP, -1); +} + +hidden int __can_exec(const char *path) +{ + int fd = -1, ret; + + /* + * In secure-execution mode, preload pathnames containing slashes are + * ignored. Furthermore, shared objects are preloaded only from the + * standard search directories and only if they have set-user-ID mode + * bit enabled (which is not typical). + */ + ret = __is_suid(path); + if (ret == -1) + return -1; + if (ret == 1) + return 0; + + fd = next_open(path, O_RDONLY | O_CLOEXEC, 0); + if (fd == -1) + return -1; + + /* + * Only ELF shared object and interpreter-script are supported. + */ + ret = __fcan_exec(fd); + + __close(fd); + + return ret; +} + int __path_setenv(const char *root, const char *name, const char *value, int overwrite) { diff --git a/iamroot.h b/iamroot.h index b8297bfd..857419f7 100755 --- a/iamroot.h +++ b/iamroot.h @@ -170,6 +170,7 @@ int __fis_fileat(int, const char *, int); int __fis_file(int); int __is_file(const char *); const char *__basename(const char *); +int __can_exec(const char *); char *__getenv(const char *); int __setenv(const char *, const char *, int); @@ -722,6 +723,8 @@ int __is_ldso(const char *); ssize_t __dl_access(const char *, int, char *, size_t); +int __elf_has_interp(int); + ssize_t __ldso_cache(const char *, char *, size_t); #if defined(__GLIBC__) && (defined(__aarch64__) || defined(__x86_64__)) diff --git a/posix_spawn.c b/posix_spawn.c index 37dd49f9..b645cd3e 100644 --- a/posix_spawn.c +++ b/posix_spawn.c @@ -99,16 +99,10 @@ int posix_spawn(pid_t *pid, const char *path, interparg[0] = *argv; /* original argv0 as argv0 */ - /* - * In secure-execution mode, preload pathnames containing slashes are - * ignored. Furthermore, shared objects are preloaded only from the - * standard search directories and only if they have set-user-ID mode - * bit enabled (which is not typical). - */ - ret = __is_suid(program); + ret = __can_exec(program); if (ret == -1) return -1; - else if (ret != 0) + else if (ret == 0) goto exec_sh; /* Do not proceed to any hack if not in chroot */