Checking an executable’s path does not tell you every piece of code it will run. Its environment can influence which libraries load, which helper programs execute and which settings the application trusts.
Shellshock, PwnKit and Looney Tunables illustrate different failures along that path. Understanding the differences makes an environment review more useful than searching for a list of suspicious variable names.
Start with the value and its reader
An environment variable is a named value supplied to a program when it starts. For example:
PATH=/usr/bin:/bin
Here, PATH lists two directories to search when resolving a command name. Child processes normally inherit their parent’s environment, although a launcher can supply a different one. Changing your shell’s environment does not rewrite an already running service’s environment. 1, 3
A value becomes security-sensitive when it reaches code that uses it to select behavior. The attacker needs a route to influence that value. Examples include launching a local program, modifying a service’s startup configuration or reaching an application that maps request data into an environment variable.
Control over your own process is not automatically privilege escalation. The concern is crossing a trust boundary: making another component act with permissions or access you should not control.
Set-user-ID programs and services have different boundaries
The set-user-ID permission bit, often called SUID, can make an executable run with its owner’s effective user ID. That identity governs many permission checks. A root-owned SUID program can therefore perform privileged work for an ordinary caller; its real user ID still identifies the caller. 3
The service approach starts a process under a configured account and accepts requests through an interface, such as a local socket. The comparison matters because the caller does not necessarily control the same inputs.
| Question | SUID executable | System service managed by systemd |
|---|---|---|
| Who supplies startup values? | The launching process supplies an environment, subject to filtering. | The service manager assembles it from configuration and other configured sources. |
| What establishes authority? | An eligible execution changes the effective user ID to the file owner’s. | The manager starts the service under its configured account. |
| What needs review? | Caller-controlled inputs across the privilege transition. | Startup configuration, request authorization and the service’s existing permissions. |
glibc, the GNU C Library, enables its loader’s secure-execution mode when the kernel supplies a nonzero AT_SECURE flag. A difference between real and effective IDs is a common trigger. In this mode, LD_LIBRARY_PATH is ignored and LD_PRELOAD is restricted. Running a service as root does not, by itself, activate this protection. 2
A systemd system service does not simply inherit the shell environment of whoever runs systemctl start. Review its unit configuration, including Environment= and EnvironmentFile=. A service can make startup inputs easier to control, but its request interface still needs authentication and authorization. 4
The dynamic linker reads values before application code
For a dynamically linked program, the dynamic linker loads shared libraries: reusable code the executable depends on. Some of this work happens before main(), the application’s main entry point.
Outside secure-execution restrictions, LD_LIBRARY_PATH influences library searches and LD_PRELOAD requests additional libraries loaded ahead of others. These have legitimate uses. They become dangerous when an attacker can influence a more privileged program’s environment and get it to load attacker-controlled code. Removing a variable inside main() cannot undo library initialization that already ran. 2, 10
Looney Tunables — CVE-2023-4911, disclosed in October 2023 — involved a different loader failure. A buffer overflow, a write beyond an allocated buffer, affected glibc’s handling of GLIBC_TUNABLES, a runtime tuning variable. A local user could exploit it to gain root privileges on affected systems. Qualys demonstrated the flaw through SUID programs. The failure was in the loader’s parsing and sanitization code. 5
That distinction affects remediation: a vulnerable loader needs a vendor fix, even when the application validates its own settings.
News coverage: BleepingComputer, 3 October 2023.
External programs add another decision point
Consider a backup application that invokes tar by name using execvp(), a function that searches PATH. If an attacker controls the search path and can place an executable in a directory searched first, the application may run the wrong program. No shell metacharacters are needed for this form of substitution. 6
An absolute path avoids that name search. It does not make the executable’s environment, arguments or dependencies trustworthy. A shell adds another interpreter: C’s system() passes a command to /bin/sh, which is not necessarily Bash. 3, 7
Shellshock — including CVE-2014-6271, disclosed in September 2014 — showed why the receiving program matters. Vulnerable Bash versions could execute trailing commands while importing shell functions, or named blocks of commands, from environment variables.
One remote route involved CGI, an interface through which a web server passes request information to a program using environment variables. If attacker-controlled request data reached a vulnerable Bash invocation, commands could run with that process’s permissions. Root access was not automatic, and an HTTP request alone was insufficient without the vulnerable execution path. Red Hat also documented follow-up flaws after the initial fix. 8
News coverage: KrebsOnSecurity, 25 September 2014.
Libraries and application code can read the environment later
Application code can read a value with getenv(). A hypothetical REPORT_PLUGIN setting might select a plugin through dlopen(), which loads a shared library into the current process. The plugin then runs with that process’s authority. Loader filtering does not validate every application-defined setting. 9, 10
glibc’s secure_getenv() withholds values in secure-execution mode. It does not validate values for an ordinary service. Code still needs to check allowed values and handle missing settings explicitly. 9
PwnKit — CVE-2021-4034, disclosed in January 2022 — connected an application bug to library behavior. Memory corruption in polkit’s SUID-root pkexec utility allowed an unsafe environment variable to be reintroduced after startup filtering. Qualys described a route using GCONV_PATH to influence character-conversion module loading and execute code as root. The vulnerability was in pkexec; library loading completed the chain. 11
The lesson extends beyond explicit plugin systems: dependencies may read environment values during error handling or other operations that look unrelated to loading code.
News coverage: Computer Weekly, 26 January 2022.
Match the control to the point of use
These historical CVEs have published fixes. Confirm installed packages against your distribution’s advisories; vendors may backport fixes without adopting the newest upstream version number. 5, 8, 11, 13
For application and service reviews, I would check five things:
- Control the launch environment. Pass only required, approved values before execution. Protect service units, environment files and their parent directories from unauthorized changes.
- Make helper execution explicit. Use trusted absolute paths, separate arguments and a deliberate child environment. Validate arguments too; avoiding a shell does not prevent a helper from interpreting an unsafe option.
- Constrain library selection. Map approved plugin names to protected files instead of accepting arbitrary paths. Include dependencies and directory permissions in that review.
- Limit authority. Use a dedicated service account with only the access it needs. Where compatible,
NoNewPrivileges=yesprevents execution from granting additional privileges; it does not remove permissions the service already has. 4, 12 - Monitor decisions, not just names. Investigate unexpected helper paths, library loads and startup-configuration changes where telemetry supports them. A variable’s presence alone is not evidence of compromise. Avoid collecting whole environments into logs; they can contain secrets. 4
For each sensitive value, record who can supply it, which component reads it, when it is read and what authority that component holds. That record tells you whether the fix belongs in the launcher, a dependency, application code or the service’s permission model.
Technical references
- Linux man-pages: environ(7) — process environments and inheritance.
- Linux man-pages: ld.so(8) — loader behavior and secure execution.
- Linux man-pages: execve(2) — execution, environment and identity changes.
- systemd: systemd.exec — service environments, identity and restrictions.
- Qualys: Looney Tunables advisory, CVE-2023-4911 — original disclosure, 3 October 2023.
- Linux man-pages: exec(3) — executable-name resolution.
- Linux man-pages: system(3) — shell invocation and privileged-program cautions.
- Red Hat: Shellshock vulnerability — Bash behavior, CGI exposure and follow-up fixes.
- Linux man-pages: getenv(3) — environment reads and
secure_getenv(). - Linux man-pages: dlopen(3) — library loading and initialization.
- Qualys: PwnKit advisory, CVE-2021-4034 — original disclosure, 25 January 2022.
- Linux kernel: No New Privileges Flag — protection and limits.
- Red Hat: Security Backporting Practice — interpreting vendor package versions.