diff --git a/_drgn.pyi b/_drgn.pyi index 007501d5f..35af2772b 100644 --- a/_drgn.pyi +++ b/_drgn.pyi @@ -451,7 +451,7 @@ class Program: return an :class:`Object`. """ ... - def set_core_dump(self, path: Path) -> None: + def set_core_dump(self, path: Union[Path, int]) -> None: """ Set the program to a core dump. @@ -459,7 +459,7 @@ class Program: mapped executable and libraries. It does not load any debugging symbols; see :meth:`load_default_debug_info()`. - :param path: Core dump file path. + :param path: Core dump file path or open file descriptor. """ ... def set_kernel(self) -> None: @@ -888,12 +888,12 @@ def filename_matches(haystack: Optional[str], needle: Optional[str]) -> bool: """ ... -def program_from_core_dump(path: Path) -> Program: +def program_from_core_dump(path: Union[Path, int]) -> Program: """ Create a :class:`Program` from a core dump file. The type of program (e.g., userspace or kernel) is determined automatically. - :param path: Core dump file path. + :param path: Core dump file path or open file descriptor. """ ... 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() diff --git a/libdrgn/drgn.h.in b/libdrgn/drgn.h.in index 2d0863e5f..2a3f82154 100644 --- a/libdrgn/drgn.h.in +++ b/libdrgn/drgn.h.in @@ -670,6 +670,14 @@ drgn_program_add_object_finder(struct drgn_program *prog, struct drgn_error *drgn_program_set_core_dump(struct drgn_program *prog, const char *path); +/** + * Set a @ref drgn_program to a core dump from a file descriptor. + * + * @param[in] path Core dump file descriptor. + * @return @c NULL on success, non-@c NULL on error. + */ +struct drgn_error *drgn_program_set_core_dump_fd(struct drgn_program *prog, int fd); + /** * Set a @ref drgn_program to the running operating system kernel. * @@ -712,6 +720,19 @@ struct drgn_error *drgn_program_load_debug_info(struct drgn_program *prog, struct drgn_error *drgn_program_from_core_dump(const char *path, struct drgn_program **ret); +/** + * Create a @ref drgn_program from a core dump file descriptor. + * + * Same as @ref drgn_program_from_core_dump but with an already-opened file + * descriptor. + * + * @param[in] fd Core dump file path descriptor. + * @param[out] ret Returned program. + * @return @c NULL on success, non-@c NULL on error. + */ +struct drgn_error *drgn_program_from_core_dump_fd(int fd, + struct drgn_program **ret); + /** * Create a @ref drgn_program from the running operating system kernel. * diff --git a/libdrgn/linux_kernel.c b/libdrgn/linux_kernel.c index 1b3080e1b..7c53c271c 100644 --- a/libdrgn/linux_kernel.c +++ b/libdrgn/linux_kernel.c @@ -412,6 +412,7 @@ struct kernel_module_iterator { /* Address of `struct list_head modules`. */ uint64_t head; bool use_sys_module; + bool use_sys_module_sections; }; static void kernel_module_iterator_deinit(struct kernel_module_iterator *it) @@ -435,6 +436,7 @@ kernel_module_iterator_init(struct kernel_module_iterator *it, it->build_id_buf = NULL; it->build_id_buf_capacity = 0; it->use_sys_module = use_sys_module; + it->use_sys_module_sections = use_sys_module; err = drgn_program_find_type(prog, "struct module", NULL, &it->module_type); if (err) @@ -825,14 +827,37 @@ struct kernel_module_section_iterator { }; static struct drgn_error * -kernel_module_section_iterator_init(struct kernel_module_section_iterator *it, - struct kernel_module_iterator *kmod_it) +kernel_module_section_iterator_init_no_sys_module(struct kernel_module_section_iterator *it, + struct kernel_module_iterator *kmod_it) { struct drgn_error *err; + it->sections_dir = NULL; + it->i = 0; + it->name = NULL; + /* it->nsections = mod->sect_attrs->nsections */ + err = drgn_object_member(&kmod_it->tmp1, &kmod_it->mod, "sect_attrs"); + if (err) + return err; + err = drgn_object_member_dereference(&kmod_it->tmp2, &kmod_it->tmp1, + "nsections"); + if (err) + return err; + err = drgn_object_read_unsigned(&kmod_it->tmp2, &it->nsections); + if (err) + return err; + /* kmod_it->tmp1 = mod->sect_attrs->attrs */ + return drgn_object_member_dereference(&kmod_it->tmp1, &kmod_it->tmp1, + "attrs"); +} + +static struct drgn_error * +kernel_module_section_iterator_init(struct kernel_module_section_iterator *it, + struct kernel_module_iterator *kmod_it) +{ it->kmod_it = kmod_it; it->yielded_percpu = false; - if (kmod_it->use_sys_module) { + if (kmod_it->use_sys_module_sections) { char *path; if (asprintf(&path, "/sys/module/%s/sections", kmod_it->name) == -1) @@ -846,26 +871,7 @@ kernel_module_section_iterator_init(struct kernel_module_section_iterator *it, } return NULL; } else { - it->sections_dir = NULL; - it->i = 0; - it->name = NULL; - /* it->nsections = mod->sect_attrs->nsections */ - err = drgn_object_member(&kmod_it->tmp1, &kmod_it->mod, - "sect_attrs"); - if (err) - return err; - err = drgn_object_member_dereference(&kmod_it->tmp2, - &kmod_it->tmp1, - "nsections"); - if (err) - return err; - err = drgn_object_read_unsigned(&kmod_it->tmp2, - &it->nsections); - if (err) - return err; - /* kmod_it->tmp1 = mod->sect_attrs->attrs */ - return drgn_object_member_dereference(&kmod_it->tmp1, - &kmod_it->tmp1, "attrs"); + return kernel_module_section_iterator_init_no_sys_module(it, kmod_it); } } @@ -971,8 +977,18 @@ kernel_module_section_iterator_next(struct kernel_module_section_iterator *it, } if (it->sections_dir) { - return kernel_module_section_iterator_next_live(it, name_ret, - address_ret); + err = kernel_module_section_iterator_next_live(it, name_ret, + address_ret); + if (err && err->code == DRGN_ERROR_OS && err->errnum == EACCES) { + closedir(it->sections_dir); + drgn_error_destroy(err); + it->kmod_it->use_sys_module_sections = false; + err = kernel_module_section_iterator_init_no_sys_module(it, it->kmod_it); + if (err) + return err; + } else { + return err; + } } if (it->i >= it->nsections) @@ -1571,7 +1587,9 @@ report_kernel_modules(struct drgn_debug_info_load_state *load, * If we're debugging the running kernel, we can use * /sys/module/$module/notes and /sys/module/$module/sections instead of * getting the equivalent information from the core dump. This fast path - * can be disabled via an environment variable for testing. + * can be disabled via an environment variable for testing. It may also + * be disabled if we encounter permission issues using + * /sys/module/$module/sections. */ bool use_sys_module = false; if (prog->flags & DRGN_PROGRAM_IS_LIVE) { diff --git a/libdrgn/program.c b/libdrgn/program.c index 52430348b..b24ad38e5 100644 --- a/libdrgn/program.c +++ b/libdrgn/program.c @@ -226,8 +226,8 @@ static struct drgn_error *has_kdump_signature(const char *path, int fd, return NULL; } -LIBDRGN_PUBLIC struct drgn_error * -drgn_program_set_core_dump(struct drgn_program *prog, const char *path) +struct drgn_error * +drgn_program_set_core_dump_fd_internal(struct drgn_program *prog, int fd, const char *path) { struct drgn_error *err; GElf_Ehdr ehdr_mem, *ehdr; @@ -241,14 +241,7 @@ drgn_program_set_core_dump(struct drgn_program *prog, const char *path) size_t vmcoreinfo_size = 0; bool have_nt_taskstruct = false, is_proc_kcore; - err = drgn_program_check_initialized(prog); - if (err) - return err; - - prog->core_fd = open(path, O_RDONLY); - if (prog->core_fd == -1) - return drgn_error_create_os("open", errno, path); - + prog->core_fd = fd; err = has_kdump_signature(path, prog->core_fd, &is_kdump); if (err) goto out_fd; @@ -595,6 +588,39 @@ drgn_program_set_core_dump(struct drgn_program *prog, const char *path) return err; } +LIBDRGN_PUBLIC struct drgn_error * +drgn_program_set_core_dump_fd(struct drgn_program *prog, int fd) +{ + struct drgn_error *err; + + err = drgn_program_check_initialized(prog); + if (err) + return err; + + #define FORMAT "/proc/self/fd/%d" + char path[sizeof(FORMAT) - sizeof("%d") + max_decimal_length(int) + 1]; + snprintf(path, sizeof(path), FORMAT, fd); + #undef FORMAT + + return drgn_program_set_core_dump_fd_internal(prog, fd, path); +} + +LIBDRGN_PUBLIC struct drgn_error * +drgn_program_set_core_dump(struct drgn_program *prog, const char *path) +{ + struct drgn_error *err; + + err = drgn_program_check_initialized(prog); + if (err) + return err; + + int fd = open(path, O_RDONLY); + if (fd == -1) + return drgn_error_create_os("open", errno, path); + + return drgn_program_set_core_dump_fd_internal(prog, fd, path); +} + LIBDRGN_PUBLIC struct drgn_error * drgn_program_set_kernel(struct drgn_program *prog) { @@ -1497,6 +1523,21 @@ struct drgn_error *drgn_program_init_core_dump(struct drgn_program *prog, return err; } +struct drgn_error *drgn_program_init_core_dump_fd(struct drgn_program *prog, int fd) +{ + struct drgn_error *err; + + err = drgn_program_set_core_dump_fd(prog, fd); + if (err) + return err; + err = drgn_program_load_debug_info(prog, NULL, 0, true, true); + if (err && err->code == DRGN_ERROR_MISSING_DEBUG_INFO) { + drgn_error_destroy(err); + err = NULL; + } + return err; +} + struct drgn_error *drgn_program_init_kernel(struct drgn_program *prog) { struct drgn_error *err; @@ -1549,6 +1590,28 @@ drgn_program_from_core_dump(const char *path, struct drgn_program **ret) return NULL; } +LIBDRGN_PUBLIC struct drgn_error * +drgn_program_from_core_dump_fd(int fd, struct drgn_program **ret) +{ + struct drgn_error *err; + struct drgn_program *prog; + + prog = malloc(sizeof(*prog)); + if (!prog) + return &drgn_enomem; + + drgn_program_init(prog, NULL); + err = drgn_program_init_core_dump_fd(prog, fd); + if (err) { + drgn_program_deinit(prog); + free(prog); + return err; + } + + *ret = prog; + return NULL; +} + LIBDRGN_PUBLIC struct drgn_error * drgn_program_from_kernel(struct drgn_program **ret) { diff --git a/libdrgn/program.h b/libdrgn/program.h index 7b870dc5b..337ce578d 100644 --- a/libdrgn/program.h +++ b/libdrgn/program.h @@ -226,6 +226,13 @@ void drgn_program_set_platform(struct drgn_program *prog, struct drgn_error *drgn_program_init_core_dump(struct drgn_program *prog, const char *path); +/** + * Implement @ref drgn_program_from_core_dump_fd() on an initialized @ref + * drgn_program. + */ +struct drgn_error *drgn_program_init_core_dump_fd(struct drgn_program *prog, + int fd); + /** * Implement @ref drgn_program_from_kernel() on an initialized @ref * drgn_program. diff --git a/libdrgn/python/drgnpy.h b/libdrgn/python/drgnpy.h index 6f5fc6d57..9491e5b03 100644 --- a/libdrgn/python/drgnpy.h +++ b/libdrgn/python/drgnpy.h @@ -330,7 +330,9 @@ struct index_arg { int index_converter(PyObject *o, void *p); struct path_arg { + bool allow_fd; bool allow_none; + int fd; char *path; Py_ssize_t length; PyObject *object; diff --git a/libdrgn/python/program.c b/libdrgn/python/program.c index 693e93cd7..f15b44b63 100644 --- a/libdrgn/python/program.c +++ b/libdrgn/python/program.c @@ -515,13 +515,16 @@ static PyObject *Program_set_core_dump(Program *self, PyObject *args, { static char *keywords[] = {"path", NULL}; struct drgn_error *err; - struct path_arg path = {}; + struct path_arg path = { .allow_fd = true }; if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&:set_core_dump", keywords, path_converter, &path)) return NULL; - err = drgn_program_set_core_dump(&self->prog, path.path); + if (path.fd >= 0) + err = drgn_program_set_core_dump_fd(&self->prog, path.fd); + else + err = drgn_program_set_core_dump(&self->prog, path.path); path_cleanup(&path); if (err) return set_drgn_error(err); @@ -1247,7 +1250,7 @@ Program *program_from_core_dump(PyObject *self, PyObject *args, PyObject *kwds) { static char *keywords[] = {"path", NULL}; struct drgn_error *err; - struct path_arg path = {}; + struct path_arg path = { .allow_fd = true }; if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&:program_from_core_dump", keywords, path_converter, &path)) @@ -1260,7 +1263,10 @@ Program *program_from_core_dump(PyObject *self, PyObject *args, PyObject *kwds) return NULL; } - err = drgn_program_init_core_dump(&prog->prog, path.path); + if (path.fd >= 0) + err = drgn_program_init_core_dump_fd(&prog->prog, path.fd); + else + err = drgn_program_init_core_dump(&prog->prog, path.path); path_cleanup(&path); if (err) return set_drgn_error(err); diff --git a/libdrgn/python/util.c b/libdrgn/python/util.c index c01e86554..16200f456 100644 --- a/libdrgn/python/util.c +++ b/libdrgn/python/util.c @@ -86,7 +86,29 @@ int path_converter(PyObject *o, void *p) } struct path_arg *path = p; - if (path->allow_none && o == Py_None) { + path->fd = -1; + path->path = NULL; + path->length = 0; + path->bytes = NULL; + if (path->allow_fd && PyIndex_Check(o)) { + _cleanup_pydecref_ PyObject *fd_obj = PyNumber_Index(o); + if (!fd_obj) + return 0; + int overflow; + long fd = PyLong_AsLongAndOverflow(fd_obj, &overflow); + if (fd == -1 && PyErr_Occurred()) + return 0; + if (overflow > 0 || fd > INT_MAX) { + PyErr_SetString(PyExc_OverflowError, + "fd is greater than maximum"); + return 0; + } + if (fd < 0) { + PyErr_SetString(PyExc_ValueError, "fd is negative"); + return 0; + } + path->fd = fd; + } else if (path->allow_none && o == Py_None) { path->path = NULL; path->length = 0; path->bytes = NULL;