All Virtuozzo development lists (kernel + QEMU)
 help / color / mirror / Atom feed
From: Konstantin Khorenko <khorenko@virtuozzo.com>
To: Pavel Tikhomirov <ptikhomirov@virtuozzo.com>,
	Andrey Drobyshev <andrey.drobyshev@virtuozzo.com>,
	Vasileios Almpanis <vasileios.almpanis@virtuozzo.com>
Cc: devel@openvz.org
Subject: [Devel] [PATCH hci-8.1 4/7] oracle/exec, elf: look for the opt-in note in every PT_NOTE segment
Date: Wed, 14 Oct 2026 19:12:23 +0200	[thread overview]
Message-ID: <20261014-b4-prep-khorenko-vstor-149847-3-doexec-oracle-fixes-v1-4-e34cbe0d1440@virtuozzo.com> (raw)
In-Reply-To: <20261014-b4-prep-khorenko-vstor-149847-3-doexec-oracle-fixes-v1-0-e34cbe0d1440@virtuozzo.com>

load_elf_binary() keeps only the last PT_NOTE segment it sees and looks
for the preserved-mem-ok note there. Linkers group notes into segments
by alignment: .note.gnu.property, aligned to 8, gets a PT_NOTE of its
own, and the 4-aligned notes share another. ld.bfd happens to put the
4-aligned one last, but another linker or a linker script can put the
segment with the opt-in elsewhere. The binary then silently gets no
preserved memory, and a live update loses the guest RAM it meant to
keep.

Check the notes of every PT_NOTE segment. The last segment, the only one
read before, keeps its check: its first note of the opt-in type fails
the exec with ENOEXEC unless it is the opt-in, that is when its name
size is over 17, its name is another or it is cut off by the end of the
segment. In the other segments only the opt-in counts and anything else
is skipped, so the notes refuse nothing the old check accepted. The
opt-in is recorded in bprm only after all segments passed, so that an
ENOEXEC does not leave it behind for the next binfmt handler. The walk
keeps the 4-byte padding it always used, so in an 8-aligned segment it
stays in step only with notes whose name size rounds up to an odd
multiple of 4 and whose descriptor size rounds up to a multiple of 8, as
the GNU property notes have. It stops at a note whose sizes run past the
segment, which on a 32-bit kernel could wrap the offset and loop
forever.

The notes are read for every ELF binary. A segment too short for a
note, larger than MAX_FILE_NOTE_SIZE or past the end of the file holds
nothing a linker emits and is skipped; the exec of a binary whose last
PT_NOTE was like that used to fail with ENOEXEC, EIO or EINVAL. An
error reading a segment, or allocating the buffer for it, fails the
exec, since skipping it could drop the opt-in and the memory with it.

Fixes: d07a690048ce ("oracle/exec, elf: require opt-in for accepting preserved mem")
https://virtuozzo.atlassian.net/browse/VSTOR-150232

Feature: oracle/mm: MADV_DOEXEC madvise() flag
Assisted-by: LLM
Signed-off-by: Konstantin Khorenko <khorenko@virtuozzo.com>
---
 fs/binfmt_elf.c | 87 ++++++++++++++++++++++++++++++++++-----------------------
 1 file changed, 52 insertions(+), 35 deletions(-)

diff --git a/fs/binfmt_elf.c b/fs/binfmt_elf.c
index a5689a2844d43..9cc84cdbb60fb 100644
--- a/fs/binfmt_elf.c
+++ b/fs/binfmt_elf.c
@@ -837,16 +837,19 @@ static int parse_elf_properties(struct file *f, const struct elf_phdr *phdr,
 
 static int get_elf_notes(struct linux_binprm *bprm, struct elf_phdr *phdr, char **notes, size_t *notes_sz)
 {
+	u64 isize = i_size_read(file_inode(bprm->file));
 	char *data;
 	size_t datasz;
 	int ret;
 
-	if (!phdr)
-		return 0;
-
+	/*
+	 * Skip a segment too short for a note, too large to read or past the
+	 * end of the file; an error reading it is fatal.
+	 */
 	datasz = phdr->p_filesz;
-	if ((datasz > MAX_FILE_NOTE_SIZE) || (datasz < sizeof(struct elf_note)))
-		return -ENOEXEC;
+	if (datasz > MAX_FILE_NOTE_SIZE || datasz < sizeof(struct elf_note) ||
+	    phdr->p_offset > isize || datasz > isize - phdr->p_offset)
+		return 0;
 
 	data = kvmalloc(datasz, GFP_KERNEL);
 	if (!data)
@@ -866,10 +869,17 @@ static int get_elf_notes(struct linux_binprm *bprm, struct elf_phdr *phdr, char
 #define PRESERVED_MEM_OK_STRING		"preserved-mem-ok"
 #define SZ_PRESERVED_MEM_OK_STRING	sizeof(PRESERVED_MEM_OK_STRING)
 
-static int check_preserved_mem_ok(struct linux_binprm *bprm, const char *data, const size_t datasz)
+/*
+ * 1 if the notes hold the opt-in. In the last PT_NOTE segment the first note
+ * of the opt-in type fails the exec with -ENOEXEC unless it is the opt-in; in
+ * the other segments only the opt-in counts.
+ */
+static int check_preserved_mem_ok(const char *data, const size_t datasz,
+				  bool last)
 {
 	size_t off = 0;
 	size_t remain;
+	u64 next;
 
 	if (!data)
 		return 0;
@@ -887,21 +897,21 @@ static int check_preserved_mem_ok(struct linux_binprm *bprm, const char *data, c
 		off += sizeof(*nhdr);
 		remain -= sizeof(*nhdr);
 
-		if (nhdr->n_type != 0x07c1feed) {
-			off += roundup(nhdr->n_namesz, 4) + roundup(nhdr->n_descsz, 4);
-			continue;
+		if (nhdr->n_type == 0x07c1feed) {
+			name = data + off;
+			if (nhdr->n_namesz <= SZ_PRESERVED_MEM_OK_STRING &&
+			    remain >= SZ_PRESERVED_MEM_OK_STRING &&
+			    !strncmp(name, PRESERVED_MEM_OK_STRING,
+				     SZ_PRESERVED_MEM_OK_STRING))
+				return 1;
+			if (last)
+				return -ENOEXEC;
 		}
-
-		if (nhdr->n_namesz > SZ_PRESERVED_MEM_OK_STRING)
-			return -ENOEXEC;
-
-		name =  data + off;
-		if (remain < SZ_PRESERVED_MEM_OK_STRING ||
-		    strncmp(name, PRESERVED_MEM_OK_STRING, SZ_PRESERVED_MEM_OK_STRING))
-			return -ENOEXEC;
-
-		bprm->accepts_preserved_mem = 1;
-		break;
+		next = round_up((u64)nhdr->n_namesz, 4) +
+		       round_up((u64)nhdr->n_descsz, 4);
+		if (next > remain)
+			break;
+		off += next;
 	}
 
 	return 0;
@@ -915,9 +925,7 @@ static int load_elf_binary(struct linux_binprm *bprm)
 	unsigned long error;
 	struct elf_phdr *elf_ppnt, *elf_phdata, *interp_elf_phdata = NULL;
 	struct elf_phdr *elf_property_phdata = NULL;
-	struct elf_phdr *elf_notes_phdata = NULL;
-	char *elf_notes = NULL;
-	size_t elf_notes_sz = 0;
+	int accepts_preserved_mem = 0, last_note = -1;
 	unsigned long elf_brk;
 	bool brk_moved = false;
 	int retval, i;
@@ -1028,10 +1036,6 @@ static int load_elf_binary(struct linux_binprm *bprm)
 				executable_stack = EXSTACK_DISABLE_X;
 			break;
 
-		case PT_NOTE:
-			elf_notes_phdata = elf_ppnt;
-			break;
-
 		case PT_LOPROC ... PT_HIPROC:
 			retval = arch_elf_pt_proc(elf_ex, elf_ppnt,
 						  bprm->file, false,
@@ -1097,13 +1101,28 @@ static int load_elf_binary(struct linux_binprm *bprm)
 	if (retval)
 		goto out_free_dentry;
 
-	retval = get_elf_notes(bprm, elf_notes_phdata, &elf_notes, &elf_notes_sz);
-	if (retval)
-		goto out_free_dentry;
+	/* The linker spreads notes over several segments by their alignment. */
+	for (i = 0, elf_ppnt = elf_phdata; i < elf_ex->e_phnum; i++, elf_ppnt++)
+		if (elf_ppnt->p_type == PT_NOTE)
+			last_note = i;
+	for (i = 0, elf_ppnt = elf_phdata; i < elf_ex->e_phnum; i++, elf_ppnt++) {
+		size_t elf_notes_sz = 0;
+		char *elf_notes = NULL;
 
-	retval = check_preserved_mem_ok(bprm, elf_notes, elf_notes_sz);
-	if (retval)
-		goto out_free_dentry;
+		if (elf_ppnt->p_type != PT_NOTE)
+			continue;
+		retval = get_elf_notes(bprm, elf_ppnt, &elf_notes, &elf_notes_sz);
+		if (retval)
+			goto out_free_dentry;
+		retval = check_preserved_mem_ok(elf_notes, elf_notes_sz,
+						i == last_note);
+		kvfree(elf_notes);
+		if (retval < 0)
+			goto out_free_dentry;
+		accepts_preserved_mem |= retval;
+	}
+	/* Only now: an ENOEXEC above lets another binfmt handler try the file. */
+	bprm->accepts_preserved_mem = accepts_preserved_mem;
 
 	/* Flush all traces of the currently running executable */
 	retval = begin_new_exec(bprm);
@@ -1381,7 +1400,6 @@ static int load_elf_binary(struct linux_binprm *bprm)
 		}
 	}
 
-	kvfree(elf_notes);
 	kfree(elf_phdata);
 
 	set_binfmt(&elf_format);
@@ -1486,7 +1504,6 @@ static int load_elf_binary(struct linux_binprm *bprm)
 	if (interpreter)
 		fput(interpreter);
 out_free_ph:
-	kvfree(elf_notes);
 	kfree(elf_phdata);
 	goto out;
 }

-- 
2.43.0

_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel

  parent reply	other threads:[~2026-10-14 17:13 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-14 17:12 [Devel] [PATCH hci-8.1 0/7] mm, exec: fix the MADV_DOEXEC code we carry from Oracle Konstantin Khorenko
2026-10-14 17:12 ` [Devel] [PATCH hci-8.1 1/7] oracle/mm: take a file reference when preserving a file-backed VMA Konstantin Khorenko
2026-10-14 17:12 ` [Devel] [PATCH hci-8.1 2/7] oracle/mm: refuse MADV_DOEXEC on special mappings Konstantin Khorenko
2026-10-14 17:12 ` [Devel] [PATCH hci-8.1 3/7] oracle/mm: keep a MADV_DOEXEC range from becoming executable Konstantin Khorenko
2026-10-14 17:12 ` Konstantin Khorenko [this message]
2026-10-14 17:12 ` [Devel] [PATCH hci-8.1 5/7] exec: refuse to preserve memory across a privilege-changing exec Konstantin Khorenko
2026-10-14 17:12 ` [Devel] [PATCH hci-8.1 6/7] selftests/mm: check that a setuid exec refuses preserved memory Konstantin Khorenko
2026-10-14 17:12 ` [Devel] [PATCH hci-8.1 7/7] selftests/mm: check MADV_DOEXEC on /dev/zero and exec permission Konstantin Khorenko

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=20261014-b4-prep-khorenko-vstor-149847-3-doexec-oracle-fixes-v1-4-e34cbe0d1440@virtuozzo.com \
    --to=khorenko@virtuozzo.com \
    --cc=andrey.drobyshev@virtuozzo.com \
    --cc=devel@openvz.org \
    --cc=ptikhomirov@virtuozzo.com \
    --cc=vasileios.almpanis@virtuozzo.com \
    /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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.