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


Reply via email to