From 10ed62f34a4565188887b06df1438ef8002c3c80 Mon Sep 17 00:00:00 2001
From: Alexander Larsson <alexl@redhat.com>
Date: Tue, 14 Apr 2026 11:46:12 +0200
Subject: [PATCH 1/2] Don't run the privilege separated code dumpable
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

If the unprivileged code is dumpable, then it can be attached to with
ptrace and execute arbitrary requests to the privileged part of the
setup. In some cases this allows privilege escalation, such as using
overlay mounts which would otherwise not be allowed in setuid mode.

Note: We still make the monitor process and the main child process
dumpable, because otherwise the owner of /proc/self is root which
makes these processed not able to use features that are needed for
e.g. detarmining portal access.

Initially reported by François Diakhate <fdiakh@gmail.com>

Signed-off-by: Alexander Larsson <alexl@redhat.com>
---
 bubblewrap.c | 22 ++++++++++++++--------
 1 file changed, 14 insertions(+), 8 deletions(-)

diff --git a/bubblewrap.c b/bubblewrap.c
index 687ed71..5fcccf7 100644
--- a/bubblewrap.c
+++ b/bubblewrap.c
@@ -937,7 +937,8 @@ switch_to_user_with_privs (void)
 /* Call setuid() and use capset() to adjust capabilities */
 static void
 drop_privs (bool keep_requested_caps,
-            bool already_changed_uid)
+            bool already_changed_uid,
+            bool set_dumpable)
 {
   assert (!keep_requested_caps || !is_privileged);
   /* Drop root uid */
@@ -947,9 +948,12 @@ drop_privs (bool keep_requested_caps,
 
   drop_all_caps (keep_requested_caps);
 
-  /* We don't have any privs now, so mark us dumpable which makes /proc/self be owned by the user instead of root */
-  if (prctl (PR_SET_DUMPABLE, 1, 0, 0, 0) != 0)
-    die_with_error ("can't set dumpable");
+  if (set_dumpable)
+    {
+      /* We don't have any privs now, so mark us dumpable which makes /proc/self be owned by the user instead of root */
+      if (prctl (PR_SET_DUMPABLE, 1, 0, 0, 0) != 0)
+        die_with_error ("can't set dumpable");
+    }
 }
 
 static void
@@ -3182,7 +3186,7 @@ main (int    argc,
         die_with_error ("Setting userns2 failed");
 
       /* We don't need any privileges in the launcher, drop them immediately. */
-      drop_privs (false, false);
+      drop_privs (false, false, true);
 
       /* Optionally bind our lifecycle to that of the parent */
       handle_die_with_parent ();
@@ -3369,8 +3373,10 @@ main (int    argc,
 
       if (child == 0)
         {
-          /* Unprivileged setup process */
-          drop_privs (false, true);
+          /* Unprivileged setup process.
+           * Note: Don't set dumpable, because we can still perform privileged
+           * operations via privileged_op(). */
+          drop_privs (false, true, false);
           close (privsep_sockets[0]);
           setup_newroot (opt_unshare_pid, privsep_sockets[1]);
           exit (0);
@@ -3499,7 +3505,7 @@ main (int    argc,
     }
 
   /* All privileged ops are done now, so drop caps we don't need */
-  drop_privs (!is_privileged, true);
+  drop_privs (!is_privileged, true, true);
 
   if (opt_block_fd != -1)
     {
-- 
2.53.0

