Re: [PATCH 1/2] fs: support $ORIGIN in ELF interpreter paths

From: Jori Koolstra

Date: Mon Jun 22 2026 - 05:54:36 EST


Hi Farid,

On Sun, Jun 21, 2026 at 09:39:33PM -0700, Farid Zakaria wrote:
> 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

This isn't a requirement from the community or anything, but I always
find it useful if I see an Assisted-by tag to know what assistence was
actually delivered by an LLM. Otherwise we might as well add
assisted-by tags for any editor.

Talking about LLMs, your patch has some issues flagged by Sashiko[1].
Please take a look.


> Signed-off-by: Farid Zakaria <farid.m.zakaria@xxxxxxxxx>
> ---
> 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
>

Thanks,
Jori.

[1]: https://sashiko.dev/#/patchset/20260622043934.179879-1-farid.m.zakaria%40gmail.com