From cd58049ed52a1dfe928c2edbd0ac1276356effa8 Mon Sep 17 00:00:00 2001 From: Stephen Brennan Date: Tue, 15 Aug 2023 23:40:45 -0700 Subject: [PATCH] cli: Open /proc/kcore via sudo when not root Non-root users can now run Drgn against the running kernel. Drgn will attempt to use sudo to open /proc/kcore and transmit the opened file descriptor back to the user process. The file descriptor is then passed to Program.set_core_dump(). The user must still have sudo privileges. Signed-off-by: Stephen Brennan --- drgn/cli.py | 6 +++- drgn/internal/sudohelper.py | 71 +++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 drgn/internal/sudohelper.py diff --git a/drgn/cli.py b/drgn/cli.py index 01b277bcd..05a0322f0 100644 --- a/drgn/cli.py +++ b/drgn/cli.py @@ -20,6 +20,7 @@ import drgn from drgn.internal.rlcompleter import Completer +from drgn.internal.sudohelper import open_via_sudo __all__ = ("run_interactive", "version_header") @@ -264,7 +265,10 @@ def _main() -> None: elif args.pid is not None: prog.set_pid(args.pid or os.getpid()) else: - prog.set_kernel() + try: + prog.set_kernel() + except PermissionError: + prog.set_core_dump(open_via_sudo("/proc/kcore", os.O_RDONLY)) except PermissionError as e: print(e, file=sys.stderr) if args.pid is not None: diff --git a/drgn/internal/sudohelper.py b/drgn/internal/sudohelper.py new file mode 100644 index 000000000..b0f6ec27b --- /dev/null +++ b/drgn/internal/sudohelper.py @@ -0,0 +1,71 @@ +# Copyright (c) Stephen Brennan +# SPDX-License-Identifier: LGPL-2.1-or-later + +"""Helper for opening a file as root and transmitting it via unix socket""" +import array +import os +from pathlib import Path +import pickle +import socket +import subprocess +import sys +import tempfile +from typing import Union + + +def open_via_sudo( + path: Union[Path, str], + flags: int, + mode: int = 0o777, +) -> int: + """Implements os.open() using sudo to get permissions""" + # Currently does not support dir_fd argument + with tempfile.TemporaryDirectory() as td: + sockpath = Path(td) / "sock" + with socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) as sock: + sock.bind(str(sockpath)) + subprocess.check_call( + [ + "sudo", + sys.executable, + "-B", + __file__, + sockpath, + path, + str(flags), + str(mode), + ], + ) + fds = array.array("i") + msg, ancdata, flags, addr = sock.recvmsg( + 4096, socket.CMSG_SPACE(fds.itemsize) + ) + for level, typ, data in ancdata: + if level == socket.SOL_SOCKET and typ == socket.SCM_RIGHTS: + data = data[: fds.itemsize] + fds.frombytes(data) + return fds[0] + raise pickle.loads(msg) + + +def main() -> None: + sockpath = sys.argv[1] + filename = sys.argv[2] + flags = int(sys.argv[3]) + mode = int(sys.argv[4]) + + sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) + sock.connect(sockpath) + try: + fd = os.open(filename, flags, mode) + fds = array.array("i", [fd]) + sock.sendmsg( + [b"success"], + [(socket.SOL_SOCKET, socket.SCM_RIGHTS, fds)], + ) + except Exception as e: + sock.sendmsg([pickle.dumps(e)]) + + +if __name__ == "__main__": + main()