Repository navigation
fix(linux): repair disabled session bus in daemon and PTY environments #21120
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
a4153ec
fix(pty): repair disabled session-bus address in spawned Linux envs
oliver-mee 99d5a7a
fix(daemon): pass the repaired bus address to the daemon and its PTYs
oliver-mee e7e70bf
refactor(pty): reuse the durable-scope bus resolver and cover every s…
oliver-mee d7db8d3
test(pty): guard session bus checks on Windows
oliver-mee File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| import { afterEach, describe, expect, it, vi } from 'vitest' | ||
| import { createDaemonPtyEnvironment } from '../daemon/pty-subprocess/spawn-environment' | ||
| import { | ||
| createLocalPtyLaunchPlan, | ||
| DeferredLocalPtyLaunchPlan | ||
| } from '../providers/local-pty-launch-plan' | ||
| import { buildLocalPtySpawnEnvironment } from '../providers/local-pty-spawn-environment' | ||
|
|
||
| const { REPAIRED, repairSessionBusEnv } = vi.hoisted(() => { | ||
| const repairedAddress = 'unix:path=/run/user/1000/bus' | ||
| /** Apply a fixed address so wiring tests isolate call sites from socket lookup. */ | ||
| const repairSessionBusEnv = (env: Record<string, string | undefined>) => { | ||
| if (env.DBUS_SESSION_BUS_ADDRESS === 'disabled:') { | ||
| env.DBUS_SESSION_BUS_ADDRESS = repairedAddress | ||
| } | ||
| } | ||
| return { REPAIRED: repairedAddress, repairSessionBusEnv } | ||
| }) | ||
|
|
||
| // Why: the helper itself is covered against real sockets in dbus-session-bus-env.test.ts. Here a | ||
| // stand-in proves each spawn path calls it on the final env, so removing a call fails a test. | ||
| vi.mock('./dbus-session-bus-env', () => ({ | ||
| repairDisabledSessionBusEnv: repairSessionBusEnv | ||
| })) | ||
|
|
||
| vi.mock('../providers/local-pty-utils', () => ({ | ||
| ensureNodePtySpawnHelperExecutable: vi.fn(), | ||
| validateWorkingDirectory: vi.fn() | ||
| })) | ||
|
|
||
| afterEach(() => vi.unstubAllEnvs()) | ||
|
|
||
| describe('session-bus repair wiring', () => { | ||
| it('repairs the marker inherited by daemon PTY shells', () => { | ||
| vi.stubEnv('DBUS_SESSION_BUS_ADDRESS', 'disabled:') | ||
|
|
||
| const env = createDaemonPtyEnvironment({ sessionId: 'session', cols: 80, rows: 24 }) | ||
|
|
||
| expect(env.DBUS_SESSION_BUS_ADDRESS).toBe(REPAIRED) | ||
| }) | ||
|
|
||
| it('repairs the marker passed explicitly to a daemon PTY', () => { | ||
| const env = createDaemonPtyEnvironment({ | ||
| sessionId: 'session', | ||
| cols: 80, | ||
| rows: 24, | ||
| env: { DBUS_SESSION_BUS_ADDRESS: 'disabled:' } | ||
| }) | ||
|
|
||
| expect(env.DBUS_SESSION_BUS_ADDRESS).toBe(REPAIRED) | ||
| }) | ||
|
|
||
| it.skipIf(process.platform === 'win32')( | ||
| 'repairs the marker inherited by local PTY shells', | ||
| () => { | ||
| vi.stubEnv('DBUS_SESSION_BUS_ADDRESS', 'disabled:') | ||
| const spawn = { cwd: '/tmp', cols: 80, rows: 24 } | ||
| const getOptions = () => ({}) | ||
| const plan = createLocalPtyLaunchPlan(spawn, () => ({ getDefaultShell: () => '/bin/sh' })) | ||
| if (plan instanceof DeferredLocalPtyLaunchPlan) { | ||
| throw new Error('a POSIX launch plan is never deferred') | ||
| } | ||
|
|
||
| const env = buildLocalPtySpawnEnvironment({ id: 'pty', spawn, getOptions, plan }) | ||
|
|
||
| expect(env).toMatchObject({ DBUS_SESSION_BUS_ADDRESS: REPAIRED }) | ||
| } | ||
| ) | ||
| }) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,119 @@ | ||
| import { mkdtempSync, rmSync, writeFileSync } from 'node:fs' | ||
| import { createServer, type Server } from 'node:net' | ||
| import { tmpdir } from 'node:os' | ||
| import { join } from 'node:path' | ||
| import { afterEach, describe, expect, it } from 'vitest' | ||
| import { repairDisabledSessionBusEnv } from './dbus-session-bus-env' | ||
|
|
||
| const servers: Server[] = [] | ||
| const dirs: string[] = [] | ||
|
|
||
| /** Create a private runtime directory with a listening Unix-domain socket at `bus`. */ | ||
| function runtimeDirWithBus(): string { | ||
| const dir = mkdtempSync(join(tmpdir(), 'xdg-runtime-with-bus-')) | ||
| const server = createServer() | ||
| server.listen(join(dir, 'bus')) | ||
| servers.push(server) | ||
| dirs.push(dir) | ||
| return dir | ||
| } | ||
|
|
||
| /** Create a private runtime directory without a bus socket. */ | ||
| function runtimeDirWithoutBus(): string { | ||
| const dir = mkdtempSync(join(tmpdir(), 'xdg-runtime-no-bus-')) | ||
| dirs.push(dir) | ||
| return dir | ||
| } | ||
|
|
||
| afterEach(() => { | ||
| for (const server of servers.splice(0)) { | ||
| server.close() | ||
| } | ||
| for (const dir of dirs.splice(0)) { | ||
| rmSync(dir, { recursive: true, force: true }) | ||
| } | ||
| }) | ||
|
|
||
| describe('repairDisabledSessionBusEnv', () => { | ||
| it.skipIf(process.platform === 'win32')( | ||
| 'replaces the disabled marker with the per-UID user bus', | ||
| () => { | ||
| const perUidDir = runtimeDirWithBus() | ||
| const env: Record<string, string | undefined> = { DBUS_SESSION_BUS_ADDRESS: 'disabled:' } | ||
|
|
||
| repairDisabledSessionBusEnv(env, 'linux', perUidDir) | ||
|
|
||
| expect(env.DBUS_SESSION_BUS_ADDRESS).toBe(`unix:path=${perUidDir}/bus`) | ||
| } | ||
| ) | ||
|
|
||
| it.skipIf(process.platform === 'win32')( | ||
| 'prefers the per-UID bus over a hardened XDG_RUNTIME_DIR without a bus', | ||
| () => { | ||
| const perUidDir = runtimeDirWithBus() | ||
| const env: Record<string, string | undefined> = { | ||
| DBUS_SESSION_BUS_ADDRESS: 'disabled:', | ||
| XDG_RUNTIME_DIR: runtimeDirWithoutBus() | ||
| } | ||
|
|
||
| repairDisabledSessionBusEnv(env, 'linux', perUidDir) | ||
|
|
||
| expect(env.DBUS_SESSION_BUS_ADDRESS).toBe(`unix:path=${perUidDir}/bus`) | ||
| } | ||
| ) | ||
|
|
||
| it.skipIf(process.platform === 'win32')( | ||
| 'falls back to XDG_RUNTIME_DIR when there is no per-UID bus', | ||
| () => { | ||
| const xdgDir = runtimeDirWithBus() | ||
| const env: Record<string, string | undefined> = { | ||
| DBUS_SESSION_BUS_ADDRESS: 'disabled:', | ||
| XDG_RUNTIME_DIR: xdgDir | ||
| } | ||
|
|
||
| repairDisabledSessionBusEnv(env, 'linux', runtimeDirWithoutBus()) | ||
|
|
||
| expect(env.DBUS_SESSION_BUS_ADDRESS).toBe(`unix:path=${xdgDir}/bus`) | ||
| } | ||
| ) | ||
|
|
||
| it('leaves the marker when no reachable bus exists', () => { | ||
| const env: Record<string, string | undefined> = { DBUS_SESSION_BUS_ADDRESS: 'disabled:' } | ||
|
|
||
| repairDisabledSessionBusEnv(env, 'linux', runtimeDirWithoutBus()) | ||
|
|
||
| expect(env.DBUS_SESSION_BUS_ADDRESS).toBe('disabled:') | ||
| }) | ||
|
|
||
| it('does not treat a plain file named bus as a bus', () => { | ||
| const dir = runtimeDirWithoutBus() | ||
| writeFileSync(join(dir, 'bus'), '') | ||
| const env: Record<string, string | undefined> = { DBUS_SESSION_BUS_ADDRESS: 'disabled:' } | ||
|
|
||
| repairDisabledSessionBusEnv(env, 'linux', dir) | ||
|
|
||
| expect(env.DBUS_SESSION_BUS_ADDRESS).toBe('disabled:') | ||
| }) | ||
|
|
||
| it('leaves a real bus address and an absent address untouched', () => { | ||
| const perUidDir = runtimeDirWithoutBus() | ||
| const withAddress: Record<string, string | undefined> = { | ||
| DBUS_SESSION_BUS_ADDRESS: 'unix:path=/run/user/1000/bus' | ||
| } | ||
| const withoutAddress: Record<string, string | undefined> = { HOME: '/home/user' } | ||
|
|
||
| repairDisabledSessionBusEnv(withAddress, 'linux', perUidDir) | ||
| repairDisabledSessionBusEnv(withoutAddress, 'linux', perUidDir) | ||
|
|
||
| expect(withAddress).toEqual({ DBUS_SESSION_BUS_ADDRESS: 'unix:path=/run/user/1000/bus' }) | ||
| expect(withoutAddress).toEqual({ HOME: '/home/user' }) | ||
| }) | ||
|
|
||
| it('does nothing off Linux', () => { | ||
| const env: Record<string, string | undefined> = { DBUS_SESSION_BUS_ADDRESS: 'disabled:' } | ||
|
|
||
| repairDisabledSessionBusEnv(env, 'darwin', runtimeDirWithoutBus()) | ||
|
|
||
| expect(env.DBUS_SESSION_BUS_ADDRESS).toBe('disabled:') | ||
| }) | ||
| }) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| import { CANONICAL_USER_RUNTIME_DIR, resolveUserRuntimeDir } from '../daemon/daemon-cgroup-scope' | ||
|
|
||
| const DISABLED_SESSION_BUS_ADDRESS = 'disabled:' | ||
|
|
||
| /** | ||
| * Chromium sets `DBUS_SESSION_BUS_ADDRESS=disabled:` when it starts without a session bus, and | ||
| * service hardening can set the same marker. Headless serve then passes it into the daemon and | ||
| * every shell or agent it spawns, so `systemctl --user` and `systemd-run --user` fail with | ||
| * "Connection refused" although the user bus is healthy. | ||
| * | ||
| * Only the exact marker is changed. It becomes the address of the bus that | ||
| * `resolveUserRuntimeDir` finds (the per-UID runtime dir first, then `XDG_RUNTIME_DIR`), the | ||
| * same resolution the durable daemon scope uses. When no bus is reachable, the env stays as it | ||
| * is. An explicit address, not a deleted variable, because shells can carry an | ||
| * `XDG_RUNTIME_DIR` that points at a private dir with no bus in it. | ||
| */ | ||
| export function repairDisabledSessionBusEnv( | ||
| env: Record<string, string | undefined>, | ||
| platform: NodeJS.Platform = process.platform, | ||
| canonicalRuntimeDir: string | null = CANONICAL_USER_RUNTIME_DIR | ||
| ): void { | ||
| if (platform !== 'linux' || env.DBUS_SESSION_BUS_ADDRESS !== DISABLED_SESSION_BUS_ADDRESS) { | ||
| return | ||
| } | ||
| const runtimeDir = resolveUserRuntimeDir(env, canonicalRuntimeDir) | ||
| if (!runtimeDir) { | ||
| return | ||
| } | ||
| env.DBUS_SESSION_BUS_ADDRESS = `unix:path=${runtimeDir}/bus` | ||
| } |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.