Managing User Accounts in Rootfs
Free Linux device drivers course: passwd, shadow, and group files
Running every process as root is one of the fastest ways to turn a minor bug into a full system compromise. This lecture, part of our free linux device drivers course, walks through how embedded Linux root filesystems configure unprivileged user accounts using the three classic files: /etc/passwd, /etc/shadow, and /etc/group.
What You Will Learn
- The field-by-field format of
/etc/passwd,/etc/shadow, and/etc/group - Why passwords live in
shadowinstead ofpasswd - How to generate password hashes and set correct file permissions
- How to wire a login shell to
gettyviarespawn - Practical do’s and don’ts for production embedded images
Prerequisites
This lecture assumes you’ve already worked through BusyBox init and inittab from the previous lecture, since login accounts only matter once getty is running.
Why /etc/passwd and /etc/shadow Are Split
/etc/passwd must be world-readable — utilities constantly look up usernames and UIDs from it. That’s a problem if password hashes live there too, because any local process could copy them out and run an offline cracking attack. The fix is to store the actual hash in /etc/shadow, which is readable only by root, and leave an x placeholder in passwd.
The /etc/passwd Format
Seven colon-separated fields per line:
| # | Field | Example |
|---|---|---|
| 1 | Login name | ep_user |
| 2 | Password placeholder | x (real hash is in shadow) |
| 3 | UID | 1000 |
| 4 | GID | 1000 |
| 5 | Comment/GECOS field | EP demo user |
| 6 | Home directory | /home/ep_user |
| 7 | Login shell | /bin/ash |
root:x:0:0:root:/root:/bin/sh
ep_user:x:1000:1000:EP demo user:/home/ep_user:/bin/ash
ep_daemon:x:1001:1001:EP background daemon:/usr/sbin:/bin/false
Setting the shell to /bin/false for a service account like ep_daemon guarantees nobody can log in interactively as that account, even with a valid password.
The /etc/shadow Format
Nine colon-separated fields; the first two are the ones you’ll touch most:
| # | Field | Meaning |
|---|---|---|
| 1 | Login name | Must match passwd |
| 2 | Password hash | Empty = no password; * or ! = login disabled |
| 3-9 | Aging fields | Last change, min/max age, warn period, inactivity, expiry — rarely used on embedded devices |
root::19800:0:99999:7:::
ep_user:$6$Kx2f9pQ$abcdEXAMPLEHASHdoNotUse:19800:0:99999:7:::
ep_daemon:*:19800:0:99999:7:::
An empty hash field for
rootmeans passwordless login — convenient during bring-up on a serial console, but never ship that to production hardware.
Generating a Real Password Hash
Use mkpasswd from the host, or run passwd directly on the target and copy the resulting hash back into your staging directory’s shadow file:
$ mkpasswd -m sha-512 -S randomsalt
$6$randomsalt$JuAJs...longhash...
The /etc/group Format
Four colon-separated fields:
root:x:0:
ep_user:x:1000:
ep_daemon:x:1001:
netdev:x:1002:ep_user
The final field is a comma-separated list of extra members — here, ep_user is also added to the netdev group for supplementary access without changing its primary GID.
Wiring Up the Login Prompt
With accounts in place, add a respawn entry for getty to /etc/inittab so a login prompt reappears every time a session ends:
::sysinit:/etc/init.d/rcS
::respawn:/sbin/getty 115200 console
Setting Permissions Correctly
$ cd ~/staging
$ chmod 0644 etc/passwd etc/group
$ chmod 0600 etc/shadow
$ chown root:root etc/passwd etc/shadow etc/group
Common Mistakes and Troubleshooting
- Login always fails: UID/GID mismatch between
passwdandgroup, or a stray space in the colon-separated fields. - Anyone can read password hashes:
shadowpermissions weren’t tightened to 0600 before packaging the rootfs. - Service account can somehow log in: shell field left as a real shell instead of
/bin/falseor/sbin/nologin. - getty doesn’t reappear after logout: the inittab entry used
onceinstead ofrespawn.
Best Practices
- Never ship an empty root password hash to production — set a real one or disable direct root login entirely.
- Create a dedicated unprivileged account for any daemon that doesn’t strictly need root.
- Keep UID/GID ranges consistent across your fleet of images to simplify permission management later.
Security Considerations
On a compromised device, the value of /etc/shadow being unreadable to non-root processes is the entire point — a misbehaving or exploited unprivileged process still can’t harvest hashes for offline cracking. Combine this with running services as non-root wherever possible.
Summary and Key Takeaways
/etc/passwdholds account metadata and is world-readable; the real secret lives in/etc/shadow, root-only./etc/groupmaps GIDs to names and supplementary memberships.- File permissions (0600 on shadow) are just as important as file content.
Conclusion
With accounts and login wired up, your rootfs can now support unprivileged processes — a prerequisite for the daemon and device-node management topics coming next in this free embedded linux course.
FAQ
Do I need /etc/shadow on a single-user embedded device?
It’s still good practice — it costs nothing and protects against any future multi-user or networked login scenario.
What’s the difference between * and an empty field in shadow?
Empty means no password is required; * or ! means the account cannot log in with a password at all.
Can I skip /etc/group entirely?
No — every GID referenced in passwd must resolve to a valid group entry, or tools that look up group names will fail.
Is mkpasswd available on all host distros?
It ships in the whois package on Debian/Ubuntu; other distros may need an equivalent tool or openssl passwd.
Should service daemons run as root?
Only if they genuinely need root privileges — otherwise create a dedicated low-privilege account for them.
Continue Your Free Embedded Linux Course
Next up: starting daemons and managing device nodes automatically.
Next Lecture Browse Full Course
2 Comments