Description
In the system program and library directories (/usr/bin, /usr/sbin, /usr/lib,
/usr/lib64, /usr/libexec, /usr/local/bin, /usr/local/sbin, /usr/local/lib, and
/bin, /sbin, /lib, /lib64 when they are real directories):
- every directory belongs to
root:rootand is not writable by group or others; - every file belongs to
rootand is not writable by group or others.
Files may belong to another group: Ubuntu ships setgid helpers owned by groups such as
shadow, _ssh or messagebus. Two packaging exceptions are accepted:
- the files of
{{ owner_exceptions }}(formatpath:owner) may belong to that owner, as long as group and others cannot write them; - directories under
/usr/localmay beroot:staffand writable by groupstaff, as long as that group has no member.
A deviation here usually means software installed outside the package manager, or tampering.
The fix is therefore manual: find the cause before changing the owner, because chown also
clears setuid and setgid bits.
Ubuntu 20.04 has both exceptions on a fresh install: the at package installs /usr/bin/at
setuid and setgid daemon:daemon, and Python creates /usr/local/lib/python3.8 group-writable
by staff, an old Debian convention for letting local administrators install into
/usr/local. Members of staff can plant code that root runs (a Python module, for example),
so they count as root: the check fails as soon as the group has a member. Ubuntu 22.04 and
later no longer have either.
Rationale
Programs in these directories run as root through services, cron, sudo and package scripts, and libraries are loaded into every process. Write access for anyone but root lets that account replace them and gain root, or persist unnoticed.
Impact
None on a standard system, where packages install everything as root. Software installed by
hand under /usr/local by a regular user must be given to root.
Parameters
| Parameter | Core | Server | Server hardened | Desktop | Desktop hardened | Bounds |
|---|---|---|---|---|---|---|
owner_exceptionslist. Files accepted with a non-root owner, as path:owner. | /usr/bin/at:daemon | /usr/bin/at:daemon | /usr/bin/at:daemon | /usr/bin/at:daemon | /usr/bin/at:daemon |
The value used is your override (tailoring file), else the profile's, else the one of the profile it extends, else the default. Bounds limit what an override may set.
Audit
Run, for each of the directories above that exists and is not a symbolic link:
find <dir> -xdev \( -type d \( ! -user root -o ! -group root -o -perm /022 \) \) -o \( ! -type d ! -type l \( ! -user root -o -perm /022 \) \) -print
The command must print nothing.
Automated: every check must pass. Scanners report each check by its id.
system-dirs
Owner, group and write bits of directories and files in system program and library directories.
script: |
out=$(for d in /usr/bin /usr/sbin /usr/lib /usr/lib64 /usr/libexec /usr/local/bin /usr/local/sbin /usr/local/lib /bin /sbin /lib /lib64; do
[ -d "$d" ] && [ ! -L "$d" ] || continue
find "$d" -xdev \( \( -type d \( ! -user root -o ! -group root -o -perm /022 \) \) -o \
\( ! -type d ! -type l \( ! -user root -o -perm /022 \) \) \) -print 2>/dev/null
done | sort -u)
staff=$(getent group staff | cut -d: -f4)
bad=
while IFS= read -r f; do
[ -n "$f" ] || continue
u=$(stat -c %U "$f") g=$(stat -c %G "$f") m=$(stat -c %a "$f")
ok=0
for e in $CB_OWNER_EXCEPTIONS; do [ "$e" = "$f:$u" ] && ok=1; done
if [ "$ok" = 1 ] && [ $((0$m & 022)) -eq 0 ]; then
echo "accepted: $f owned by $u"; continue
fi
case $f in /usr/local/*)
if [ -d "$f" ] && [ "$u:$g" = root:staff ] && [ -z "$staff" ] && [ $((0$m & 02)) -eq 0 ]; then
echo "accepted: $f writable by group staff, which has no member"; continue
fi ;;
esac
bad="$bad$f
"
done <<EOF
$out
EOF
[ -z "$bad" ] && { echo "system programs and libraries belong to root and are not writable by others"; exit 0; }
echo "$(printf '%s' "$bad" | wc -l) item(s):"
printf '%s' "$bad" | head -n 20 | while IFS= read -r f; do stat -c '%A %U:%G %n' "$f"; done
exit 1
Remediation
For each reported item (the exceptions of the description aside):
- Find its package with
dpkg -S <path>and check it withdpkg --verify <package>. A changed file from a package is a possible compromise: investigate first, then reinstall the package withapt-get install --reinstall <package>. - For files installed by hand, run
chown root <path>(then restore any setuid or setgid bit that was intended) andchmod go-w <path>; for directories,chown root:root <dir>andchmod go-w <dir>. - Under
/usr/local, either keep groupstaffwithout members, or runchmod -R g-w /usr/localandchgrp -R root /usr/local.
References
- file-hierarchy(7), Ubuntu manual pages Canonical, vendor-doc, accessed 2026-10-19
- dpkg(1), Ubuntu manual pages (--verify) Canonical, vendor-doc, accessed 2026-10-19
- ANSSI-BP-028 v2.0 - Recommandations de configuration d'un système GNU/Linux (R50) ANSSI, guide, accessed 2026-10-19
Mappings
| Framework | ID | Relation | Note |
|---|---|---|---|
| ANSSI-BP-028 - Recommandations de configuration d'un système GNU/Linux2.0 | R50 | partial | R50 covers every sensitive file; this control covers system programs and libraries. |
| NIST SP 800-53 Rev. 55.1.1 | CM-5(6) | equivalent |
How this control relates to the other framework's: equivalent, stricter, looser, partial or related.
Threats mitigated
Related controls
- related
CB-UBU-FS-010Review setuid and setgid programs and keep only those that are needed
People and lifecycle
| Authors |
|
| Verified | 2026-10-19 by Sn0wAlice on 20.04, Proxmox VM 2520, Ubuntu 20.04 cloud image, kernel 5.4.0-216-generic. Passes on fresh install with the two exceptions reported (/usr/bin/at owned by daemon, /usr/local/lib/python3.8 group staff); fails once staff has a member; group-writable file owned by nobody in /usr/local/bin detected. |
| Verified | 2026-10-19 by Sn0wAlice on 22.04, Proxmox VM 2522, Ubuntu 22.04 cloud image, kernel 5.15.0-198-generic. Passes on fresh install; group-writable file owned by nobody in /usr/local/bin detected. |
| Verified | 2026-10-19 by Sn0wAlice on 24.04, Proxmox VM 2524, Ubuntu 24.04 cloud image, kernel 6.8.0-147-generic. Passes on fresh install. |
| Verified | 2026-10-19 by Sn0wAlice on 26.04, Proxmox VM 2526, Ubuntu 26.04 cloud image, kernel 7.0.0-38-generic. Passes on fresh install; group-writable file owned by nobody in /usr/local/bin detected. |
| Introduced in | v0.1.0 |
| Created, updated | 2026-10-19, 2026-10-19 |
| Translations | fr |