Skip to content

fix(libc): correct privilege drop order and sentinel in js_os_exec#1487

Open
andreasrosdal wants to merge 1 commit into
quickjs-ng:masterfrom
nordstjernen-web:claude/sec-os-exec-privdrop
Open

fix(libc): correct privilege drop order and sentinel in js_os_exec#1487
andreasrosdal wants to merge 1 commit into
quickjs-ng:masterfrom
nordstjernen-web:claude/sec-os-exec-privdrop

Conversation

@andreasrosdal
Copy link
Copy Markdown
Contributor

Two related bugs in os.exec({uid, gid}):

  1. The drop ran setuid() before setgid()/setgroups(). After setuid() to a non-root uid, the process no longer holds the capability required for setgid()/setgroups() to succeed, so the gid drop silently failed and the spawned program kept the parent's gid. Reorder: groups, then gid, then uid.

  2. The "was the option supplied" check used uid == (uint32_t)-1 and gid == (uint32_t)-1 as the "unset" sentinel. The legal POSIX value 0xFFFFFFFF collides with this sentinel, so passing exec({uid: 0xFFFFFFFF}) silently skipped the drop. Replace with explicit uid_set / gid_set bools.

No test: setuid()/setgid() require either root or capabilities and behave differently across platforms (Linux vs. *BSD vs. macOS), so a portable automated test isn't viable. Verified by reading the diff against the Linux setuid(2) and setgid(2) man pages.

Two related bugs in os.exec({uid, gid}):

1. The drop ran setuid() before setgid()/setgroups(). After setuid()
   to a non-root uid, the process no longer holds the capability
   required for setgid()/setgroups() to succeed, so the gid drop
   silently failed and the spawned program kept the parent's gid.
   Reorder: groups, then gid, then uid.

2. The "was the option supplied" check used `uid == (uint32_t)-1`
   and `gid == (uint32_t)-1` as the "unset" sentinel. The legal
   POSIX value 0xFFFFFFFF collides with this sentinel, so passing
   exec({uid: 0xFFFFFFFF}) silently skipped the drop. Replace with
   explicit uid_set / gid_set bools.

No test: setuid()/setgid() require either root or capabilities and
behave differently across platforms (Linux vs. *BSD vs. macOS), so a
portable automated test isn't viable. Verified by reading the diff
against the Linux setuid(2) and setgid(2) man pages.
Comment thread quickjs-libc.c
Comment on lines +3583 to +3584
uint32_t uid = 0, gid = 0;
bool uid_set = false, gid_set = false;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can leave uid and gid uninitialized, that way the compiler hopefully complains if we use them without initializing them first. Or does it give off false negatives?

The names uid_set and gid_set imply the action is already done. A better name is do_setuid or must_setuid or something along those lines.

Comment thread quickjs-libc.c
Comment on lines +3761 to +3765
/* Drop privileges in the correct order: supplementary groups and the
primary gid must change before setuid(), because setuid(non-root)
strips the capability needed for setgroups()/setgid() to succeed.
Track "was set" with explicit bools instead of a (uint32_t)-1
sentinel, which collided with the legitimate value 0xFFFFFFFF. */
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's got that waffling LLM quality to it and I don't like seeing that in code I have to look at.

Suggested change
/* Drop privileges in the correct order: supplementary groups and the
primary gid must change before setuid(), because setuid(non-root)
strips the capability needed for setgroups()/setgid() to succeed.
Track "was set" with explicit bools instead of a (uint32_t)-1
sentinel, which collided with the legitimate value 0xFFFFFFFF. */

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants