diff --git a/NEWS b/NEWS
index f9d90c5194..cacd3f8be6 100644
--- a/NEWS
+++ b/NEWS
@@ -9,6 +9,15 @@ Version 2.44
 
 Major new features:
 
+* When the dynamic linker is executed as a binfmt interpreter (for
+  example through a Linux binfmt_misc entry registered with the 'O'
+  flag) and the kernel passes the executed program as an open
+  descriptor in the AT_EXECFD auxiliary vector entry, the dynamic
+  linker now loads the program from that descriptor instead of
+  re-opening it by path.  Execute-only (--x) binaries work under such
+  handlers, and the descriptor refers to the file the kernel actually
+  access-checked, eliminating the re-open race.
+
 * A new tunable, glibc.elf.thp, is added to map read-only segments with
   Transparent Huge Pages (THP) if THP isn't disable in kernel.  When
   glibc.elf.thp is set to 1, malloc uses the actual kernel THP mode
diff --git a/elf/dl-load.c b/elf/dl-load.c
index 95404adae9..f2f14dd6f3 100644
--- a/elf/dl-load.c
+++ b/elf/dl-load.c
@@ -1625,11 +1625,14 @@ open_verify (const char *name, int fd,
       __set_errno (0);
       fbp->len = 0;
       assert (sizeof (fbp->buf) > sizeof (ElfW(Ehdr)));
-      /* Read in the header.  */
+      /* Read the header with pread: an executable descriptor handed to
+	 the loader may be shared, so its file position must be neither
+	 relied upon nor disturbed.  */
       do
 	{
-	  ssize_t retlen = __read_nocancel (fd, fbp->buf + fbp->len,
-					    sizeof (fbp->buf) - fbp->len);
+	  ssize_t retlen = __pread64_nocancel (fd, fbp->buf + fbp->len,
+					       sizeof (fbp->buf) - fbp->len,
+					       fbp->len);
 	  if (retlen <= 0)
 	    break;
 	  fbp->len += retlen;
@@ -2241,6 +2244,62 @@ _dl_map_object (struct link_map *loader, const char *name,
   return _dl_map_new_object (loader, name, type, trace_mode, mode, nsid);
 }
 
+/* Map in the main executable, already opened on FD.  The descriptor
+   comes from the kernel (AT_EXECFD, from a binfmt interpreter dispatch
+   that kept the executed binary open) or from an explicit loader
+   invocation.  NAME is the name the program is known by and is only
+   used for diagnostics; the canonical name used for $ORIGIN is derived
+   from the descriptor itself (__RTLD_OPENEXEC).  There may be no path
+   the program could be opened by: the descriptor is readable even for
+   an execute-only binary, and it refers to the very file the kernel
+   access-checked, so no path re-open takes its place.  */
+struct link_map *
+_dl_map_object_execfd (int fd, const char *name)
+{
+  struct filebuf fb;
+  bool found_other_class = false;
+
+  /* open_verify reads with pread and ignores the file position, so only
+     a descriptor that is not open at all needs rejecting (an explicit
+     loader invocation can be handed one).  */
+  if (__fcntl64_nocancel (fd, F_GETFD) == -1)
+    _dl_signal_error (errno, name, NULL,
+		      N_("cannot load main program from descriptor"));
+
+  fd = open_verify (name, fd, &fb, NULL, 0, __RTLD_OPENEXEC,
+		    &found_other_class, false);
+  if (__glibc_unlikely (fd == -1))
+    {
+      if (found_other_class)
+	_dl_signal_error (0, name, NULL,
+			  ELFW(CLASS) == ELFCLASS32
+			  ? N_("wrong ELF class: ELFCLASS64")
+			  : N_("wrong ELF class: ELFCLASS32"));
+      else if (errno == ENOENT)
+	/* The descriptor is open (checked above), so ENOENT here is
+	   open_verify's report of an incompatible ELF machine, not a
+	   missing file.  */
+	_dl_signal_error (0, name, NULL,
+			  N_("cannot load main program from descriptor:"
+			     " incompatible ELF machine"));
+      else
+	_dl_signal_error (errno, name, NULL,
+			  N_("cannot load main program from descriptor"));
+    }
+
+  char *realname = __strdup (name);
+  if (realname == NULL)
+    {
+      __close_nocancel (fd);
+      _dl_signal_error (ENOMEM, name, NULL,
+			N_("cannot allocate name record"));
+    }
+
+  return _dl_map_object_from_fd (name, NULL, fd, &fb, realname, NULL,
+				 lt_executable, __RTLD_OPENEXEC,
+				 __libc_stack_end, LM_ID_BASE);
+}
+
 
 struct add_path_state
 {
diff --git a/elf/rtld.c b/elf/rtld.c
index e5ba71fef1..3d383ae3b9 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -54,6 +54,7 @@
 #include <dl-audit-check.h>
 #include <dl-call_tls_init_tp.h>
 #include <dl-exec-post.h>
+#include <dl-standard-fds.h>
 
 #include <assert.h>
 
@@ -1044,10 +1045,11 @@ load_audit_modules (struct link_map *main_map, struct audit_list *audit_list)
     }
 }
 
-/* Check if the executable is not actually dynamically linked, and
-   invoke it directly in that case.  */
-static void
-rtld_chain_load (struct link_map *main_map, char *argv0)
+/* Diagnose the dynamic loader run against itself (fatal), and return
+   whether the main executable is dynamically linked: it has DT_NEEDED
+   dependencies or a program interpreter.  */
+static bool
+rtld_main_map_is_dynamic (struct link_map *main_map)
 {
   /* The dynamic loader run against itself.  */
   const char *rtld_soname = l_soname (&_dl_rtld_map);
@@ -1058,14 +1060,26 @@ rtld_chain_load (struct link_map *main_map, char *argv0)
   /* With DT_NEEDED dependencies, the executable is dynamically
      linked.  */
   if (__glibc_unlikely (main_map->l_info[DT_NEEDED] != NULL))
-    return;
+    return true;
 
   /* If the executable has program interpreter, it is dynamically
      linked.  */
   for (size_t i = 0; i < main_map->l_phnum; ++i)
     if (main_map->l_phdr[i].p_type == PT_INTERP)
-      return;
+      return true;
+
+  return false;
+}
 
+/* Check if the executable is not actually dynamically linked, and
+   invoke it directly in that case.  */
+static void
+rtld_chain_load (struct link_map *main_map, char *argv0)
+{
+  if (rtld_main_map_is_dynamic (main_map))
+    return;
+
+  const char *rtld_soname = l_soname (&_dl_rtld_map);
   const char *pathname = _dl_argv[0];
   if (argv0 != NULL)
     _dl_argv[0] = argv0;
@@ -1079,6 +1093,20 @@ rtld_chain_load (struct link_map *main_map, char *argv0)
 		     rtld_soname, pathname, errcode);
 }
 
+/* The main executable was loaded from a descriptor and cannot be
+   chain-loaded through execve: there may be no path it can be executed
+   by, and re-executing it would run the process through the binfmt
+   handler that chose this dynamic linker in the first place, again.
+   Refuse the cases rtld_chain_load would have chained instead of
+   crashing on them later (bug 28648).  */
+static void
+rtld_execfd_check (struct link_map *main_map)
+{
+  if (!rtld_main_map_is_dynamic (main_map))
+    _dl_fatal_printf ("%s: cannot execute a statically linked binary"
+		      " from a descriptor\n", l_soname (&_dl_rtld_map));
+}
+
 /* Called to complete the initialization of the link map for the main
    executable.  Returns true if there is a PT_INTERP segment.  */
 static bool
@@ -1382,16 +1410,50 @@ dl_main (const ElfW(Phdr) *phdr,
 	 like that.  We just load it and use its entry point; we don't
 	 pay attention to its PT_INTERP command (we are the interpreter
 	 ourselves).  This is an easy way to test a new ld.so before
-	 installing it.  */
+	 installing it.
+
+	 This also happens when the kernel executes us on behalf of a
+	 binfmt interpreter dispatch (binfmt_misc): the handler splices
+	 our path and the program's path into the argument vector, so
+	 the arguments are processed just the same.  If the handler
+	 also kept the program open ('O' and 'C' entries), AT_EXECFD
+	 carries the descriptor and the program is loaded from it
+	 instead of re-opening the path (see below).  */
       rtld_is_main = true;
 
       char *argv0 = NULL;
       char **orig_argv = _dl_argv;
+      int execfd = -1;
+      /* True if the kernel dispatched us as a binfmt interpreter
+	 (AT_EXECFD is present).  */
+      bool from_execfd = false;
 
       /* Note the place where the dynamic linker actually came from.  */
       _dl_rtld_map.l_name = rtld_progname;
 
-      while (_dl_argc > 1)
+#ifdef HAVE_AUX_VECTOR
+      /* A binfmt interpreter dispatch that keeps the executed binary
+	 open passes the descriptor in AT_EXECFD.  Load the program
+	 from it: the path spliced into the argument vector may not be
+	 openable again (execute-only binaries), and the descriptor
+	 refers to the very file the kernel access-checked, so no
+	 re-open races against it.  The raw vector must be scanned
+	 because a valid descriptor 0 and an absent entry cannot be
+	 told apart in the parsed values.  */
+      for (ElfW(auxv_t) *av = auxv; av->a_type != AT_NULL; av++)
+	if (av->a_type == AT_EXECFD)
+	  {
+	    execfd = av->a_un.a_val;
+	    from_execfd = true;
+	    break;
+	  }
+#endif
+
+      /* When the kernel dispatches us as a binfmt interpreter, argv[1]
+	 is the spliced program path, not a loader option.  It is
+	 attacker-controlled and may begin with "--" (e.g. a program
+	 named "--preload"), so do not parse it as an option.  */
+      while (!from_execfd && _dl_argc > 1)
 	if (! strcmp (_dl_argv[1], "--list"))
 	  {
 	    if (state.mode != rtld_mode_help)
@@ -1507,6 +1569,17 @@ dl_main (const ElfW(Phdr) *phdr,
 	else
 	  break;
 
+      /* A descriptor on a standard slot is consumed and closed while
+	 the program is mapped below, and a secure process must not run
+	 with a silently closed standard descriptor: the startup check
+	 ran while the descriptor still occupied the slot.  Re-run it
+	 once the program is mapped and the slot is free.  Moving the
+	 descriptor out of the standard range instead would only change
+	 which descriptor number ends up closed.  */
+      bool recheck_standard_fds
+	= (execfd >= 0 && execfd <= STDERR_FILENO
+	   && __glibc_unlikely (__libc_enable_secure));
+
       if (__glibc_unlikely (state.mode == rtld_mode_list_tunables))
 	{
 	  __tunables_print ();
@@ -1581,16 +1654,29 @@ dl_main (const ElfW(Phdr) *phdr,
 #ifdef HAVE_THP
 	  _dl_get_thp_config ();
 #endif
-	  _dl_map_object (NULL, rtld_progname, lt_executable, 0,
-			  __RTLD_OPENEXEC, LM_ID_BASE);
+	  if (execfd != -1)
+	    _dl_map_object_execfd (execfd, rtld_progname);
+	  else
+	    _dl_map_object (NULL, rtld_progname, lt_executable, 0,
+			    __RTLD_OPENEXEC, LM_ID_BASE);
 	  rtld_timer_stop (&load_time, start);
 	}
 
+      /* Fill the standard slot freed by closing the program
+	 descriptor (see recheck_standard_fds above).  */
+      if (__glibc_unlikely (recheck_standard_fds))
+	_dl_recheck_standard_fds ();
+
       /* Now the map for the main executable is available.  */
       main_map = GL(dl_ns)[LM_ID_BASE]._ns_loaded;
 
       if (__glibc_likely (state.mode == rtld_mode_normal))
-	rtld_chain_load (main_map, argv0);
+	{
+	  if (execfd != -1)
+	    rtld_execfd_check (main_map);
+	  else
+	    rtld_chain_load (main_map, argv0);
+	}
 
       phdr = main_map->l_phdr;
       phnum = main_map->l_phnum;
@@ -1622,6 +1708,13 @@ dl_main (const ElfW(Phdr) *phdr,
 	  case AT_EXECFN:
 	    av->a_un.a_val = (uintptr_t) _dl_argv[0];
 	    break;
+	  case AT_EXECFD:
+	    /* An AT_EXECFD entry is present only when the kernel
+	       dispatched us, and the program is then always loaded and
+	       closed from the descriptor, so do not leave a dangling
+	       number behind.  */
+	    av->a_type = AT_IGNORE;
+	    break;
 	  }
 #endif
 
@@ -1650,8 +1743,14 @@ dl_main (const ElfW(Phdr) *phdr,
 
       /* At this point we are in a bit of trouble.  We would have to
 	 fill in the values for l_dev and l_ino.  But in general we
-	 do not know where the file is.  We also do not handle AT_EXECFD
-	 even if it would be passed up.
+	 do not know where the file is.  AT_EXECFD is deliberately not
+	 consumed here: it carries the file the kernel did *not* load
+	 and is addressed to the main program the kernel *did* load -
+	 the registered binfmt interpreter (e.g. a dynamically linked
+	 qemu-user), whose PT_INTERP we merely are.  Only when the
+	 registered interpreter is ld.so itself is ld.so that main
+	 program, and then the descriptor is consumed in the
+	 rtld-as-command branch above.
 
 	 We leave the values here defined to 0.  This is normally no
 	 problem as the program code itself is normally no shared
diff --git a/sysdeps/generic/dl-standard-fds.h b/sysdeps/generic/dl-standard-fds.h
new file mode 100644
index 0000000000..d6c3589ffe
--- /dev/null
+++ b/sysdeps/generic/dl-standard-fds.h
@@ -0,0 +1,34 @@
+/* Re-check the standard file descriptors in the dynamic linker.  Generic.
+   Copyright (C) 2026 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public License as
+   published by the Free Software Foundation; either version 2.1 of the
+   License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; see the file COPYING.LIB.  If
+   not, see <https://www.gnu.org/licenses/>.  */
+
+#ifndef _DL_STANDARD_FDS_H
+#define _DL_STANDARD_FDS_H
+
+/* Re-run the startup standard-descriptor check after the dynamic
+   linker closed a descriptor in the standard range: a secure process
+   must not start with a silently closed standard descriptor.  The
+   generic version is a no-op: ports without the check in the dynamic
+   linker heal the standard descriptors during libc initialization
+   instead (the Hurd does from its _hurd_fd_subinit hook, and does not
+   define __libc_check_standard_fds for shared builds).  */
+static inline void
+_dl_recheck_standard_fds (void)
+{
+}
+
+#endif
diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h
index f94247ad9f..b97282aaac 100644
--- a/sysdeps/generic/ldsodefs.h
+++ b/sysdeps/generic/ldsodefs.h
@@ -933,6 +933,13 @@ struct link_map *_dl_map_new_object (struct link_map *loader,
 				     int type, int trace_mode, int mode,
 					     Lmid_t nsid) attribute_hidden;
 
+/* Map in the main executable from the already-open descriptor FD
+   (AT_EXECFD or an explicit loader invocation).  NAME is the name the
+   program is known by; the canonical name used for $ORIGIN is derived
+   from FD.  FD is consumed.  */
+extern struct link_map *_dl_map_object_execfd (int fd, const char *name)
+     attribute_hidden;
+
 
 /* Call _dl_map_object on the dependencies of MAP, and set up
    MAP->l_searchlist.  PRELOADS points to a vector of NPRELOADS previously
diff --git a/sysdeps/mips/dl-machine-reject-phdr.h b/sysdeps/mips/dl-machine-reject-phdr.h
index e64494c198..88d0a3cd6a 100644
--- a/sysdeps/mips/dl-machine-reject-phdr.h
+++ b/sysdeps/mips/dl-machine-reject-phdr.h
@@ -209,8 +209,8 @@ elf_machine_reject_phdr_p (const struct dl_machine_phdr_info *info,
       if (ph->p_filesz < size)
 	REJECT ("   contains malformed PT_MIPS_ABIFLAGS\n");
 
-      __lseek (fd, ph->p_offset, SEEK_SET);
-      if (__libc_read (fd, (void *) &mips_abiflags, size) != size)
+      if (__pread64_nocancel (fd, (void *) &mips_abiflags, size,
+			      ph->p_offset) != size)
 	REJECT ("   unable to read PT_MIPS_ABIFLAGS\n");
 
       if (__glibc_unlikely (mips_abiflags.flags2 != 0))
diff --git a/sysdeps/unix/sysv/linux/dl-standard-fds.h b/sysdeps/unix/sysv/linux/dl-standard-fds.h
new file mode 100644
index 0000000000..207af0ab0b
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/dl-standard-fds.h
@@ -0,0 +1,33 @@
+/* Re-check the standard file descriptors in the dynamic linker.  Linux.
+   Copyright (C) 2026 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public License as
+   published by the Free Software Foundation; either version 2.1 of the
+   License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; see the file COPYING.LIB.  If
+   not, see <https://www.gnu.org/licenses/>.  */
+
+#ifndef _DL_STANDARD_FDS_H
+#define _DL_STANDARD_FDS_H
+
+#include <unistd.h>
+
+/* Linux runs the startup standard-descriptor check inside the dynamic
+   linker (dl-sysdep.c), so the re-check after closing a program
+   descriptor runs there as well.  */
+static inline void
+_dl_recheck_standard_fds (void)
+{
+  __libc_check_standard_fds ();
+}
+
+#endif
