Skip to content
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

Add the ability to run drgn against the live kernel as non-root user #347

Merged
merged 3 commits into from
Sep 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions _drgn.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -451,15 +451,15 @@ 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.

This loads the memory segments from the core dump and determines the
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:
Expand Down Expand Up @@ -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.
"""
...

Expand Down
6 changes: 5 additions & 1 deletion drgn/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down Expand Up @@ -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:
Expand Down
71 changes: 71 additions & 0 deletions drgn/internal/sudohelper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Copyright (c) Stephen Brennan <[email protected]>
# 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()
21 changes: 21 additions & 0 deletions libdrgn/drgn.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down Expand Up @@ -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.
*
Expand Down
70 changes: 44 additions & 26 deletions libdrgn/linux_kernel.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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);
}
}

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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) {
Expand Down
83 changes: 73 additions & 10 deletions libdrgn/program.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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)
{
Expand Down
Loading
Loading