Skip to content

m_jupe: require PRIV_LOCAL_JUPE on the implicit-local JUPE form - #123

Open
MrIron-no wants to merge 1 commit into
UndernetIRC:mainfrom
MrIron-no:fix/jupe-local-priv-bypass
Open

MrIron-no wants to merge 1 commit into
UndernetIRC:mainfrom
MrIron-no:fix/jupe-local-priv-bypass

Conversation

@MrIron-no

Copy link
Copy Markdown
Contributor

Fixes #106.

Summary

In mo_jupe() all privilege checks lived inside the if (target) block.
The four-parameter form omits <target>, defaults to a local jupe
(JUPE_LOCAL), and fell straight through to jupe_add() /
jupe_activate() with no HasPriv() check. So these did the same thing
but only the second was gated:

JUPE +evil.server 3600 :reason                 (was ungated)
JUPE +evil.server my.server.name 3600 :reason  (requires PRIV_LOCAL_JUPE)

An oper whose Operator block or class explicitly revokes local_jupe
could still create, activate and deactivate local jupes by omitting the
target. On a hub an active local jupe refuses the named server at link
time and squits it if introduced, so this granted real operational power
contrary to explicit configuration.

Mitigating factors (from the issue): only opers reach mo_jupe(),
CONFIG_OPERCMDS (default off) gates the whole path, and LOCAL_JUPE is
in the default privilege set — so it only bit where an admin had
deliberately removed the privilege.

Fix

Gate the implicit-local branch with PRIV_LOCAL_JUPE, mirroring
mo_gline()'s unconditional PRIV_LOCAL_GLINE check on its local path.
Two lines, no behaviour change for anyone who has the privilege.

Enforcement audit

I traced every path in mo_jupe() and confirmed each jupe_* call
honours the flag its privilege is paired with, so a local privilege
cannot produce a global effect:

Command shape Privilege Flag Effect
implicit local (parc 4) PRIV_LOCAL_JUPE (fixed) JUPE_LOCAL local, no propagation
explicit target = this server PRIV_LOCAL_JUPE JUPE_LOCAL local, no propagation
explicit remote target PRIV_JUPE relayed forwarded, returns
target * PRIV_JUPE none global, propagated

propagate_jupe() returns early on JupeIsLocal, so a JUPE_LOCAL jupe
never leaves the server; a PRIV_LOCAL_JUPE-only oper acting on an
existing global jupe only toggles the local JUPE_LDEACT state without
propagating. ms_jupe() (server-to-server) is trusted and correctly has
no privilege check.

Tests

Verified by inspection and a clean -Wall compile. A behavioural
regression test (an oper with local_jupe revoked getting
ERR_NOPRIVILEGES on the implicit-local form) needs the integration
harness and can follow.

In mo_jupe() all privilege checks lived inside the "if (target)" block.
The four-parameter form omits <target>, defaults to a local jupe
(JUPE_LOCAL), and fell through to jupe_add()/jupe_activate() with no
HasPriv() check. An oper whose Operator block or class explicitly
revokes local_jupe could therefore still create, activate and deactivate
local jupes by omitting the target parameter:

  JUPE +evil.server 3600 :reason                 (was ungated)
  JUPE +evil.server my.server.name 3600 :reason  (required PRIV_LOCAL_JUPE)

On a hub an active local jupe refuses the named server at link time and
squits it if introduced, so this granted real operational power contrary
to explicit configuration. Only opers reach mo_jupe(), CONFIG_OPERCMDS
(default off) gates the whole path, and LOCAL_JUPE is in the default
privilege set, so it only bit where an admin had deliberately removed the
privilege.

Gate the implicit-local branch with PRIV_LOCAL_JUPE, mirroring
mo_gline()'s unconditional PRIV_LOCAL_GLINE check on its local path.

Closes UndernetIRC#106.
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.

mo_jupe: implicit-local JUPE form bypasses the PRIV_LOCAL_JUPE check

1 participant