[PATCH] elf: add AT_ARGV and AT_ENVV auxiliary vector entries

From: ahajkova

Date: Tue Jun 23 2026 - 04:35:40 EST


From: Alexandra Hájková <ahajkova@xxxxxxxxxx>

Add AT_ARGV (52), which contains the address of the argv pointer
array on the initial process stack, and AT_ENVV (53), which contains
the address of the envp pointer array.

Add a kselftest (tools/testing/selftests/exec/test_auxv) that reads
/proc/self/auxv and verifies AT_ARGV and AT_ENVV point to the process's
own argv[] and envp[].

The motivation is to allow GDB to find argv and envp in core dumps
cleanly. Currently GDB locates them by scanning backwards through
stack memory from AT_EXECFN, which is fragile. With AT_ARGV and
AT_ENVV it can read the addresses directly from the core dump's
auxv note.

Signed-off-by: Alexandra Hájková <ahajkova@xxxxxxxxxx>
---
fs/binfmt_elf.c | 9 +++++-
include/linux/auxvec.h | 2 +-
include/uapi/linux/auxvec.h | 2 ++
tools/testing/selftests/exec/Makefile | 1 +
tools/testing/selftests/exec/test_auxv.c | 37 ++++++++++++++++++++++++
5 files changed, 49 insertions(+), 2 deletions(-)
create mode 100644 tools/testing/selftests/exec/test_auxv.c

diff --git a/fs/binfmt_elf.c b/fs/binfmt_elf.c
index 8e89cc5b2820..6a0830a63335 100644
--- a/fs/binfmt_elf.c
+++ b/fs/binfmt_elf.c
@@ -182,6 +182,8 @@ create_elf_tables(struct linux_binprm *bprm, const struct elfhdr *exec,
int ei_index;
const struct cred *cred = current_cred();
struct vm_area_struct *vma;
+ elf_addr_t *at_argv_val;
+ elf_addr_t *at_envv_val;

/*
* In some cases (e.g. Hyper-Threading), we want to avoid L1
@@ -288,6 +290,10 @@ create_elf_tables(struct linux_binprm *bprm, const struct elfhdr *exec,
NEW_AUX_ENT(AT_RSEQ_FEATURE_SIZE, offsetof(struct rseq, end));
NEW_AUX_ENT(AT_RSEQ_ALIGN, __alignof__(struct rseq));
#endif
+ at_argv_val = elf_info + 1;
+ NEW_AUX_ENT(AT_ARGV, 0);
+ at_envv_val = elf_info + 1;
+ NEW_AUX_ENT(AT_ENVV, 0);
#undef NEW_AUX_ENT
/* AT_NULL is zero; clear the rest too */
memset(elf_info, 0, (char *)mm->saved_auxv +
@@ -309,7 +315,8 @@ create_elf_tables(struct linux_binprm *bprm, const struct elfhdr *exec,
#else
sp = (elf_addr_t __user *)bprm->p;
#endif
-
+ *at_argv_val = (unsigned long)(sp + 1);
+ *at_envv_val = (unsigned long)(sp + (argc + 2));

/*
* Grow the stack manually; some architectures have a limit on how
diff --git a/include/linux/auxvec.h b/include/linux/auxvec.h
index 8bcb9b726262..7184de95b89d 100644
--- a/include/linux/auxvec.h
+++ b/include/linux/auxvec.h
@@ -4,6 +4,6 @@

#include <uapi/linux/auxvec.h>

-#define AT_VECTOR_SIZE_BASE 24 /* NEW_AUX_ENT entries in auxiliary table */
+#define AT_VECTOR_SIZE_BASE 26 /* NEW_AUX_ENT entries in auxiliary table */
/* number of "#define AT_.*" above, minus {AT_NULL, AT_IGNORE, AT_NOTELF} */
#endif /* _LINUX_AUXVEC_H */
diff --git a/include/uapi/linux/auxvec.h b/include/uapi/linux/auxvec.h
index cc61cb9b3e9a..e7af32709aba 100644
--- a/include/uapi/linux/auxvec.h
+++ b/include/uapi/linux/auxvec.h
@@ -40,5 +40,7 @@
#ifndef AT_MINSIGSTKSZ
#define AT_MINSIGSTKSZ 51 /* minimal stack size for signal delivery */
#endif
+#define AT_ARGV 52 /* address of argv[] */
+#define AT_ENVV 53 /* address of envp[] */

#endif /* _UAPI_LINUX_AUXVEC_H */
diff --git a/tools/testing/selftests/exec/Makefile b/tools/testing/selftests/exec/Makefile
index 45a3cfc435cf..4370a0a65d22 100644
--- a/tools/testing/selftests/exec/Makefile
+++ b/tools/testing/selftests/exec/Makefile
@@ -20,6 +20,7 @@ TEST_FILES := Makefile
TEST_GEN_PROGS += recursion-depth
TEST_GEN_PROGS += null-argv
TEST_GEN_PROGS += check-exec
+TEST_GEN_PROGS += test_auxv

EXTRA_CLEAN := $(OUTPUT)/subdir.moved $(OUTPUT)/execveat.moved $(OUTPUT)/xxxxx* \
$(OUTPUT)/S_I*.test
diff --git a/tools/testing/selftests/exec/test_auxv.c b/tools/testing/selftests/exec/test_auxv.c
new file mode 100644
index 000000000000..5d1215562d40
--- /dev/null
+++ b/tools/testing/selftests/exec/test_auxv.c
@@ -0,0 +1,37 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <stdio.h>
+#include <stdlib.h>
+#include <linux/auxvec.h>
+#include "kselftest.h"
+
+int main(int argc, char **argv, char **envp)
+{
+ FILE *file = fopen("/proc/self/auxv", "rb");
+ int argv_found = 0;
+ int envp_found = 0;
+ struct {
+ unsigned long type;
+ unsigned long val;
+ } entry;
+
+ if (file == NULL)
+ return 1;
+
+ ksft_print_header();
+ ksft_set_plan(1);
+ while (fread(&entry, sizeof(entry), 1, file) == 1) {
+ if (entry.type == 0)
+ break;
+ if (entry.type == AT_ARGV && entry.val == (unsigned long)argv)
+ argv_found++;
+ if (entry.type == AT_ENVV && entry.val == (unsigned long)envp)
+ envp_found++;
+ }
+ if (argv_found && envp_found)
+ ksft_test_result_pass("AT_ARGV and AT_ENVV\n");
+ else
+ ksft_test_result_fail("AT_ARGV and AT_ENVV\n");
+
+ fclose(file);
+ ksft_finished();
+}
--
2.47.3