From: Mark Hatle <mark.hatle@kernel.crashing.org>
To: yocto-patches@lists.yoctoproject.org
Cc: richard.purdie@linuxfoundation.org, frezidok1@gmail.com
Subject: [pseudo][PATCH 20/23] exec*: Replace bash workaround to avoid memory corruption
Date: Thu, 2 Jul 2026 19:46:15 -0500 [thread overview]
Message-ID: <1783039578-31531-21-git-send-email-mark.hatle@kernel.crashing.org> (raw)
In-Reply-To: <1783039578-31531-1-git-send-email-mark.hatle@kernel.crashing.org>
From: Richard Purdie <richard.purdie@linuxfoundation.org>
Bash intercepts getenv/setenv/unsetenv and does magic with it internally.
The data pointed to by environ may not be allocated by glibc but by bash
and the glibc env functions have their own memory handling outside of malloc.
Unfortuantely bash doesn't keep environ and the result of setenv/getenv/unsetenv
in sync either. This means the current workaround badly corrupts memory and it
is just luck we're not breaking more often than the occasional opkg-build segfaults
we've been seeing.
Fixing this is tricky, the best we can probably do is to read through environ and
create our own copy of the it, modifying it how we need to keep the pseudo variables
correct.
We do already have a function which can copy and modify the environment, we can
therefore swap some setupenv calls for setupenvp and switch out environ around
the exec calls.
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Message-ID: <20260701131336.3578279-6-richard.purdie@linuxfoundation.org>
Signed-off-by: Mark Hatle <mark.hatle@kernel.crashing.org>
---
ports/common/guts/execv.c | 19 ++++++++++++++-----
ports/common/guts/execvp.c | 15 ++++++++++++---
ports/unix/guts/popen.c | 16 +++++++++++++---
ports/unix/guts/system.c | 16 +++++++++++++---
pseudo_client.h | 3 ---
pseudo_util.c | 12 +++---------
pseudo_wrappers.c | 6 ------
7 files changed, 55 insertions(+), 32 deletions(-)
diff --git a/ports/common/guts/execv.c b/ports/common/guts/execv.c
index 7819911..eb328ed 100644
--- a/ports/common/guts/execv.c
+++ b/ports/common/guts/execv.c
@@ -8,6 +8,8 @@
* wrap_execv(const char *file, char *const *argv) {
* int rc = -1;
*/
+ char **new_environ, **orig_environ;
+
/* note: we don't canonicalize this, because we are intentionally
* NOT redirecting execs into the chroot environment. If you try
* to execute /bin/sh, you get the actual /bin/sh, not
@@ -19,17 +21,24 @@
pseudo_client_op(OP_EXEC, PSA_EXEC, -1, -1, path_guess, 0);
}
- pseudo_setupenv();
- if (pseudo_has_unload(NULL)) {
- /* and here we attach */
- pseudo_dropenv();
- }
+ /* Due to bash intercepting setenv/getenv/unsetenv and changing environ
+ internally itself at will, we create our own environ copy at process
+ creation based on it to ensure it is correct */
+ orig_environ = environ;
+ new_environ = pseudo_setupenvp(environ);
+ if (pseudo_has_unload(new_environ))
+ new_environ = pseudo_dropenvp(new_environ);
+ environ = new_environ;
+
/* if exec() fails, we may end up taking signals unexpectedly...
* not much we can do about that.
*/
sigprocmask(SIG_SETMASK, &pseudo_saved_sigmask, NULL);
rc = real_execv(file, argv);
+ environ = orig_environ;
+ free(new_environ);
+
/* return rc;
* }
*/
diff --git a/ports/common/guts/execvp.c b/ports/common/guts/execvp.c
index acc9fdc..177e4ee 100644
--- a/ports/common/guts/execvp.c
+++ b/ports/common/guts/execvp.c
@@ -8,6 +8,7 @@
* wrap_execvp(const char *file, char *const *argv) {
* int rc = -1;
*/
+ char **new_environ, **orig_environ;
/* note: we don't canonicalize this, because we are intentionally
* NOT redirecting execs into the chroot environment. If you try
@@ -20,9 +21,14 @@
pseudo_client_op(OP_EXEC, PSA_EXEC, -1, -1, path_guess, 0);
}
- pseudo_setupenv();
- if (pseudo_has_unload(NULL))
- pseudo_dropenv();
+ /* Due to bash intercepting setenv/getenv/unsetenv and changing environ
+ internally itself at will, we create our own environ copy at process
+ creation based on it to ensure it is correct */
+ orig_environ = environ;
+ new_environ = pseudo_setupenvp(environ);
+ if (pseudo_has_unload(new_environ))
+ new_environ = pseudo_dropenvp(new_environ);
+ environ = new_environ;
/* if exec() fails, we may end up taking signals unexpectedly...
* not much we can do about that.
@@ -30,6 +36,9 @@
sigprocmask(SIG_SETMASK, &pseudo_saved_sigmask, NULL);
rc = real_execvp(file, argv);
+ environ = orig_environ;
+ free(new_environ);
+
/* return rc;
* }
*/
diff --git a/ports/unix/guts/popen.c b/ports/unix/guts/popen.c
index d19ec7e..1d48d04 100644
--- a/ports/unix/guts/popen.c
+++ b/ports/unix/guts/popen.c
@@ -7,15 +7,25 @@
* FILE *popen(const char *command, const char *mode)
* FILE *rc = NULL;
*/
+ char **new_environ, **orig_environ;
+
/* on at least some systems, popen() calls fork and exec
* in ways that avoid our usual enforcement of the environment.
*/
- pseudo_setupenv();
- if (pseudo_has_unload(NULL))
- pseudo_dropenv();
+ /* Due to bash intercepting setenv/getenv/unsetenv and changing environ
+ internally itself at will, we create our own environ copy at process
+ creation based on it to ensure it is correct */
+ orig_environ = environ;
+ new_environ = pseudo_setupenvp(environ);
+ if (pseudo_has_unload(new_environ))
+ new_environ = pseudo_dropenvp(new_environ);
+ environ = new_environ;
rc = real_popen(command, mode);
+ environ = orig_environ;
+ free(new_environ);
+
/* return rc;
* }
*/
diff --git a/ports/unix/guts/system.c b/ports/unix/guts/system.c
index 1214314..4b374ec 100644
--- a/ports/unix/guts/system.c
+++ b/ports/unix/guts/system.c
@@ -7,15 +7,25 @@
* int system(const char *command)
* int rc = -1;
*/
+ char **new_environ, **orig_environ;
+
if (!command)
return 1;
- pseudo_setupenv();
- if (pseudo_has_unload(NULL))
- pseudo_dropenv();
+ /* Due to bash intercepting setenv/getenv/unsetenv and changing environ
+ internally itself at will, we create our own environ copy at process
+ creation based on it to ensure it is correct */
+ orig_environ = environ;
+ new_environ = pseudo_setupenvp(environ);
+ if (pseudo_has_unload(new_environ))
+ new_environ = pseudo_dropenvp(new_environ);
+ environ = new_environ;
rc = real_system(command);
+ environ = orig_environ;
+ free(new_environ);
+
/* return rc;
* }
*/
diff --git a/pseudo_client.h b/pseudo_client.h
index a013f88..43bbc0a 100644
--- a/pseudo_client.h
+++ b/pseudo_client.h
@@ -52,9 +52,6 @@ extern FILE *pseudo_grp;
/* pseudo_wrappers will try to initialize these */
extern int (*pseudo_real_lstat)(const char *path, PSEUDO_STATBUF *buf);
-extern int (*pseudo_real_unsetenv)(const char *);
-extern char * (*pseudo_real_getenv)(const char *);
-extern int (*pseudo_real_setenv)(const char *, const char *, int);
extern int (*pseudo_real_fork)(void);
extern int (*pseudo_real_execv)(const char *, char * const *);
diff --git a/pseudo_util.c b/pseudo_util.c
index 61d47e4..13c974f 100644
--- a/pseudo_util.c
+++ b/pseudo_util.c
@@ -73,15 +73,9 @@ typedef struct {
char *data;
} pseudo_evlog_entry;
-/* so bash overrides getenv/unsetenv/etcetera, preventing them from
- * actually modifying environ, so we have pseudo_wrappers try to dlsym
- * the right values. This could fail, in which case we'd get null
- * pointers, and we'll just call whatever the linker gives us and
- * hope for the best.
- */
-#define SETENV(x, y, z) (pseudo_real_setenv ? pseudo_real_setenv : setenv)(x, y, z)
-#define GETENV(x) (pseudo_real_getenv ? pseudo_real_getenv : getenv)(x)
-#define UNSETENV(x) (pseudo_real_unsetenv ? pseudo_real_unsetenv : unsetenv)(x)
+#define SETENV(x, y, z) setenv(x, y, z)
+#define GETENV(x) getenv(x)
+#define UNSETENV(x) unsetenv(x)
#define PSEUDO_EVLOG_ENTRIES 250
#define PSEUDO_EVLOG_LENGTH 256
diff --git a/pseudo_wrappers.c b/pseudo_wrappers.c
index 662c276..6cd8e5d 100644
--- a/pseudo_wrappers.c
+++ b/pseudo_wrappers.c
@@ -173,12 +173,6 @@ pseudo_init_wrappers(void) {
pseudo_real_fsetxattr = real_fsetxattr;
#endif
pseudo_real_lstat = base_lstat;
- /* bash has its own local copies of these which it uses
- * instead of ours...
- */
- pseudo_real_unsetenv = dlsym(RTLD_NEXT, "unsetenv");
- pseudo_real_getenv = dlsym(RTLD_NEXT, "getenv");
- pseudo_real_setenv = dlsym(RTLD_NEXT, "setenv");
/* and these are used so the client's server spawn can bypass
* wrappers.
*/
--
2.53.0
next prev parent reply other threads:[~2026-07-03 0:46 UTC|newest]
Thread overview: 28+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-03 0:45 [pseudo][PATCH 00/23] Create new pseudo 1.99.0 version Mark Hatle
2026-07-03 0:45 ` [pseudo][PATCH 01/23] Makefile.in: Move version to 1.99.0 to prep for 2.0 development Mark Hatle
2026-07-03 0:45 ` [pseudo][PATCH 02/23] pseudo_util: Add log severity flags Mark Hatle
2026-07-03 0:45 ` [pseudo][PATCH 03/23] pseudo: Add new logging macros Mark Hatle
2026-07-03 0:45 ` [pseudo][PATCH 04/23] pseudo_util: Change pseudo_diag() calls to appropriate " Mark Hatle
2026-07-03 0:46 ` [pseudo][PATCH 05/23] pseudo_db: Change pseudo_diag() calls to appropriate macros Mark Hatle
2026-07-03 0:46 ` [pseudo][PATCH 06/23] pseudo_client: " Mark Hatle
2026-07-03 0:46 ` [pseudo][PATCH 07/23] pseudo_server: " Mark Hatle
2026-07-03 0:46 ` [pseudo][PATCH 08/23] pseudo.c: " Mark Hatle
2026-07-03 0:46 ` [pseudo][PATCH 09/23] pseudolog.c: " Mark Hatle
2026-07-03 0:46 ` [pseudo][PATCH 10/23] wrappers: " Mark Hatle
2026-07-03 0:46 ` [pseudo][PATCH 11/23] pseudo: Change pseudo_diag() name to pseudo_log() Mark Hatle
2026-07-03 0:46 ` [pseudo][PATCH 12/23] pseudo_util: Add default log severity values Mark Hatle
2026-07-03 10:43 ` [yocto-patches] " Paul Barker
2026-07-03 12:44 ` Dmitry Sakhonchik
2026-07-03 0:46 ` [pseudo][PATCH 13/23] pseudo_util.c: strchr now returns const char Mark Hatle
2026-07-03 0:46 ` [pseudo][PATCH 14/23] test/test-openat2-func.c: Remove unusuaed saved_errno Mark Hatle
2026-07-03 10:44 ` [yocto-patches] " Paul Barker
2026-07-03 0:46 ` [pseudo][PATCH 15/23] pseudo.h: Avoid accessing unallocated memory Mark Hatle
2026-07-03 0:46 ` [pseudo][PATCH 16/23] pseudo_util: Avoid accidental free calls for without_libpseudo() Mark Hatle
2026-07-03 0:46 ` [pseudo][PATCH 17/23] pseudo_util: Ensure pseudo_setupenvp handles memory consistently Mark Hatle
2026-07-03 0:46 ` [pseudo][PATCH 18/23] pseudo_util: Avoid a memory leak in pseudo_dropenv() Mark Hatle
2026-07-03 0:46 ` [pseudo][PATCH 19/23] pseudo_util: Clean up memory handling for setupenvp results Mark Hatle
2026-07-03 0:46 ` Mark Hatle [this message]
2026-07-03 0:46 ` [pseudo][PATCH 21/23] pseudo_util: Correctly free memory allocated by pseudo_setupenvp Mark Hatle
2026-07-03 0:46 ` [pseudo][PATCH 22/23] test-bash-exec-env: Add bash env test case Mark Hatle
2026-07-03 11:13 ` [yocto-patches] " Paul Barker
2026-07-03 0:46 ` [pseudo][PATCH 23/23] test: various: Move to makefile compilation Mark Hatle
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=1783039578-31531-21-git-send-email-mark.hatle@kernel.crashing.org \
--to=mark.hatle@kernel.crashing.org \
--cc=frezidok1@gmail.com \
--cc=richard.purdie@linuxfoundation.org \
--cc=yocto-patches@lists.yoctoproject.org \
/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.