Process Hardening // Countermeasures to Process Snooping
Context
On Linux,
/procexposes every user’s processes by default. Any local user can read arguments, environment variables, and memory maps of any process. Tools likepspy,ps -ef,cat /proc/PID/cmdlineexploit this visibility to intercept credentials passed as process arguments.Real-world example: Codify_EN —
mysqldump -p"password"executed as root exposes the password to any local user.
1 // The Problem: Process Snooping
Why it works
When a program is executed, the kernel writes its arguments (argv) to /proc/PID/cmdline. This file is readable by all users by default.
# Any user can do this:
ps -ef | grep mysql
# root 12345 mysqldump -u root -pSuperSecret123 --all-databases
cat /proc/12345/cmdline | tr '\0' ' '
# mysqldump -u root -pSuperSecret123 --all-databasesHow pspy exploits this
pspy uses inotify on /proc to receive notifications when a new process is created. As soon as the kernel writes the PID, pspy reads /proc/PID/cmdline and captures the arguments before the process terminates or masks them.
# Typical pspy output:
CMD: UID=0 /usr/bin/mysqldump -u root -pSuperSecret123 --all-databases
2 // Countermeasure: hidepid
What it does
The hidepid option on the /proc filesystem controls process visibility between users.
| Value | Effect |
|---|---|
hidepid=0 | Default — everyone sees all processes |
hidepid=1 | Users see other PIDs but not their details (cmdline, environ, maps) |
hidepid=2 | Users see only their own processes — other users’ /proc/PID/ is inaccessible |
Apply temporarily (immediate, does not persist across reboot)
sudo mount -o remount,hidepid=2 /procApply permanently (persists across reboot)
# /etc/fstab — add or modify the /proc line:
proc /proc proc defaults,hidepid=2 0 0Verification
# As a non-root user:
ps aux
# Shows only own processes
ls /proc/ | grep -E '^[0-9]+$' | wc -l
# Only own PIDs
cat /proc/1/cmdline
# cat: /proc/1/cmdline: Permission deniedImpact on pspy
With hidepid=2, pspy can no longer read /proc/PID/cmdline of root processes. The process snooping attack is completely neutralized.
Limitation
hidepiddoes not protect processes from the same user. If an attacker obtains a shell as the same UID running the command, they will still see the arguments. Defense must be layered.
3 // Countermeasure: argv masking
The concept
Some programs overwrite argv in memory after startup to hide sensitive arguments. The argv memory area is modifiable by the process itself — by overwriting the bytes, the content visible in /proc/PID/cmdline changes.
Example: MySQL behavior
# Original command:
mysql -u root -pSuperSecret123
# What ps shows after argv masking:
mysql -u root -pxxxxxxxxxxxxxxxMySQL reads the password from argv[2], stores it internally, then overwrites the bytes with x. This happens after the process has started — there is a time window where the password is visible in cleartext.
C demo
// argv_mask_demo.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
printf("PID: %d\n", getpid());
printf("Original arguments:\n");
for (int i = 0; i < argc; i++)
printf(" argv[%d] = %s\n", i, argv[i]);
// Masking: overwrite every argument with 'X'
for (int i = 1; i < argc; i++)
memset(argv[i], 'X', strlen(argv[i]));
printf("Arguments masked. Verify with:\n");
printf(" cat /proc/%d/cmdline | tr '\\0' ' '\n", getpid());
// Keep process alive for inspection
pause();
return 0;
}gcc -o argv_mask_demo argv_mask_demo.c
./argv_mask_demo password123 secret &# Before masking (time window):
cat /proc/$(pgrep argv_mask)/cmdline | tr '\0' ' '
# ./argv_mask_demo password123 secret
# After masking:
cat /proc/$(pgrep argv_mask)/cmdline | tr '\0' ' '
# ./argv_mask_demo XXXXXXXXXXX XXXXXXRace condition
Why argv masking is not enough
Masking happens after
execve(). Between the moment the kernel creates the process and the moment userspace code overwritesargv, there is a time window.pspywithinotifyreads/proc/PID/cmdlinewithin this window, capturing the original arguments.Timeline:
execve() → kernel writes argv to /proc/PID/cmdline ↓ (window) → pspy reads cmdline → password in cleartext main() → memset() → argv overwritten with 'X' ↓ → ps/cat now show 'XXXXXXX'Argv masking reduces the exposure window but does not eliminate it.
4 // Best Practice: Never Pass Credentials as Arguments
The correct defense eliminates the problem at the root: credentials must never appear in argv.
MySQL/MariaDB — configuration file
# ~/.my.cnf (permissions: 600)
[client]
user=root
password=SuperSecret123chmod 600 ~/.my.cnf
mysqldump --all-databases # reads credentials from fileNo -p argument in the process — nothing to read from /proc/PID/cmdline.
Environment variables (use with caution)
export MYSQL_PWD='SuperSecret123'
mysqldump -u root --all-databases
/proc/PID/environis readable with the same permissions ascmdline. Withhidepid=0, environment variables are exposed the same way. Prefer configuration files.
Bash scripts // read from file, not from argument
# Wrong:
DB_PASS=$(/usr/bin/cat /root/.creds)
mysqldump -u root -p"$DB_PASS" --all-databases
# Correct:
mysqldump --defaults-file=/root/.my.cnf --all-databases5 // Hardening Checklist
- Enable
hidepid=2on/procin/etc/fstab - Verify no scripts pass credentials as process arguments
- Replace
-p"password"with--defaults-fileor~/.my.cnffor MySQL - Set
600permissions on all files containing credentials - Quote variables in bash comparisons:
[[ "$a" == "$b" ]] - Verify with
ps auxwweandpspythat no process leaks secrets