[Bug 297512] kern.proc.env sysctl fails with ENOMEM when last env string is near the end of the stack mapping; breaks ConsoleKit2 session lookup and GNOME screen unlock

[email protected]
Newsgroups gmane.os.freebsd.bugs
Message-ID <[email protected]/bugzilla/>
https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=297512

            Bug ID: 297512
           Summary: kern.proc.env sysctl fails with ENOMEM when last env
                    string is near the end of the stack mapping; breaks
                    ConsoleKit2 session lookup and GNOME screen unlock
           Product: Base System
           Version: 15.1-RELEASE
          Hardware: Any
                OS: Any
            Status: New
          Severity: Affects Only Me
          Priority: ---
         Component: kern
          Assignee: [email protected]
          Reporter: [email protected]

Following a lock screen problem on GNOME, the source has been tracked down by
Fable as an error in the kernel component. Here is the full tested report.

On FreeBSD 15.1-RELEASE, sysctl kern.proc.env.<pid> (used by procstat
penv, kvm_getenvv(), and ConsoleKit2's GetSessionForUnixProcess) fails
with ENOMEM for a subset of processes, determined at exec time by
stack-gap ASLR.

Real-world impact: when it fails for gnome-shell, the GNOME screen
shield cannot resolve its ConsoleKit session. The visible symptom is a
lock screen that accepts no password: wrong passwords are rejected
normally, but a correct password does nothing at all (PAM
authentication succeeds, the unlock completion signal is never
delivered), permanently locking the user out of the session.

ROOT CAUSE

get_ps_strings() (sys/kern/kern_proc.c) reads each argv/env string in
256-byte chunks via proc_read_string() -> vmspace_iop(). The comment in
proc_read_string() says a short read at the end of a mapped region is
expected and handled:

    This may return a short read if the string is shorter than the
    chunk and is aligned at the end of the page, and the following page
    is not mapped.

However vmspace_iop() (sys/kern/sys_process.c) discards partial
transfers whenever the underlying vmspace_rwmem() reports an error:

    error = vmspace_rwmem(vm, &uio);
    if (error != 0 || uio.uio_resid == slen)
            return (-1);
    return (slen - uio.uio_resid);

vmspace_rwmem() copies page by page and returns EFAULT when the next
page fault fails, even if previous pages were copied successfully. So
when the last environment string starts within 256 bytes of the end of
the stack mapping, the chunk read crosses the mapping end,
vmspace_rwmem() returns EFAULT with a partial transfer (the entire
string was actually read), and vmspace_iop() throws the data away and
returns -1. proc_read_string() turns that into ENOMEM and the whole
sysctl fails.

Whether a process is affected depends on the sub-page stack offset
chosen by ASLR at exec time (exec_new_vmspace(): stack_top -=
rounddown2(stack_off & PAGE_MASK, sizeof(void *))), combined with the
size of the fixed data above the last env string (execpath, canary,
page sizes, ps_strings). With kern.elf64.aslr.stack=0 the bug is masked
because the region above the stack top is adjacent to other mapped
pages, so the over-read succeeds.

EVIDENCE (live system, gnome-session pid 4246)

DTrace on vmspace_rwmem during a failing "procstat penv 4246" -- every
read succeeds except the last string, which is read almost completely
(249 of 256 bytes; the full string content plus NUL is included) and
then discarded:

    @ 0x820d4ffa0 req=32  left=0 err=0     <- ps_strings
    @ 0x820d4f858 req=208 left=0 err=0     <- env vector (26 entries)
    @ 0x820d4fbf5 req=256 left=0 err=0     <- env[0]
      ... 24 more successful string reads ...
    @ 0x820d4ff07 req=256 left=7 err=14    <- env[25]
                                              "SSH_AGENT_PID=4255",
                                              EFAULT crossing end of
                                              stack mapping at
                                              0x820d50000

Reading the same addresses via ptrace(PT_IO) succeeds and returns the
complete, intact environment, including the XDG_SESSION_COOKIE that
ConsoleKit2 needs.

Observed on two processes of one GNOME session (gnome-session-binary
and gnome-shell): last env string 249 and 241 bytes from the end of the
stack mapping respectively -- both < 256, so both unreadable. Sibling
processes with distance >= 256 read fine.

IMPACT

- procstat penv <pid> randomly fails ("Cannot allocate memory") for a
  subset of processes, re-rolled at every exec.
- ConsoleKit2 GetSessionForUnixProcess fails for affected processes (it
  reads XDG_SESSION_COOKIE via kvm_getenvv -> this sysctl).
- GNOME (x11/gnome-shell 47 + sysutils/consolekit2): when gnome-shell
  is affected, ScreenShield._getLoginSession() fails at session start:

      GNOME Shell-CRITICAL: Could not get a proxy for the current
      session: ...ConsoleKit.Manager.Error.General: Unable to lookup
      session information for process 'NNNN'

  and the lock screen can never complete an unlock: PAM authentication
  succeeds but the success is not signalled back to the shield. The
  user is locked out until the session is killed or the shield is
  deactivated via D-Bus by root.

REGRESSION HISTORY

FreeBSD 12.x/13.x proc_iop() deliberately ignored the proc_rwmem()
error and returned partial transfers (only failing when zero bytes were
read):

    proc_rwmem(p, &uio);
    if (uio.uio_resid == slen)
            return (-1);
    return (slen - uio.uio_resid);

The refactor that introduced vmspace_iop() (first appearing in
stable/14) added the "error != 0 ||" clause, silently changing the
short-read semantics that proc_read_string() documents and depends on.
The defect is present in stable/14, releng/15.1, and main as of
2026-08-14.

SUGGESTED FIX

Let vmspace_iop() report partial transfers instead of discarding them,
per proc_read_string()'s documented expectation:

--- sys/kern/sys_process.c
+++ sys/kern/sys_process.c
@@ vmspace_iop
        error = vmspace_rwmem(vm, &uio);
-       if (error != 0 || uio.uio_resid == slen)
+       if (uio.uio_resid == slen)
                return (-1);
        return (slen - uio.uio_resid);

Callers that require a full-length transfer (get_proc_vector()) already
compare the return value against the requested size, so their behavior
is unchanged. This also restores the historical proc_readmem()
semantics.

RELATED REPORTS (not duplicates)

- Bug 248537 "procstat -e/kvm_getenvv() fails for specific processes"
  (2020, filed against 12.1, still open): same surface symptom. 12.x/
  13.x tolerated partial reads, so pre-14 the sysctl only failed when a
  chunk read returned zero bytes (a much rarer alignment). The
  regression described here makes the same symptom far more common: any
  process whose last env string starts within 256 bytes of the end of
  the stack mapping now fails, with a pristine, verified-intact env
  block (read back completely via ptrace(PT_IO)).
- GNOME Discourse "Gnome on FreeBSD - can't unlock desktop" (Nov 2025,
  https://discourse.gnome.org/t/gnome-on-freebsd-cant-unlock-desktop/32293):
  same user-facing GNOME symptom (correct password silently re-prompts,
  wrong password errors normally); no root cause identified there.

WORKAROUNDS

- sysctl kern.elf64.aslr.stack=0 (masks the bug; costs stack ASLR).
- For GNOME: logging out and back in re-rolls the stack layout and
  usually restores working unlock.
- Root can force-unlock a stuck shield, as the session user on their
  session bus:
      gdbus call --session --dest org.gnome.ScreenSaver \
        --object-path /org/gnome/ScreenSaver \
        --method org.gnome.ScreenSaver.SetActive false

ENVIRONMENT

FreeBSD 15.1-RELEASE-p2 amd64 (GENERIC), gnome-shell 47, gdm 47.0_2,
consolekit2 (sysutils/consolekit2).

VALIDATION OF THE PREDICTOR (before patch)

A scan of all 139 user processes on a live 15.1 system, measuring each
process's last-env-string distance to the next page boundary and
whether that page is mapped (via ptrace), matches the failure exactly:
4 processes were predicted affected (distance < 256 and next page
unmapped) and precisely those 4 -- powerd, gnome-session-binary,
gnome-shell, gsd-sound -- fail kern.proc.env with ENOMEM; the other
135 succeed. No mismatch in either direction.

PATCH TESTED

The suggested fix was applied to releng/15.1 sources and the rebuilt
GENERIC kernel was booted on the affected machine. Results:

- Whole-system scan after reboot: 112 processes, zero kern.proc.env
  failures. Two processes (powerd, evolution-addressbook) landed in the
  previously-failing layout (last env string < 256 bytes from an
  unmapped page boundary) and both read correctly. powerd had the same
  in-window layout on the unpatched kernel and failed with ENOMEM
  there, so it provides a direct same-daemon before/after comparison.
- On-demand reproduction: repeatedly spawning /bin/sleep with varying
  environment sizes until one instance landed in the failing layout
  (distance 214, next page unmapped); procstat penv read its complete
  environment without error on the patched kernel.
- GNOME: fresh session shows no ConsoleKit CRITICAL in gnome-shell's
  log, GetSessionForUnixProcess resolves gnome-shell to its session,
  and screen lock/unlock works. (Note: whether gnome-shell itself lands
  in the failing layout is re-rolled each login; the sysctl-level tests
  above are the deterministic evidence.)

-- 
You are receiving this mail because:
You are the assignee for the bug.
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.