{"record":{"id":"5f2bca15f6905a81","repo":"kovidgoyal/kitty","slug":"incorrect-owner-on-pwfile-uid-shm-stats-st-uid","errorCode":null,"errorMessage":"Incorrect owner on pwfile: uid={shm.stats.st_uid} gid={shm.stats.st_gid}","messagePattern":"Incorrect owner on pwfile: uid=(.+?) gid=(.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"kittens/ssh/utils.py","lineNumber":169,"sourceCode":"    db = json.dumps(data).encode('utf-8')\n    with SharedMemory(size=len(db) + SharedMemory.num_bytes_for_size, prefix=prefix) as shm:\n        shm.write_data_with_size(db)\n        shm.flush()\n        atexit.register(shm.close)  # keeps shm alive till exit\n        get_boss().atexit.shm_unlink(shm.name)\n    return shm.name\n\n\ndef read_data_from_shared_memory(shm_name: str) -> Any:\n    import json\n    import stat\n\n    from kitty.shm import SharedMemory\n\n    with SharedMemory(shm_name, readonly=True) as shm:\n        shm.unlink()\n        if shm.stats.st_uid != os.geteuid() or shm.stats.st_gid != os.getegid():\n            raise ValueError(f'Incorrect owner on pwfile: uid={shm.stats.st_uid} gid={shm.stats.st_gid}')\n        mode = stat.S_IMODE(shm.stats.st_mode)\n        if mode != stat.S_IREAD | stat.S_IWRITE:\n            raise ValueError(f'Incorrect permissions on pwfile: 0o{mode:03o}')\n        return json.loads(shm.read_data_with_size())\n\n\ndef get_ssh_data(msgb: memoryview, request_id: str) -> Iterator[bytes | memoryview]:\n    # Unfortunately we cannot use EOF (\\x04) to flush the kernel line buffer\n    # because ssh with controlmasters mangles EOF replacing it with null bytes\n    from base64 import standard_b64decode\n\n    yield b'\\nKITTY_DATA_START\\n'  # to discard leading data\n    try:\n        msg = standard_b64decode(msgb).decode('utf-8')\n        md = dict(x.split('=', 1) for x in msg.split(':'))\n        pw = md['pw']\n        pwfilename = md['pwfile']\n        rq_id = md['id']","sourceCodeStart":151,"sourceCodeEnd":187,"githubUrl":"https://github.com/kovidgoyal/kitty/blob/6d5d0c440603ad9bdf6dcd599f73f6dde21acb44/kittens/ssh/utils.py#L151-L187","documentation":"The ssh kitten passes passwords between the kitty process and the ssh wrapper over a POSIX shared-memory file, and read_data_from_shared_memory() validates that the shm file's uid/gid exactly match the current process's euid/egid before trusting it (and it unlinks it after opening). A mismatch raises ValueError('Incorrect owner on pwfile: ...'). This is a security check against another user planting or spoofing the pwfile.","triggerScenarios":"get_ssh_data() -> read_data_from_shared_memory(shm_name) when /dev/shm (or the tmpfs backing the shm) reports different st_uid/st_gid than os.geteuid()/os.getegid(). Typical causes: running under sudo/su where euid differs from the session that created the file, setuid/setgid wrappers, containers with ID-mapped or shared /dev/shm, or /dev/shm mounted from a host with different uid mapping.","commonSituations":"Running `kitten ssh` after sudo (root euid vs file owned by the user); container/Docker setups sharing /dev/shm between containers; NFS/overlay mounts for /dev/shm that rewrite ownership; multi-user systems where a stale or foreign shm file with the same name exists.","solutions":["Avoid mixing privilege levels: run kitten ssh as the same user that owns the kitty session (don't sudo).","Clear stale/foreign shm files: remove matching /dev/shm/kitty-* entries (or reboot/clean tmpfs) and retry.","In containers, ensure /dev/shm is a private tmpfs with correct uid mapping (`--shm-size` and no shared /dev/shm between mismatched users).","If reproducing in tests, create the SharedMemory file with the current process's uid/gid (the normal kitty flow does this automatically)."],"exampleFix":"# before\nsudo -u otheruser kitten ssh host  # euid != shm owner -> ValueError: Incorrect owner on pwfile\n\n# after\n# run as the same user that owns the kitty session\nkitten ssh host","handlingStrategy":"validation","validationCode":"import os, stat\n\ndef shm_owner_ok(path_or_stats) -> bool:\n    st = path_or_stats\n    return st.st_uid == os.geteuid() and st.st_gid == os.getegid()\n\n# before passing control to kitten ssh, ensure a clean /dev/shm\nimport glob\nfor p in glob.glob('/dev/shm/kitty*'):\n    try:\n        if not shm_owner_ok(os.stat(p)):\n            os.unlink(p)\n    except FileNotFoundError:\n        pass","typeGuard":null,"tryCatchPattern":"try:\n    data = read_data_from_shared_memory(name)\nexcept ValueError as e:\n    if 'Incorrect owner on pwfile' in str(e):\n        log.warning('stale/foreign shm file; clearing and retrying once')\n        # remove the offending file and let kitty recreate it\n    else:\n        raise","preventionTips":["Run kitty and kitten ssh under the same Unix user; never sudo one side.","Keep /dev/shm private per container/user in containerized setups.","Clean stray /dev/shm kitty files after crashes (add to session cleanup scripts)."],"tags":["kitty","ssh","shared-memory","permissions","security","uid-mismatch"],"backgroundTag":"ipc-file-ownership-mismatch","analyzedSha":"6d5d0c440603ad9bdf6dcd599f73f6dde21acb44","analyzedAt":"2026-08-27T14:20:20.142Z","schemaVersion":2},"datasetVersion":"2026-08-27T19:17:21.184Z"}