cybersecurity · beginner · ~15 min · safe pentest lab

Audit File Modes for Privilege-Escalation Bits

Recognize the special Unix permission bits that enable local privilege escalation (setuid/setgid/world-writable) and test for them safely with bitmasks, without assuming a superficially normal mode is harmless.

Challenge

Static permission audit

On Linux, a file's mode carries special permission bits that a privilege-escalation attacker loves to find. A setuid binary (04000) runs with the file owner's identity — a setuid-root program is a classic escalation path. A setgid binary (02000) runs with the file's group. A world-writable file (00002) lets any local user rewrite its contents, which can hijack scripts, configs, or a Docker mount.

You are writing the core check for an offline auditor. The harness bakes a fixed array of file modes into memory (no live filesystem, no network, no target). Your job is purely arithmetic on the bits handed to you.

Implement:

int is_dangerous_mode(unsigned mode);

Return 1 if any of these bits is set: setuid (04000), setgid (02000), or world-writable (00002). Otherwise return 0. The insecure assumption to avoid is treating an ordinary-looking mode like 0755 as safe while ignoring an added setuid bit that turns it into 04755.

Edge cases

  • 0000 and other benign modes (e.g. 0644, 0755) are safe -> return 0.
  • The sticky bit 01000 alone is not in scope and must return 0.
  • Every combination of the dangerous bits (e.g. 06755) is dangerous, as is an all-ones mode 0xFFFFFFFF.

Input format

A single unsigned integer mode holding Unix permission bits (octal), taken from a fixed in-harness array. No I/O; the value is passed as the function argument.

Output format

Return an int: 1 if the mode has setuid, setgid, or world-writable set; 0 otherwise.

Constraints

C11, freestanding logic (no syscalls, no filesystem, no allocation). mode may be any value in 0 .. 0xFFFFFFFF. Test only the three bits in scope: 04000, 02000, 00002. Do not treat sticky (01000) or any other bit as dangerous. Constant-time bit checks only.

Starter code

int is_dangerous_mode(unsigned mode){
    /* TODO: audit the permission bits. Deny by default is NOT enough here —
       you must actually detect setuid (04000), setgid (02000), and
       world-writable (00002). Return 1 if any is set, else 0. */
    (void)mode;
    return -1; /* placeholder: not a valid answer */
}

Common mistakes

Using logical AND (&&) instead of bitwise AND (&); comparing mode == 04000 (exact equality) instead of masking, which misses combined modes like 04755; forgetting the world-writable bit and only checking setuid/setgid; returning the masked value directly (which can be a non-1 truthy number) when the contract asks for exactly 0 or 1; confusing the sticky bit 01000 with a dangerous bit.

Edge cases to handle

mode 0 (no bits) -> 0; benign 0644 and 0755 -> 0; sticky-only 01000 -> 0 (out of scope, must be rejected as non-dangerous); world-writable-only 00002 -> 1; setuid+setgid combos like 06755 -> 1; all-ones 0xFFFFFFFF -> 1.

Background lessons

Solve this exercise in the browser editor — compile and run against the test harness, no setup required.