From: Brian Witte <brianwitte@mailfence.com>
To: Kees Cook <kees@kernel.org>
Cc: Brian Witte <brianwitte@mailfence.com>,
Alexander Viro <viro@zeniv.linux.org.uk>,
Christian Brauner <brauner@kernel.org>, Jan Kara <jack@suse.cz>,
Lorenzo Stoakes <ljs@kernel.org>, Jann Horn <jannh@google.com>,
Helge Deller <deller@gmx.de>,
Justin Stitt <justinstitt@google.com>,
Daniel Micay <danielmicay@gmail.com>,
Shuah Khan <shuah@kernel.org>,
linux-fsdevel@vger.kernel.org, linux-mm@kvack.org,
linux-hardening@vger.kernel.org, linux-kselftest@vger.kernel.org,
linux-kernel@vger.kernel.org
Subject: [PATCH 1/2] exec: randomize the argument block's offset within its page
Date: Fri, 23 Oct 2026 03:54:41 +0200 (CEST) [thread overview]
Message-ID: <20261023015345.1594514-2-brianwitte@mailfence.com> (raw)
In-Reply-To: <20261023015345.1594514-1-brianwitte@mailfence.com>
bprm->p starts one word below STACK_TOP_MAX, and setup_arg_pages()
moves the stack only by whole pages, so the argv, envp and execfn
strings sit at the same offset within their page on every exec. Three
execs on x86_64 put argv[0] at 0x7ffeee30af9f, 0x7ffdf3709f9f and
0x7ffc871e0f9f.
Lower bprm->p by a random amount under a page before the strings are
copied: about 12 bits of entropy with 4K pages and 16 with 64K.
bprm->argmin is derived from bprm->p, so the argument size limit is
unchanged, but up to a page of RLIMIT_STACK is used.
ADDR_NO_RANDOMIZE disables the offset; PF_RANDOMIZE cannot be used,
since load_elf_binary() sets it after the strings are copied.
PER_CLEAR_ON_SETID also clears the flag for set-id execs only after the
copy, and moving the strings then would mean copying them all. So
bprm_fill_uid()'s set-id checks are repeated early, without its
inode_lock(), and a set-id exec gets the offset regardless of the flag.
A racing chmod or sysctl write can make the offset disagree with the ELF
loader but never affects the credentials. Only the file passed to
execve() is checked: set-id interpreters, file capabilities and LSM
transitions keep today's fixed offset under the flag. The checks need
bprm->file, so they run in bprm_mm_init(), not create_init_stack_vma().
The idea comes from PaX RANDUSTACK; linux-hardened carries a version by
Daniel Micay and Levente Polyak.
Suggested-by: Daniel Micay <danielmicay@gmail.com>
Link: https://github.com/KSPP/linux/issues/283
Signed-off-by: Brian Witte <brianwitte@mailfence.com>
---
fs/exec.c | 32 ++++++++++++++++++++++++++++++++
1 file changed, 32 insertions(+)
diff --git a/fs/exec.c b/fs/exec.c
index 5d7b0fe32dbf..1070f0799b27 100644
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -70,6 +70,7 @@
#include <linux/user_events.h>
#include <linux/rseq.h>
#include <linux/ksm.h>
+#include <linux/random.h>
#include <linux/uaccess.h>
#include <asm/mmu_context.h>
@@ -195,6 +196,33 @@ static bool valid_arg_len(struct linux_binprm *bprm, long len)
return len <= MAX_ARG_STRLEN;
}
+/*
+ * Called before credentials exist: treat ADDR_NO_RANDOMIZE as cleared for
+ * files bprm_fill_uid() will treat as set-id.
+ */
+static bool bprm_randomize_args(struct linux_binprm *bprm)
+{
+ struct file *file = bprm->file;
+ const struct mnt_idmap *idmap = file_mnt_idmap(file);
+ struct inode *inode = file_inode(file);
+ /* bprm->cred, not yet set, will be in the caller's namespace. */
+ struct user_namespace *ns = current_user_ns();
+ umode_t mode;
+
+ if (!READ_ONCE(randomize_va_space))
+ return false;
+ if (!(current->personality & ADDR_NO_RANDOMIZE))
+ return true;
+ if (!mnt_may_suid(file->f_path.mnt) || task_no_new_privs(current))
+ return false;
+ mode = READ_ONCE(inode->i_mode);
+ if (!(mode & S_ISUID) &&
+ (mode & (S_ISGID | S_IXGRP)) != (S_ISGID | S_IXGRP))
+ return false;
+ return vfsuid_has_mapping(ns, i_uid_into_vfsuid(idmap, inode)) &&
+ vfsgid_has_mapping(ns, i_gid_into_vfsgid(idmap, inode));
+}
+
#else
static inline void acct_arg_size(struct linux_binprm *bprm, unsigned long pages)
@@ -279,6 +307,10 @@ static int bprm_mm_init(struct linux_binprm *bprm)
err = create_init_stack_vma(bprm->mm, &bprm->vma, &bprm->p);
if (err)
goto err;
+
+ /* Randomize the strings' offset within their page; keep the top word. */
+ if (bprm_randomize_args(bprm))
+ bprm->p -= get_random_u32_below(PAGE_SIZE - sizeof(void *));
#endif
return 0;
--
2.47.3
next prev parent reply other threads:[~2026-10-23 1:54 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-23 1:54 [PATCH 0/2] exec: randomize the argument block's in-page offset Brian Witte
2026-10-23 1:54 ` Brian Witte [this message]
2026-10-23 1:54 ` [PATCH 2/2] selftests/exec: test " Brian Witte
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261023015345.1594514-2-brianwitte@mailfence.com \
--to=brianwitte@mailfence.com \
--cc=brauner@kernel.org \
--cc=danielmicay@gmail.com \
--cc=deller@gmx.de \
--cc=jack@suse.cz \
--cc=jannh@google.com \
--cc=justinstitt@google.com \
--cc=kees@kernel.org \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-hardening@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=ljs@kernel.org \
--cc=shuah@kernel.org \
--cc=viro@zeniv.linux.org.uk \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®