Currently, standard ELF and ELF FDPIC loaders expect a fixed path to the dynamic linker/interpreter (PT_INTERP). However, for systems utilizing relocatable dynamic interpreters (such as Nix/store-based environments), hardcoding this path is inflexible and breaks binary portability.
Introduce support for resolving the $ORIGIN placeholder in the ELF interpreter path. This maps the dynamic linker relative to the path of the binary being executed, matching user-space origin resolution. To avoid code duplication, implement a shared 'resolve_elf_interpreter()' helper in the VFS exec layer. For safety, limit detection strictly to the prefix string "$ORIGIN" to prevent complex parsing exploits. Assisted-by: Antigravity:Gemini-Pro Signed-off-by: Farid Zakaria <[email protected]> --- fs/binfmt_elf.c | 11 +++++++++-- fs/binfmt_elf_fdpic.c | 15 +++++++++++++-- fs/exec.c | 42 +++++++++++++++++++++++++++++++++++++++++ include/linux/binfmts.h | 2 ++ 4 files changed, 66 insertions(+), 4 deletions(-) diff --git a/fs/binfmt_elf.c b/fs/binfmt_elf.c index 16a56b6b3..af11f96ae 100644 --- a/fs/binfmt_elf.c +++ b/fs/binfmt_elf.c @@ -872,7 +872,7 @@ static int load_elf_binary(struct linux_binprm *bprm) elf_ppnt = elf_phdata; for (i = 0; i < elf_ex->e_phnum; i++, elf_ppnt++) { - char *elf_interpreter; + char *elf_interpreter, *resolved_interp; if (elf_ppnt->p_type == PT_GNU_PROPERTY) { elf_property_phdata = elf_ppnt; @@ -904,8 +904,15 @@ static int load_elf_binary(struct linux_binprm *bprm) if (elf_interpreter[elf_ppnt->p_filesz - 1] != '\0') goto out_free_interp; - interpreter = open_exec(elf_interpreter); + resolved_interp = resolve_elf_interpreter(bprm, elf_interpreter); kfree(elf_interpreter); + if (IS_ERR(resolved_interp)) { + retval = PTR_ERR(resolved_interp); + goto out_free_ph; + } + + interpreter = open_exec(resolved_interp); + kfree(resolved_interp); retval = PTR_ERR(interpreter); if (IS_ERR(interpreter)) goto out_free_ph; diff --git a/fs/binfmt_elf_fdpic.c b/fs/binfmt_elf_fdpic.c index 7e3108489..e85727d71 100644 --- a/fs/binfmt_elf_fdpic.c +++ b/fs/binfmt_elf_fdpic.c @@ -230,7 +230,9 @@ static int load_elf_fdpic_binary(struct linux_binprm *bprm) for (i = 0; i < exec_params.hdr.e_phnum; i++, phdr++) { switch (phdr->p_type) { - case PT_INTERP: + case PT_INTERP: { + char *resolved_interp; + retval = -ENOMEM; if (phdr->p_filesz > PATH_MAX) goto error; @@ -259,7 +261,15 @@ static int load_elf_fdpic_binary(struct linux_binprm *bprm) kdebug("Using ELF interpreter %s", interpreter_name); /* replace the program with the interpreter */ - interpreter = open_exec(interpreter_name); + resolved_interp = resolve_elf_interpreter(bprm, interpreter_name); + kfree(interpreter_name); + if (IS_ERR(resolved_interp)) { + retval = PTR_ERR(resolved_interp); + goto error; + } + + interpreter = open_exec(resolved_interp); + kfree(resolved_interp); retval = PTR_ERR(interpreter); if (IS_ERR(interpreter)) { interpreter = NULL; @@ -284,6 +294,7 @@ static int load_elf_fdpic_binary(struct linux_binprm *bprm) interp_params.hdr = *((struct elfhdr *) bprm->buf); break; + } case PT_LOAD: #ifdef CONFIG_MMU diff --git a/fs/exec.c b/fs/exec.c index b92fe7db1..0978ae613 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -2024,6 +2024,48 @@ static int __init init_fs_exec_sysctls(void) fs_initcall(init_fs_exec_sysctls); #endif /* CONFIG_SYSCTL */ +char *resolve_elf_interpreter(struct linux_binprm *bprm, const char *elf_interpreter) +{ + char *pathbuf, *path, *slash, *resolved; + + if (strncmp(elf_interpreter, "$ORIGIN", 7) != 0) { + char *ret = kstrdup(elf_interpreter, GFP_KERNEL); + + return ret ? ret : ERR_PTR(-ENOMEM); + } + + pathbuf = kmalloc(PATH_MAX, GFP_KERNEL); + if (!pathbuf) + return ERR_PTR(-ENOMEM); + + path = file_path(bprm->file, pathbuf, PATH_MAX); + if (IS_ERR(path)) { + kfree(pathbuf); + return (char *)path; + } + + slash = strrchr(path, '/'); + if (slash) { + if (slash == path) + *(slash + 1) = '\0'; + else + *slash = '\0'; + } else { + kfree(pathbuf); + char *ret = kstrdup(elf_interpreter, GFP_KERNEL); + + return ret ? ret : ERR_PTR(-ENOMEM); + } + + resolved = kasprintf(GFP_KERNEL, "%s%s", path, elf_interpreter + 7); + kfree(pathbuf); + if (!resolved) + return ERR_PTR(-ENOMEM); + + return resolved; +} +EXPORT_SYMBOL(resolve_elf_interpreter); + #ifdef CONFIG_EXEC_KUNIT_TEST #include "tests/exec_kunit.c" #endif diff --git a/include/linux/binfmts.h b/include/linux/binfmts.h index 2c77e383e..17419cd3d 100644 --- a/include/linux/binfmts.h +++ b/include/linux/binfmts.h @@ -150,4 +150,6 @@ extern ssize_t read_code(struct file *, unsigned long, loff_t, size_t); int kernel_execve(const char *filename, const char *const *argv, const char *const *envp); +char *resolve_elf_interpreter(struct linux_binprm *bprm, const char *elf_interpreter); + #endif /* _LINUX_BINFMTS_H */ -- 2.51.2

