Skip to content

Usernames with uppercase letters cannot get a comm socket (leapctl@.service restart loop) #4

Description

@Hoazit

Hello,

I ran into a bug in privleap that makes it unusable for any account whose
username contains an uppercase letter. Details and reproduction below.

Summary

normalize_user_id() rejects any username containing uppercase letters, so
accounts like Example_User can never be granted a comm socket. privleap is
effectively unusable for those users, and leapctl@<uid>.service restarts
forever.

Environment

  • Kicksecure 18 on Debian 13.6 (trixie), kernel 6.12.107+deb13-amd64
  • privleap 3:6.1-1
  • user-sysmaint-split 3:12.7-1, systemcheck 3:52.2-1
  • Affected account: Example_User, UID 1000

What happens

privleapd.service is active and creates comm sockets normally:

$ ls /run/privleapd/comm/
root  sdwdate  systemcheck  tb-updater

Every one of those names is lowercase. The socket for UID 1000 is missing, and
the unit that should create it never succeeds:

$ systemctl status leapctl@1000.service
   Active: activating (auto-restart) (Result: exit-code)
  Process: ExecStart=/usr/bin/leapctl --create 1000 (code=exited, status=1/FAILURE)
$ systemctl show leapctl@1000.service -p NRestarts -p ExecMainStatus
NRestarts=1806
ExecMainStatus=1

That count was taken after 10 hours of uptime: one attempt every 20 seconds
(RestartSec=20), each spawning a Python process, for as long as the machine
stays on.

Root cause

user_name_regex in privleap.py:1230 accepts lowercase only:

user_name_regex: re.Pattern[str] = re.compile(r"[a-z_][-a-z0-9_]*\$?\Z")

normalize_user_id() (privleap.py:1628) gates on it, so an existing account
is reported as nonexistent:

$ python3 -c "from privleap.privleap import PrivleapCommon as P; \
    print(P.normalize_user_id('1000'), P.normalize_user_id('Example_User'))"
Example_User None

The UID path works, so leapctl --create 1000 resolves the name and sends it
to the daemon. The daemon then normalizes the same name a second time
(privleapd.py:316), this time hitting the regex, logs "Account does not
exist" and returns a control error. leapctl exits 1.

Two things worth noting:

  • RestartPreventExitStatus=2 in leapctl@.service is meant to stop the loop
    when an account is legitimately not permitted to have a socket (exit 2).
    This failure exits 1, so the loop never stops.
  • The regex is redundant as an existence check: inside the same branch,
    normalize_user_id() already verifies membership in pwd.getpwall().

Reproduction

  1. Create an account whose name contains an uppercase letter.
  2. Log in.
  3. ls /run/privleapd/comm/ — no socket for that account.
  4. systemctl status leapctl@$(id -u).service — restart loop, exit status 1.

The name is valid by Debian's own rules

Debian's adduser accepts uppercase in non-system usernames. From
man 5 adduser.conf, NAME_REGEX "defaults to the most conservative
^[a-zA-Z][a-zA-Z0-9_-]*\$?$", and SYS_NAME_REGEX to
^[a-zA-Z_][a-zA-Z0-9_-]*\$?$. Both allow A-Z.

$ python3 -c "
import re
print(bool(re.match(r'^[a-zA-Z][a-zA-Z0-9_-]*\$?\$', 'Example_User')))   # adduser
print(bool(re.match(r'[a-z_][-a-z0-9_]*\$?\Z',      'Example_User')))   # privleap
"
True
False

So an account created within Debian's most conservative default is rejected by
privleap.

Knock-on effect: systemcheck reports Tor as down

systemcheck queries Tor through privleap. In
helper-scripts/tor_bootstrap_check.bsh, check_tor_circuit_established()
runs the tor-circuit-established-check privleap action, and on any non-zero
exit code falls through to:

tor_circuit_established="0"
tor_circuit_established_word="not established"

So a privleap failure is reported to the user as "Tor Circuit: not
established", repeated every 2 seconds for the full timeout. On the affected
machine Tor was working the whole time:

$ curl -sS --socks5-hostname 127.0.0.1:9050 https://check.torproject.org/api/ip
{"IsTor":true, ...}

This is the part that worries users: the check reports a broken Tor connection
when the only thing broken is the socket it uses to ask. Distinguishing
"could not query" from "not established" would help regardless of how the
username issue is resolved.

Suggested fix

Allow uppercase in user_name_regex, or drop the regex gate in
normalize_user_id() and rely on the pwd lookup that already follows it.

Still present in master as of 2026-09-15.

Thanks for your work on Kicksecure.


Investigated with Claude Code. Every command above was run on the affected
machine and the outputs are pasted as produced, with two edits: the account
name is replaced by Example_User throughout, and the exit relay address is
trimmed from the check.torproject.org response.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions