User Minus One Is Root — Java Bug Hunt
Modelled on sudo CVE-2019-14287 (October 2019): with a sudoers rule meant to allow running a command as any user except root ((ALL, !root)), asking for user…
- Language: Java
- Layer: Backend
- Difficulty: Easy
- Concepts: Security, Auth, Overflow
- Modelled on: sudo · CVE-2019-14287
- Visible tests: an ordinary user id is allowed; user id -1 is denied
- Reward: 50 XP for a complete fix
Briefing
Modelled on sudo CVE-2019-14287 (October 2019): with a sudoers rule meant to allow running a command as any user except root ((ALL, !root)), asking for user id -1 (or its unsigned form 4294967295) passed the "not root" check — and because the system call used to switch ids treats -1 as "leave this id unchanged", the command ran with sudo's own id: root.
RunAs.java reconstructs the runas step: parse #<uid>, check the policy, switch ids through Kernel.setresuid.
Fix RunAs.run so user id -1 can never be requested.
Bug report
BUG-SUDO-14287 · Priority: Critical (privilege escalation) · Reported by: security
run(spec) returns "uid=<n>" for the effective uid the command runs as, or "denied".
- spec must be "#" followed by a decimal integer, else "denied"
- values are 32-bit uids: a negative n means n + 2^32 (so "#-2" is 4294967294); values below -2^31 or above 4294967295 are "denied"
- 4294967295 (-1) is not a user id — it is setresuid's "unchanged" marker — and must be "denied", however it is written
- the policy (RunasPolicy.allows) must then approve the uid
Observed: run("#-1") returns "uid=0" — the policy said "not root" and the command ran as root.
Logs
[sudo] alice : TTY=pts/0 ; USER=#-1 ; COMMAND=/usr/bin/id
[sudo] policy (ALL, !root): target uid 4294967295 != 0 -> allowed
[id] uid=0(root) gid=1000(alice)The code as shipped
RunAs.java (editable)
class RunAs {
static String run(String spec) {
if (spec == null || !spec.startsWith("#")) return "denied";
long n;
try {
n = Long.parseLong(spec.substring(1));
} catch (NumberFormatException e) {
return "denied";
}
if (n < -2147483648L || n > 0xFFFFFFFFL) return "denied";
long uid = n & 0xFFFFFFFFL;
if (!RunasPolicy.allows(uid)) return "denied";
return "uid=" + Kernel.setresuid(uid);
}
}Read-only context: Kernel.java, RunasPolicy.java.
Open the hunt to edit the files, run the visible tests and submit against the hidden ones. More Java bug hunts.