diff options
author | dsmith <dsmith> | 2007-08-14 15:23:59 +0000 |
---|---|---|
committer | dsmith <dsmith> | 2007-08-14 15:23:59 +0000 |
commit | 5eddf13b73a01f3b334e5be80fc3cc1b312d1fea (patch) | |
tree | 1c74859db1203887498df3975b851f91228ed8f4 | |
parent | 615d0e8b9a906c6d1db1e82866a2530194ebed36 (diff) | |
download | systemtap-steved-5eddf13b73a01f3b334e5be80fc3cc1b312d1fea.tar.gz systemtap-steved-5eddf13b73a01f3b334e5be80fc3cc1b312d1fea.tar.xz systemtap-steved-5eddf13b73a01f3b334e5be80fc3cc1b312d1fea.zip |
2007-08-14 David Smith <dsmith@redhat.com>
Merge from setuid-branch. Changes also by Martin Hunt
<hunt@redhat.com>.
* staprun.c (init_staprun): Drop CAP_SYS_ADMIN when we're done
with it.
(main): Calls parse_modpath instead of path_parse_modname. Just
call parse_modpath with argv[optind]. Let it allocate and set
modpath and modname. If no modulename was given, display usage
and exit. Drop CAP_SYS_NICE when we're done with it. Set
atexit(exit_cleanup) so cleanup always gets called and modules get
removed. Call handle_symbols.
(run_stapio): Set argv[0] to stapio so that it executes as itself
instead of staprun.
(cleanup): Only do cleanups once and only try to remove module
when appropriate.
(exit_cleanup): New. Calls cleanup().
(mountfs): Sets uid to root before making directory and then
restores uid.
(setup_ctl_channel): Uses DEBUGFS define and improved
error message.
(setup_relayfs): Ditto.
(setup_oldrelayfs): Uses DEBUGFS and RELAYFS defines.
(run_stp_check): Replaced by mountfs().
(mountfs): New function. Replaces an external script with C code.
(init_staprun): Calls mountfs() instead of run_stp_check().
* staprun.h: Renamed path_parse_modname to parse_modpath. Added
MODULE_NAME_LEN define. Added [_][p]err macros. Removed
VERSION_CMD.
* mainloop.c (cleanup_and_exit): Make sure initialized is 2
before exiting with code 2.
(stp_main_loop): Set initialized to 2 when STP_TRANSPORT
is received. Call cleanup_and_exit() with proper status.
(start_cmd): exit 1 instead of -1.
(system_cmd): Ditto.
(init_staprun): Renamed init_stapio.
(cleanup_and_exit): Set exit status.
* cap.c: New file.
* common.c: New file.
* stapio.c: New file.
* staprun_funcs.c: New file.
* Makefile: Removed.
* symbols.c (get_sections): Move the filter code up so that
uninteresting section names are filtered out before
attempting to open them.
(do_kernel_symbols): Better detect overfow conditions and realloc
new space.
(do_module): After sending all modules, send a null message to
indicate we are finished.
* ctl.c (init_ctl_channel): When attempting to attach, if the
control channel doesn't exist, print a better error message.
* relay_old.c (init_oldrelayfs): Errors out if
open_relayfs_files() couldn't open any files.
PR 4795
* mainloop.c (send_request): Fixed buffer overflow check.
* staprun.h: Added buffer overflow checking versions of
strcpy/sprintf/snprintf.
* common.c (path_parse_modname): Checks for overflows on
strcpy/sprintf/snprintf.
(read_buffer_info): Ditto.
* ctl.c (init_ctl_channel): Ditto.
* relay.c (init_relayfs): Ditto.
* relay_old.c (open_relayfs_files): Ditto.
(init_oldrelayfs): Ditto.
* staprun_funcs.c (insert_module): Ditto.
(check_path): Ditto.
* symbols.c (get_sections): Ditto.
-rw-r--r-- | runtime/staprun/ChangeLog | 76 | ||||
-rw-r--r-- | runtime/staprun/cap.c | 158 | ||||
-rw-r--r-- | runtime/staprun/common.c | 307 | ||||
-rw-r--r-- | runtime/staprun/ctl.c | 56 | ||||
-rw-r--r-- | runtime/staprun/mainloop.c | 302 | ||||
-rw-r--r-- | runtime/staprun/relay.c | 58 | ||||
-rw-r--r-- | runtime/staprun/relay_old.c | 89 | ||||
-rw-r--r-- | runtime/staprun/stapio.c | 69 | ||||
-rw-r--r-- | runtime/staprun/staprun.c | 274 | ||||
-rw-r--r-- | runtime/staprun/staprun.h | 109 | ||||
-rw-r--r-- | runtime/staprun/staprun_funcs.c | 443 | ||||
-rw-r--r-- | runtime/staprun/symbols.c | 138 |
12 files changed, 1596 insertions, 483 deletions
diff --git a/runtime/staprun/ChangeLog b/runtime/staprun/ChangeLog index aefd87bf..de522180 100644 --- a/runtime/staprun/ChangeLog +++ b/runtime/staprun/ChangeLog @@ -1,3 +1,79 @@ +2007-08-14 David Smith <dsmith@redhat.com> + + Merge from setuid-branch. Changes also by Martin Hunt + <hunt@redhat.com>. + + * staprun.c (init_staprun): Drop CAP_SYS_ADMIN when we're done + with it. + (main): Calls parse_modpath instead of path_parse_modname. Just + call parse_modpath with argv[optind]. Let it allocate and set + modpath and modname. If no modulename was given, display usage + and exit. Drop CAP_SYS_NICE when we're done with it. Set + atexit(exit_cleanup) so cleanup always gets called and modules get + removed. Call handle_symbols. + (run_stapio): Set argv[0] to stapio so that it executes as itself + instead of staprun. + (cleanup): Only do cleanups once and only try to remove module + when appropriate. + (exit_cleanup): New. Calls cleanup(). + (mountfs): Sets uid to root before making directory and then + restores uid. + (setup_ctl_channel): Uses DEBUGFS define and improved + error message. + (setup_relayfs): Ditto. + (setup_oldrelayfs): Uses DEBUGFS and RELAYFS defines. + (run_stp_check): Replaced by mountfs(). + (mountfs): New function. Replaces an external script with C code. + (init_staprun): Calls mountfs() instead of run_stp_check(). + + * staprun.h: Renamed path_parse_modname to parse_modpath. Added + MODULE_NAME_LEN define. Added [_][p]err macros. Removed + VERSION_CMD. + + * mainloop.c (cleanup_and_exit): Make sure initialized is 2 + before exiting with code 2. + (stp_main_loop): Set initialized to 2 when STP_TRANSPORT + is received. Call cleanup_and_exit() with proper status. + (start_cmd): exit 1 instead of -1. + (system_cmd): Ditto. + (init_staprun): Renamed init_stapio. + (cleanup_and_exit): Set exit status. + + * cap.c: New file. + * common.c: New file. + * stapio.c: New file. + * staprun_funcs.c: New file. + * Makefile: Removed. + + * symbols.c (get_sections): Move the filter code up so that + uninteresting section names are filtered out before + attempting to open them. + (do_kernel_symbols): Better detect overfow conditions and realloc + new space. + (do_module): After sending all modules, send a null message to + indicate we are finished. + + * ctl.c (init_ctl_channel): When attempting to attach, if the + control channel doesn't exist, print a better error message. + + * relay_old.c (init_oldrelayfs): Errors out if + open_relayfs_files() couldn't open any files. + + PR 4795 + * mainloop.c (send_request): Fixed buffer overflow check. + * staprun.h: Added buffer overflow checking versions of + strcpy/sprintf/snprintf. + * common.c (path_parse_modname): Checks for overflows on + strcpy/sprintf/snprintf. + (read_buffer_info): Ditto. + * ctl.c (init_ctl_channel): Ditto. + * relay.c (init_relayfs): Ditto. + * relay_old.c (open_relayfs_files): Ditto. + (init_oldrelayfs): Ditto. + * staprun_funcs.c (insert_module): Ditto. + (check_path): Ditto. + * symbols.c (get_sections): Ditto. + 2007-07-09 David Smith <dsmith@redhat.com> * relay.c (init_relayfs): Fixed a buffer size bug introduced by diff --git a/runtime/staprun/cap.c b/runtime/staprun/cap.c new file mode 100644 index 00000000..df4a7130 --- /dev/null +++ b/runtime/staprun/cap.c @@ -0,0 +1,158 @@ +/* -*- linux-c -*- + * + * cap.c - staprun capabilities functions + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Copyright (C) 2007 Red Hat, Inc. + * + */ + +#include "staprun.h" +#include <sys/prctl.h> + +/* like perror, but exits */ +#define ferror(msg) { \ + _perr(msg); \ + exit(1); \ + } \ + +/* + * init_cap() sets up the initial capabilities for staprun. Then + * it calls prctl( PR_SET_KEEPCAPS) to arrrange to keep these capabilities + * even when not running as root. Next it resets the real, effective, and + * saved uid and gid back to the normal user. + * + * There are two sets of capabilities we are concerned with; permitted + * and effective. The permitted capabilities are all the capabilities + * that this process is ever permitted to have. They are defined in init_cap() + * and may be permanently removed with drop_cap(). + * + * Effective capabilities are the capabilities from the permitted set + * that are currently enabled. A good practice would be to only enable + * capabilities when necessary and to delete or drop them as soon as possible. + * + * Capabilities we might use include: + * + * CAP_SYS_MODULE - insert and remove kernel modules + * CAP_SYS_ADMIN - misc, including mounting and unmounting + * CAP_SYS_NICE - setpriority() + * CAP_SETUID - allows setuid + * CAP_SETGID - allows setgid + * CAP_CHOWN - allows chown + */ + +int init_cap(void) +{ + cap_t caps = cap_init(); + cap_value_t capv[] = {CAP_SYS_MODULE, CAP_SYS_ADMIN, CAP_SYS_NICE, CAP_SETUID, CAP_SETGID}; + const int numcaps = sizeof(capv) / sizeof(capv[0]); + uid_t uid = getuid(); + gid_t gid = getgid(); + + cap_clear(caps); + if (caps == NULL) + ferror("cap_init"); + + if (cap_set_flag(caps, CAP_PERMITTED, numcaps, capv, CAP_SET) < 0) + ferror("cap_set_flag"); + + if (cap_set_proc(caps) < 0) + ferror("cap_set_proc"); + + cap_free(caps); + + if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) < 0) + ferror("prctl"); + + if (setresuid(uid, uid, uid) < 0) + ferror("setresuid"); + + if (setresgid(gid, gid, gid) < 0) + ferror("setresgid"); + + return 1; +} + +void print_cap(char *text) +{ + int p; + cap_t caps = cap_get_proc(); + uid_t uid, euid, suid; + gid_t gid, egid, sgid; + + if (caps == NULL) { + perr("cap_get_proc"); + return; + } + + getresuid(&uid, &euid, &suid); + getresgid(&gid, &egid, &sgid); + + printf("***** %s\n", text); + + if ((p=prctl(PR_GET_KEEPCAPS, 0, 0, 0, 0)) < 0) + perr("Couldn't get PR_SET_KEEPCAPS flag value"); + else + printf("KEEPCAPS: %d\n", p); + + printf("uid: %d, euid: %d, suid: %d\ngid: %d. egid: %d, sgid: %d\n", + uid, euid, suid, gid, egid, sgid ); + printf("Caps: %s\n", cap_to_text(caps, NULL)); + cap_free(caps); + printf("*****\n\n"); +} + +/* drop_cap() permanently removes a capability from the permitted set. There is + * no way to recover the capability after this. You do not need to remove + * it from the effective set before calling this. + */ +void drop_cap(cap_value_t cap) +{ + cap_t caps = cap_get_proc(); + if (caps == NULL) + ferror("cap_get_proc failed"); + if (cap_set_flag(caps, CAP_PERMITTED, 1, &cap, CAP_CLEAR) < 0) + ferror("Could not clear effective capabilities"); + if (cap_set_proc(caps) < 0) + ferror("Could not apply capability set"); + cap_free(caps); +} + +/* add_cap() adds a permitted capability to the effective set. */ +void add_cap(cap_value_t cap) +{ + cap_t caps = cap_get_proc(); + if (caps == NULL) + ferror("cap_get_proc failed"); + if (cap_set_flag(caps, CAP_EFFECTIVE, 1, &cap, CAP_SET) < 0) + ferror("Could not set effective capabilities"); + if (cap_set_proc(caps) < 0) + ferror("Could not apply capability set"); + cap_free(caps); +} + +/* del_cap() deletes a permitted capability from the effective set. */ +void del_cap(cap_value_t cap) +{ + cap_t caps = cap_get_proc(); + if (caps == NULL) + ferror("cap_get_proc failed"); + if (cap_set_flag(caps, CAP_EFFECTIVE, 1, &cap, CAP_CLEAR) < 0) + ferror("Could not clear effective capabilities"); + if (cap_set_proc(caps) < 0) + ferror("Could not apply capability set"); + cap_free(caps); +} diff --git a/runtime/staprun/common.c b/runtime/staprun/common.c new file mode 100644 index 00000000..cbe88be0 --- /dev/null +++ b/runtime/staprun/common.c @@ -0,0 +1,307 @@ +/* -*- linux-c -*- + * + * common.c - staprun suid/user common code + * + * This file is part of systemtap, and is free software. You can + * redistribute it and/or modify it under the terms of the GNU General + * Public License (GPL); either version 2, or (at your option) any + * later version. + * + * Copyright (C) 2007 Red Hat Inc. + */ + +#include "staprun.h" +#include <sys/types.h> +#include <unistd.h> +#include <sys/utsname.h> + +/* variables needed by parse_args() */ +int verbose; +int target_pid; +unsigned int buffer_size; +char *target_cmd; +char *outfile_name; +int attach_mod; +int load_only; + +/* module variables */ +char *modname = NULL; +char *modpath = ""; +char *modoptions[MAXMODOPTIONS]; + +int initialized = 0; +int control_channel = 0; + +void parse_args(int argc, char **argv) +{ + int c; + + /* Initialize option variables. */ + verbose = 0; + target_pid = 0; + buffer_size = 0; + target_cmd = NULL; + outfile_name = NULL; + attach_mod = 0; + load_only = 0; + + while ((c = getopt(argc, argv, "ALvb:t:d:c:o:x:")) != EOF) { + switch (c) { + case 'v': + verbose++; + break; + case 'b': + buffer_size = (unsigned)atoi(optarg); + if (buffer_size < 1 || buffer_size > 64) { + err("Invalid buffer size '%d' (should be 1-64).\n", buffer_size); + usage(argv[0]); + } + break; + case 't': + case 'x': + target_pid = atoi(optarg); + break; + case 'd': + /* obsolete internal option used by stap */ + break; + case 'c': + target_cmd = optarg; + break; + case 'o': + outfile_name = optarg; + break; + case 'A': + attach_mod = 1; + break; + case 'L': + load_only = 1; + break; + default: + usage(argv[0]); + } + } + + if (attach_mod && load_only) { + err("You can't specify the '-A' and '-L' options together.\n"); + usage(argv[0]); + } + + if (attach_mod && buffer_size) { + err("You can't specify the '-A' and '-b' options together. The '-b'\n" + "buffer size option only has an effect when the module is inserted.\n"); + usage(argv[0]); + } + + if (attach_mod && target_cmd) { + err("You can't specify the '-A' and '-c' options together. The '-c cmd'\n" + "option used to start a command only has an effect when the module\n" + "is inserted.\n"); + usage(argv[0]); + } + + if (attach_mod && target_pid) { + err("You can't specify the '-A' and '-x' options together. The '-x pid'\n" + "option only has an effect when the module is inserted.\n"); + usage(argv[0]); + } +} + +void usage(char *prog) +{ + err("\n%s [-v] [-c cmd ] [-x pid] [-u user]\n" + "\t[-A|-L] [-b bufsize] [-o FILE] MODULE [module-options]\n", prog); + err("-v Increase verbosity.\n"); + err("-c cmd Command \'cmd\' will be run and staprun will\n"); + err(" exit when it does. The '_stp_target' variable\n"); + err(" will contain the pid for the command.\n"); + err("-x pid Sets the '_stp_target' variable to pid.\n"); + err("-o FILE Send output to FILE.\n"); + err("-b buffer size The systemtap module specifies a buffer size.\n"); + err(" Setting one here will override that value. The\n"); + err(" value should be an integer between 1 and 64\n"); + err(" which be assumed to be the buffer size in MB.\n"); + err(" That value will be per-cpu in bulk mode.\n"); + err("-L Load module and start probes, then detach.\n"); + err("-A Attach to loaded systemtap module.\n"); + err("MODULE can be either a module name or a module path. If a\n"); + err("module name is used, it is looked for in the following\n"); + err("directory: /lib/modules/`uname -r`/systemtap\n"); + exit(1); +} + +/* + * parse_modpath. Here's how this code interprets the global modpath: + * + * (1) If modpath contains a '/', it is assumed to be an absolute or + * relative file path to a module (such as "../foo.ko" or + * "/tmp/stapXYZ/stap_foo.ko"). + * + * (2) If modpath doesn't contain a '/' and ends in '.ko', it is a file + * path to a module in the current directory (such as "foo.ko"). + * + * (3) If modpath doesn't contain a '/' and doesn't end in '.ko', then + * it is a module name and the full pathname of the module is + * '/lib/modules/`uname -r`/systemtap/PATH.ko'. For instance, if + * modpath was "foo", the full module pathname would be + * '/lib/modules/`uname -r`/systemtap/foo.ko'. + */ +void parse_modpath(const char *inpath) +{ + const char *mptr = rindex(inpath, '/'); + char *ptr; + + dbug(3, "inpath=%s\n", inpath); + + /* If we couldn't find a '/', ... */ + if (mptr == NULL) { + size_t plen = strlen(inpath); + + /* If the path ends with the '.ko' file extension, + * then we've got a module in the current + * directory. */ + if (plen > 3 && strcmp(&inpath[plen - 3], ".ko") == 0) { + mptr = inpath; + modpath = strdup(inpath); + if (!modpath) { + err("Memory allocation failed. Exiting.\n"); + exit(1); + } + } else { + /* If we didn't find the '.ko' file extension, then + * we've just got a module name, not a module path. + * Look for the module in /lib/modules/`uname + * -r`/systemtap. */ + + struct utsname utsbuf; + int len; + #define MODULE_PATH "/lib/modules/%s/systemtap/%s.ko" + + /* First, we need to figure out what the + * kernel version. */ + if (uname(&utsbuf) != 0) { + perr("Unable to determine kernel version, uname failed"); + exit(-1); + } + + /* Build the module path, which will look like + * '/lib/modules/KVER/systemtap/{path}.ko'. */ + len = sizeof(MODULE_PATH) + sizeof(utsbuf.release) + strlen(inpath); + modpath = malloc(len); + if (!modpath) { + err("Memory allocation failed. Exiting.\n"); + exit(1); + } + + if (snprintf_chk(modpath, len, MODULE_PATH, utsbuf.release, inpath)) + exit(-1); + + dbug(2, "modpath=\"%s\"\n", modpath); + + mptr = rindex(modpath, '/'); + mptr++; + } + } else { + /* We found a '/', so the module name starts with the next + * character. */ + mptr++; + + modpath = strdup(inpath); + if (!modpath) { + err("Memory allocation failed. Exiting.\n"); + exit(1); + } + } + + modname = strdup(mptr); + if (!modname) { + err("Memory allocation failed. Exiting.\n"); + exit(1); + } + + ptr = rindex(modname, '.'); + if (ptr) + *ptr = '\0'; + + /* We've finally got a real modname. Make sure it isn't too + * long. If it is too long, init_module() will appear to + * work, but the module can't be removed (because you end up + * with control characters in the module name). */ + if (strlen(modname) > MODULE_NAME_LEN) { + err("ERROR: Module name ('%s') is too long.\n", modname); + exit(1); + } +} + +#define ERR_MSG "\nUNEXPECTED FATAL ERROR in staprun. Please file a bug report.\n" +static void fatal_handler (int signum) +{ + int rc; + char *str = strsignal(signum); + rc = write (STDERR_FILENO, ERR_MSG, sizeof(ERR_MSG)); + rc = write (STDERR_FILENO, str, strlen(str)); + rc = write (STDERR_FILENO, "\n", 1); + if (initialized) + _exit(3); + else + _exit(1); +} + +void setup_signals(void) +{ + sigset_t s; + struct sigaction a; + + /* blocking all signals while we set things up */ + sigfillset(&s); +#ifdef SINGLE_THREADED + sigprocmask(SIG_SETMASK, &s, NULL); +#else + pthread_sigmask(SIG_SETMASK, &s, NULL); +#endif + /* set some of them to be ignored */ + memset(&a, 0, sizeof(a)); + sigfillset(&a.sa_mask); + a.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &a, NULL); + sigaction(SIGUSR2, &a, NULL); + + /* for serious errors, handle them in fatal_handler */ + a.sa_handler = fatal_handler; + sigaction(SIGBUS, &a, NULL); + sigaction(SIGFPE, &a, NULL); + sigaction(SIGILL, &a, NULL); + sigaction(SIGSEGV, &a, NULL); + sigaction(SIGXCPU, &a, NULL); + sigaction(SIGXFSZ, &a, NULL); + + /* unblock all signals */ + sigemptyset(&s); + +#ifdef SINGLE_THREADED + sigprocmask(SIG_SETMASK, &s, NULL); +#else + pthread_sigmask(SIG_SETMASK, &s, NULL); +#endif +} + +/** + * send_request - send request to kernel over control channel + * @type: the relay-app command id + * @data: pointer to the data to be sent + * @len: length of the data to be sent + * + * Returns 0 on success, negative otherwise. + */ +int send_request(int type, void *data, int len) +{ + char buf[1024]; + + /* Before doing memcpy, make sure 'buf' is big enough. */ + if ((len + 4) > (int)sizeof(buf)) { + _err("exceeded maximum send_request size.\n"); + return -1; + } + memcpy(buf, &type, 4); + memcpy(&buf[4], data, len); + return write(control_channel, buf, len+4); +} diff --git a/runtime/staprun/ctl.c b/runtime/staprun/ctl.c index 53c27190..72592bdf 100644 --- a/runtime/staprun/ctl.c +++ b/runtime/staprun/ctl.c @@ -12,62 +12,28 @@ #include "staprun.h" -/* This is only used in the old relayfs code */ -static void read_buffer_info(void) -{ - char buf[PATH_MAX]; - struct statfs st; - int fd, len, ret; - - if (!use_old_transport) - return; - - if (statfs("/sys/kernel/debug", &st) == 0 && (int) st.f_type == (int) DEBUGFS_MAGIC) - return; - - sprintf (buf, "/proc/systemtap/%s/bufsize", modname); - fd = open(buf, O_RDONLY); - if (fd < 0) - return; - - len = read(fd, buf, sizeof(buf)); - if (len <= 0) { - fprintf (stderr, "ERROR: couldn't read bufsize.\n"); - close(fd); - return; - } - ret = sscanf(buf, "%u,%u", &n_subbufs, &subbuf_size); - if (ret != 2) - fprintf (stderr, "ERROR: couldn't read bufsize.\n"); - - dbug(2, "n_subbufs= %u, size=%u\n", n_subbufs, subbuf_size); - close(fd); - return; -} - - int init_ctl_channel(void) { char buf[PATH_MAX]; struct statfs st; - if (statfs("/sys/kernel/debug", &st) == 0 && (int) st.f_type == (int) DEBUGFS_MAGIC) - sprintf (buf, "/sys/kernel/debug/systemtap/%s/cmd", modname); - else - sprintf (buf, "/proc/systemtap/%s/cmd", modname); - + if (statfs("/sys/kernel/debug", &st) == 0 && (int) st.f_type == (int) DEBUGFS_MAGIC) { + if (sprintf_chk(buf, "/sys/kernel/debug/systemtap/%s/cmd", modname)) + return -1; + } else { + if (sprintf_chk(buf, "/proc/systemtap/%s/cmd", modname)) + return -1; + } + dbug(2, "Opening %s\n", buf); control_channel = open(buf, O_RDWR); if (control_channel < 0) { - if (attach_mod) - fprintf (stderr, "ERROR: Cannot connect to module \"%s\".\n", modname); + if (attach_mod && errno == ENOENT) + err("ERROR: Can not attach. Module %s not running.\n", modname); else - fprintf (stderr, "ERROR: couldn't open control channel %s\n", buf); - fprintf (stderr, "errcode = %s\n", strerror(errno)); + perr("Couldn't open control channel '%s'", buf); return -1; } - - read_buffer_info(); return 0; } diff --git a/runtime/staprun/mainloop.c b/runtime/staprun/mainloop.c index 7b8c5e31..4acfb001 100644 --- a/runtime/staprun/mainloop.c +++ b/runtime/staprun/mainloop.c @@ -11,23 +11,12 @@ */ #include "staprun.h" +#include <sys/utsname.h> /* globals */ -int control_channel = 0; int ncpus; int use_old_transport = 0; -#define ERR_MSG "\nUNEXPECTED FATAL ERROR in staprun. Please file a bug report.\n" -void fatal_handler (int signum) -{ - int rc; - char *str = strsignal(signum); - rc = write (STDERR_FILENO, ERR_MSG, sizeof(ERR_MSG)); - rc = write (STDERR_FILENO, str, strlen(str)); - rc = write (STDERR_FILENO, "\n", 1); - _exit(-1); -} - static void sigproc(int signum) { dbug(2, "sigproc %d (%s)\n", signum, strsignal(signum)); @@ -60,56 +49,6 @@ static void setup_main_signals(int cleanup) sigaction(SIGQUIT, &a, NULL); } -void setup_signals(void) -{ - sigset_t s; - struct sigaction a; - - /* blocking all signals while we set things up */ - sigfillset(&s); - pthread_sigmask(SIG_SETMASK, &s, NULL); - - /* set some of them to be ignored */ - memset(&a, 0, sizeof(a)); - sigfillset(&a.sa_mask); - a.sa_handler = SIG_IGN; - sigaction(SIGPIPE, &a, NULL); - sigaction(SIGUSR2, &a, NULL); - - /* for serious errors, handle them in fatal_handler */ - a.sa_handler = fatal_handler; - sigaction(SIGBUS, &a, NULL); - sigaction(SIGFPE, &a, NULL); - sigaction(SIGILL, &a, NULL); - sigaction(SIGSEGV, &a, NULL); - sigaction(SIGXCPU, &a, NULL); - sigaction(SIGXFSZ, &a, NULL); - - /* unblock all signals */ - sigemptyset(&s); - pthread_sigmask(SIG_SETMASK, &s, NULL); -} - - -/** - * send_request - send request to kernel over control channel - * @type: the relay-app command id - * @data: pointer to the data to be sent - * @len: length of the data to be sent - * - * Returns 0 on success, negative otherwise. - */ -int send_request(int type, void *data, int len) -{ - char buf[1024]; - if (len > (int)sizeof(buf)) { - err("exceeded maximum send_request size.\n"); - return -1; - } - memcpy(buf, &type, 4); - memcpy(&buf[4],data,len); - return write(control_channel, buf, len+4); -} /* * start_cmd forks the command given on the command line @@ -131,23 +70,17 @@ void start_cmd(void) dbug (1, "execing target_cmd %s\n", target_cmd); if ((pid = fork()) < 0) { - perror ("fork"); - exit(-1); + _perr("fork"); + exit(1); } else if (pid == 0) { int signum; - if (setregid(cmd_gid, cmd_gid) < 0) { - perror("setregid"); - } - if (setreuid(cmd_uid, cmd_uid) < 0) { - perror("setreuid"); - } /* wait here until signaled */ sigwait(&usrset, &signum); if (execl("/bin/sh", "sh", "-c", target_cmd, NULL) < 0) perror(target_cmd); - _exit(-1); + _exit(1); } target_pid = pid; } @@ -156,8 +89,6 @@ void start_cmd(void) * system_cmd() executes system commands in response * to an STP_SYSTEM message from the module. These * messages are sent by the system() systemtap function. - * uid and gid are set because staprun is running as root and - * it is best to run commands as the real user. */ void system_cmd(char *cmd) { @@ -165,57 +96,104 @@ void system_cmd(char *cmd) dbug (2, "system %s\n", cmd); if ((pid = fork()) < 0) { - perror ("fork"); + _perr("fork"); } else if (pid == 0) { - if (setregid(cmd_gid, cmd_gid) < 0) { - perror("setregid"); - } - if (setreuid(cmd_uid, cmd_uid) < 0) { - perror("setreuid"); - } if (execl("/bin/sh", "sh", "-c", cmd, NULL) < 0) - perror(cmd); - _exit(-1); + perr("%s", cmd); + _exit(1); } } +static int using_old_transport(void) +{ + struct utsname utsbuf; + int i; + long int kver[3]; + char *start, *end; -/* stp_check script */ -#ifdef PKGLIBDIR -char *stp_check=PKGLIBDIR "/stp_check"; -#else -char *stp_check="stp_check"; -#endif + if (uname(&utsbuf) != 0) { + _perr("Unable to determine kernel version, uname failed"); + return -1; + } + + start = utsbuf.release; + for (i = 0; i < 3; i++) { + errno = 0; + kver[i] = strtol(start, &end, 10); + if (errno != 0) { + _perr("Unable to parse kernel version, strtol failed"); + return -1; + } + start = end; + start++; + } -static int run_stp_check (void) + if (KERNEL_VERSION(kver[0], kver[1], kver[2]) + <= KERNEL_VERSION(2, 6, 15)) { + dbug(2, "Using OLD TRANSPORT\n"); + return 1; + } + return 0; +} + +/* This is only used in the old relayfs code */ +static void read_buffer_info(void) { - int ret ; - /* run the _stp_check script */ - dbug(2, "executing %s\n", stp_check); - ret = system(stp_check); - return ret; + char buf[PATH_MAX]; + struct statfs st; + int fd, len, ret; + + if (!use_old_transport) + return; + + if (statfs("/sys/kernel/debug", &st) == 0 && (int) st.f_type == (int) DEBUGFS_MAGIC) + return; + + if (sprintf_chk(buf, "/proc/systemtap/%s/bufsize", modname)) + return; + fd = open(buf, O_RDONLY); + if (fd < 0) + return; + + len = read(fd, buf, sizeof(buf)); + if (len <= 0) { + perr("Couldn't read bufsize"); + close(fd); + return; + } + ret = sscanf(buf, "%u,%u", &n_subbufs, &subbuf_size); + if (ret != 2) + perr("Couldn't read bufsize"); + + dbug(2, "n_subbufs= %u, size=%u\n", n_subbufs, subbuf_size); + close(fd); + return; } + /** - * init_stp - initialize the app + * init_stapio - initialize the app * @print_summary: boolean, print summary or not at end of run * * Returns 0 on success, negative otherwise. */ -int init_staprun(void) +int init_stapio(void) { - char bufcmd[128]; - int rstatus; - int pid; + dbug(2, "init_stapio\n"); - if (system(VERSION_CMD)) { - dbug(2, "Using OLD TRANSPORT\n"); - use_old_transport = 1; + use_old_transport = using_old_transport(); + if (use_old_transport < 0) + return -1; + + /* create control channel */ + if (init_ctl_channel() < 0) { + err("Failed to initialize control channel.\n"); + return -1; } + read_buffer_info(); if (attach_mod) { - if (init_ctl_channel() < 0) - return -1; + dbug(2, "Attaching\n"); if (use_old_transport) { if (init_oldrelayfs() < 0) { close_ctl_channel(); @@ -230,57 +208,22 @@ int init_staprun(void) return 0; } - if (run_stp_check() < 0) - return -1; - - /* insert module */ - sprintf(bufcmd, "_stp_bufsize=%d", buffer_size); - modoptions[0] = "insmod"; - modoptions[1] = modpath; - modoptions[2] = bufcmd; - /* modoptions[3...N] set by command line parser. */ - - if ((pid = fork()) < 0) { - perror ("fork"); - exit(-1); - } else if (pid == 0) { - if (execvp("/sbin/insmod", modoptions) < 0) - _exit(-1); - } - if (waitpid(pid, &rstatus, 0) < 0) { - perror("waitpid"); - exit(-1); - } - if (WIFEXITED(rstatus) && WEXITSTATUS(rstatus)) { - fprintf(stderr, "ERROR, couldn't insmod probe module %s\n", modpath); - return -1; - } - - /* create control channel */ - if (init_ctl_channel() < 0) { - err("Failed to initialize control channel.\n"); - goto exit1; - } - /* fork target_cmd if requested. */ /* It will not actually exec until signalled. */ if (target_cmd) start_cmd(); return 0; - -exit1: - snprintf(bufcmd, sizeof(bufcmd), "/sbin/rmmod -w %s", modname); - if (system(bufcmd)) - fprintf(stderr, "ERROR: couldn't rmmod probe module %s.\n", modname); - return -1; } - - +/* cleanup_and_exit() closed channels and frees memory + * then exits with the following status codes: + * 1 - failed to initialize. + * 2 - disconnected + * 3 - initialized + */ void cleanup_and_exit (int closed) { - char tmpbuf[128]; pid_t err; static int exiting = 0; @@ -295,7 +238,7 @@ void cleanup_and_exit (int closed) /* what about child processes? we will wait for them here. */ err = waitpid(-1, NULL, WNOHANG); if (err >= 0) - fprintf(stderr,"\nWaiting for processes to exit\n"); + err("\nWaiting for processes to exit\n"); while(wait(NULL) > 0) ; if (use_old_transport) @@ -306,22 +249,16 @@ void cleanup_and_exit (int closed) dbug(1, "closing control channel\n"); close_ctl_channel(); - if (closed == 0) { - dbug(1, "removing module\n"); - snprintf(tmpbuf, sizeof(tmpbuf), "/sbin/rmmod -w %s", modname); - if (system(tmpbuf)) { - fprintf(stderr, "ERROR: couldn't rmmod probe module %s.\n", modname); - exit(1); - } - } else if (closed == 2) { - fprintf(stderr, "\nDisconnecting from systemtap module.\n"); - fprintf(stderr, "To reconnect, type \"staprun -A %s\"\n", modname); - } - - exit(0); + if (initialized == 2 && closed == 2) { + err("\nDisconnecting from systemtap module.\n" \ + "To reconnect, type \"staprun -A %s\"\n", modname); + } else if (initialized) + closed = 3; + else + closed = 1; + exit(closed); } - /** * stp_main_loop - loop forever reading data */ @@ -342,13 +279,11 @@ int stp_main_loop(void) while (1) { /* handle messages from control channel */ nb = read(control_channel, recvbuf, sizeof(recvbuf)); if (nb <= 0) { - if (errno != EINTR) { - perror("recv"); - fprintf(stderr, "WARNING: unexpected EOF. nb=%ld\n", (long)nb); - } + if (errno != EINTR) + _perr("Unexpected EOF in read (nb=%ld)", (long)nb); continue; } - + type = *(int *)recvbuf; data = (void *)(recvbuf + sizeof(int)); @@ -362,10 +297,8 @@ int stp_main_loop(void) bw = write(out_fd[0], data, nb - sizeof(int)); } if (bw != (nb - (ssize_t)sizeof(int))) { - perror("write"); - fprintf(stderr, - "ERROR: write error. nb=%ld\n", (long)nb); - cleanup_and_exit(0); + _perr("write error (nb=%ld)", (long)nb); + cleanup_and_exit(1); } break; } @@ -388,7 +321,7 @@ int stp_main_loop(void) if (t->res < 0) { if (target_cmd) kill (target_pid, SIGKILL); - cleanup_and_exit(0); + cleanup_and_exit(1); } else if (target_cmd) kill (target_pid, SIGUSR1); break; @@ -405,41 +338,20 @@ int stp_main_loop(void) struct _stp_msg_start ts; if (use_old_transport) { if (init_oldrelayfs() < 0) - cleanup_and_exit(0); + cleanup_and_exit(1); } else { if (init_relayfs() < 0) - cleanup_and_exit(0); + cleanup_and_exit(1); } ts.target = target_pid; + initialized = 2; send_request(STP_START, &ts, sizeof(ts)); if (load_only) cleanup_and_exit(2); break; } - case STP_MODULE: - { - dbug(2, "STP_MODULES request received\n"); - do_module(data); - break; - } - case STP_SYMBOLS: - { - struct _stp_msg_symbol *req = (struct _stp_msg_symbol *)data; - dbug(2, "STP_SYMBOLS request received\n"); - if (req->endian != 0x1234) { - fprintf(stderr,"ERROR: staprun is compiled with different endianess than the kernel!\n"); - cleanup_and_exit(0); - } - if (req->ptr_size != sizeof(char *)) { - fprintf(stderr,"ERROR: staprun is compiled with %d-bit pointers and the kernel uses %d-bit.\n", - 8*(int)sizeof(char *), 8*req->ptr_size); - cleanup_and_exit(0); - } - do_kernel_symbols(); - break; - } default: - fprintf(stderr, "WARNING: ignored message of type %d\n", (type)); + err("WARNING: ignored message of type %d\n", (type)); } } fclose(ofp); diff --git a/runtime/staprun/relay.c b/runtime/staprun/relay.c index 84e80660..30c4ce1e 100644 --- a/runtime/staprun/relay.c +++ b/runtime/staprun/relay.c @@ -74,9 +74,8 @@ static void *reader_thread(void *data) cpu_set_t cpu_mask; CPU_ZERO(&cpu_mask); CPU_SET(cpu, &cpu_mask); - if( sched_setaffinity( 0, sizeof(cpu_mask), &cpu_mask ) < 0 ) { - perror("sched_setaffinity"); - } + if( sched_setaffinity( 0, sizeof(cpu_mask), &cpu_mask ) < 0 ) + _perr("sched_setaffinity"); #ifdef NEED_PPOLL /* Without a real ppoll, there is a small race condition that could */ /* block ppoll(). So use a timeout to prevent that. */ @@ -95,15 +94,14 @@ static void *reader_thread(void *data) if (rc < 0) { dbug(3, "cpu=%d poll=%d errno=%d\n", cpu, rc, errno); if (errno != EINTR) { - fprintf(stderr, "poll error: %s\n",strerror(errno)); + _perr("poll error"); return(NULL); } stop_threads = 1; } while ((rc = read(relay_fd[cpu], buf, sizeof(buf))) > 0) { if (write(out_fd[cpu], buf, rc) != rc) { - fprintf(stderr, "Couldn't write to output fd %d for cpu %d, exiting: errcode = %d: %s\n", - out_fd[cpu], cpu, errno, strerror(errno)); + perr("Couldn't write to output %d for cpu %d, exiting.", out_fd[cpu], cpu); return(NULL); } } @@ -124,16 +122,21 @@ int init_relayfs(void) char rqbuf[128]; char buf[PATH_MAX], relay_filebase[PATH_MAX]; - dbug(1, "initializing relayfs\n"); + dbug(2, "initializing relayfs\n"); reader[0] = (pthread_t)0; relay_fd[0] = 0; out_fd[0] = 0; - if (statfs("/sys/kernel/debug", &st) == 0 && (int) st.f_type == (int) DEBUGFS_MAGIC) - sprintf(relay_filebase, "/sys/kernel/debug/systemtap/%s", modname); + if (statfs("/sys/kernel/debug", &st) == 0 + && (int) st.f_type == (int) DEBUGFS_MAGIC) { + if (sprintf_chk(relay_filebase, + "/sys/kernel/debug/systemtap/%s", + modname)) + return -1; + } else { - fprintf(stderr,"Cannot find relayfs or debugfs mount point.\n"); + err("Cannot find relayfs or debugfs mount point.\n"); return -1; } @@ -141,7 +144,8 @@ int init_relayfs(void) bulkmode = 1; for (i = 0; i < NR_CPUS; i++) { - sprintf(buf, "%s/trace%d", relay_filebase, i); + if (sprintf_chk(buf, "%s/trace%d", relay_filebase, i)) + return -1; dbug(2, "attempting to open %s\n", buf); relay_fd[i] = open(buf, O_RDONLY | O_NONBLOCK); if (relay_fd[i] < 0) @@ -151,12 +155,12 @@ int init_relayfs(void) dbug(2, "ncpus=%d, bulkmode = %d\n", ncpus, bulkmode); if (ncpus == 0) { - err("couldn't open %s.\n", buf); + _err("couldn't open %s.\n", buf); return -1; } if (ncpus > 1 && bulkmode == 0) { - err("ncpus=%d, bulkmode = %d\n", ncpus, bulkmode); - err("This is inconsistent! Please file a bug report. Exiting now.\n"); + _err("ncpus=%d, bulkmode = %d\n", ncpus, bulkmode); + _err("This is inconsistent! Please file a bug report. Exiting now.\n"); return -1; } @@ -164,16 +168,20 @@ int init_relayfs(void) for (i = 0; i < ncpus; i++) { if (outfile_name) { /* special case: for testing we sometimes want to write to /dev/null */ - if (strcmp(outfile_name, "/dev/null") == 0) - strcpy(buf, outfile_name); - else - sprintf(buf, "%s_%d", outfile_name, i); - } else - sprintf(buf, "stpd_cpu%d", i); + if (strcmp(outfile_name, "/dev/null") == 0) { + strcpy(buf, "/dev/null"); + } else { + if (sprintf_chk(buf, "%s_%d", outfile_name, i)) + return -1; + } + } else { + if (sprintf_chk(buf, "stpd_cpu%d", i)) + return -1; + } out_fd[i] = open (buf, O_CREAT|O_TRUNC|O_WRONLY, 0666); if (out_fd[i] < 0) { - fprintf(stderr, "ERROR: couldn't open output file %s.\n", buf); + perr("Couldn't open output file %s", buf); return -1; } } @@ -182,7 +190,7 @@ int init_relayfs(void) if (outfile_name) { out_fd[0] = open (outfile_name, O_CREAT|O_TRUNC|O_WRONLY, 0666); if (out_fd[0] < 0) { - fprintf(stderr, "ERROR: couldn't open output file %s.\n", outfile_name); + perr("Couldn't open output file %s", outfile_name); return -1; } } else @@ -191,9 +199,9 @@ int init_relayfs(void) } dbug(2, "starting threads\n"); for (i = 0; i < ncpus; i++) { - if (pthread_create(&reader[i], NULL, reader_thread, (void *)(long)i) < 0) { - fprintf(stderr, "failed to create thread\n"); - perror("Error creating thread"); + if (pthread_create(&reader[i], NULL, reader_thread, + (void *)(long)i) < 0) { + _perr("failed to create thread"); return -1; } } diff --git a/runtime/staprun/relay_old.c b/runtime/staprun/relay_old.c index 19f57788..3f65acbb 100644 --- a/runtime/staprun/relay_old.c +++ b/runtime/staprun/relay_old.c @@ -82,32 +82,40 @@ static int open_relayfs_files(int cpu, const char *relay_filebase, const char *p memset(&status[cpu], 0, sizeof(struct buf_status)); status[cpu].info.cpu = cpu; - sprintf(tmp, "%s%d", relay_filebase, cpu); + if (sprintf_chk(tmp, "%s%d", relay_filebase, cpu)) + return -1; + dbug(2, "Opening %s.\n", tmp); relay_fd[cpu] = open(tmp, O_RDONLY | O_NONBLOCK); if (relay_fd[cpu] < 0) { relay_fd[cpu] = 0; return 0; } - sprintf(tmp, "%s%d", proc_filebase, cpu); + if (sprintf_chk(tmp, "%s%d", proc_filebase, cpu)) + goto err1; dbug(2, "Opening %s.\n", tmp); proc_fd[cpu] = open(tmp, O_RDWR | O_NONBLOCK); if (proc_fd[cpu] < 0) { - fprintf(stderr, "ERROR: couldn't open proc file %s: errcode = %s\n", tmp, strerror(errno)); + perr("Couldn't open proc file %s", tmp); goto err1; } if (outfile_name) { - /* special case: for testing we sometimes want to write to /dev/null */ - if (strcmp(outfile_name, "/dev/null") == 0) - strcpy(tmp, outfile_name); - else - sprintf(tmp, "%s_%d", outfile_name, cpu); - } else - sprintf(tmp, "stpd_cpu%d", cpu); + /* special case: for testing we sometimes want to + * write to /dev/null */ + if (strcmp(outfile_name, "/dev/null") == 0) { + strcpy(tmp, "/dev/null"); + } else { + if (sprintf_chk(tmp, "%s_%d", outfile_name, cpu)) + goto err1; + } + } else { + if (sprintf_chk(tmp, "stpd_cpu%d", cpu)) + goto err1; + } if((percpu_tmpfile[cpu] = fopen(tmp, "w+")) == NULL) { - fprintf(stderr, "ERROR: Couldn't open output file %s: errcode = %s\n", tmp, strerror(errno)); + perr("Couldn't open output file %s", tmp); goto err2; } @@ -117,12 +125,14 @@ static int open_relayfs_files(int cpu, const char *relay_filebase, const char *p 0); if(relay_buffer[cpu] == MAP_FAILED) { - fprintf(stderr, "ERROR: couldn't mmap relay file, total_bufsize (%d) = subbuf_size (%d) * n_subbufs(%d), error = %s \n", (int)total_bufsize, (int)subbuf_size, (int)n_subbufs, strerror(errno)); + _perr("Couldn't mmap relay file, total_bufsize (%d)" \ + "= subbuf_size (%d) * n_subbufs(%d)", + (int)total_bufsize, (int)subbuf_size, (int)n_subbufs); goto err3; } - + return 1; - + err3: fclose(percpu_tmpfile[cpu]); err2: @@ -157,7 +167,7 @@ static int process_subbufs(struct _stp_buf_info *info) len = (subbuf_size - sizeof(padding)) - padding; if (len) { if (fwrite_unlocked (subbuf_ptr, len, 1, percpu_tmpfile[cpu]) != 1) { - fprintf(stderr, "ERROR: couldn't write to output file for cpu %d, exiting: errcode = %d: %s\n", cpu, errno, strerror(errno)); + _perr("Couldn't write to output file for cpu %d, exiting:", cpu); exit(1); } } @@ -181,9 +191,8 @@ static void *reader_thread(void *data) CPU_ZERO(&cpu_mask); CPU_SET(cpu, &cpu_mask); - if( sched_setaffinity( 0, sizeof(cpu_mask), &cpu_mask ) < 0 ) { - perror("sched_setaffinity"); - } + if( sched_setaffinity( 0, sizeof(cpu_mask), &cpu_mask ) < 0 ) + _perr("sched_setaffinity"); pollfd.fd = relay_fd[cpu]; pollfd.events = POLLIN; @@ -192,12 +201,10 @@ static void *reader_thread(void *data) rc = poll(&pollfd, 1, -1); if (rc < 0) { if (errno != EINTR) { - fprintf(stderr, "ERROR: poll error: %s\n", - strerror(errno)); + _perr("poll error"); exit(1); } - fprintf(stderr, "WARNING: poll warning: %s\n", - strerror(errno)); + err("WARNING: poll warning: %s\n", strerror(errno)); rc = 0; } @@ -210,7 +217,7 @@ static void *reader_thread(void *data) consumed_info.cpu = cpu; consumed_info.consumed = subbufs_consumed; if (write (proc_fd[cpu], &consumed_info, sizeof(struct _stp_consumed_info)) < 0) - fprintf(stderr,"WARNING: writing consumed info failed.\n"); + perr("writing consumed info failed"); } if (status[cpu].info.flushing) pthread_exit(NULL); @@ -237,7 +244,7 @@ int init_oldrelayfs(void) if (outfile_name) { out_fd[0] = open (outfile_name, O_CREAT|O_TRUNC|O_WRONLY, 0666); if (out_fd[0] < 0) { - fprintf(stderr, "ERROR: couldn't open output file %s.\n", outfile_name); + perr("Couldn't open output file '%s'", outfile_name); return -1; } } else @@ -245,14 +252,23 @@ int init_oldrelayfs(void) return 0; } - if (statfs("/sys/kernel/debug", &st) == 0 && (int) st.f_type == (int) DEBUGFS_MAGIC) { - sprintf(relay_filebase, "/sys/kernel/debug/systemtap/%s/trace", modname); - sprintf(proc_filebase, "/sys/kernel/debug/systemtap/%s/", modname); - } else if (statfs("/mnt/relay", &st) == 0 && (int) st.f_type == (int) RELAYFS_MAGIC) { - sprintf(relay_filebase, "/mnt/relay/systemtap/%s/trace", modname); - sprintf(proc_filebase, "/proc/systemtap/%s/", modname); + if (statfs("/sys/kernel/debug", &st) == 0 + && (int) st.f_type == (int) DEBUGFS_MAGIC) { + if (sprintf_chk(relay_filebase, + "/sys/kernel/debug/systemtap/%s/trace", + modname)) + return -1; + if (sprintf_chk(proc_filebase, + "/sys/kernel/debug/systemtap/%s/", modname)) + return -1; + } else if (statfs("/mnt/relay", &st) == 0 + && (int) st.f_type == (int) RELAYFS_MAGIC) { + if (sprintf_chk(relay_filebase, "/mnt/relay/systemtap/%s/trace", modname)) + return -1; + if (sprintf_chk(proc_filebase, "/proc/systemtap/%s/", modname)) + return -1; } else { - fprintf(stderr,"Cannot find relayfs or debugfs mount point.\n"); + err("Cannot find relayfs or debugfs mount point.\n"); return -1; } @@ -266,7 +282,7 @@ int init_oldrelayfs(void) if (ret == 0) break; if (ret < 0) { - fprintf(stderr, "ERROR: couldn't open relayfs files, cpu = %d\n", i); + err("ERROR: couldn't open relayfs files, cpu = %d\n", i); goto err; } } @@ -274,11 +290,18 @@ int init_oldrelayfs(void) ncpus = i; dbug(2, "ncpus=%d\n", ncpus); + if (ncpus == 0) { + err("couldn't open relayfs files.\n"); + return -1; + } + for (i = 0; i < ncpus; i++) { /* create a thread for each per-cpu buffer */ if (pthread_create(&reader[i], NULL, reader_thread, (void *)(long)i) < 0) { + int saved_errno = errno; close_relayfs_files(i); - fprintf(stderr, "ERROR: Couldn't create reader thread, cpu = %d\n", i); + err("ERROR: Couldn't create reader thread, cpu = %d: %s\n", + i, strerror(saved_errno)); goto err; } } diff --git a/runtime/staprun/stapio.c b/runtime/staprun/stapio.c new file mode 100644 index 00000000..696167af --- /dev/null +++ b/runtime/staprun/stapio.c @@ -0,0 +1,69 @@ +/* -*- linux-c -*- + * + * stapio.c - SystemTap module io handler. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Copyright (C) 2005-2007 Red Hat, Inc. + * + */ + +#include "staprun.h" +#include <pwd.h> +char *__name__ = "stapio"; + +int main(int argc, char **argv) +{ + setup_signals(); + + parse_args(argc, argv); + + if (buffer_size) + dbug(1, "Using a buffer of %u bytes.\n", buffer_size); + + if (optind < argc) { + parse_modpath(argv[optind++]); + dbug(2, "modpath=\"%s\", modname=\"%s\"\n", modpath, modname); + } + + if (optind < argc) { + if (attach_mod) { + err("ERROR: Cannot have module options with attach (-A).\n"); + usage(argv[0]); + } else { + unsigned start_idx = 3; /* reserve three slots in modoptions[] */ + while (optind < argc && start_idx+1 < MAXMODOPTIONS) + modoptions[start_idx++] = argv[optind++]; + modoptions[start_idx] = NULL; + } + } + + if (modpath == NULL || *modpath == '\0') { + err("ERROR: Need a module name or path to load.\n"); + usage(argv[0]); + } + + if (init_stapio()) + exit(1); + + initialized = 1; + + if (stp_main_loop()) { + err("ERROR: Couldn't enter main loop. Exiting.\n"); + exit(1); + } + + return 0; +} diff --git a/runtime/staprun/staprun.c b/runtime/staprun/staprun.c index f40f676d..d9738cd7 100644 --- a/runtime/staprun/staprun.c +++ b/runtime/staprun/staprun.c @@ -21,166 +21,198 @@ */ #include "staprun.h" -#include <pwd.h> - -extern char *optarg; -extern int optopt; -extern int optind; - -int verbose = 0; -int target_pid = 0; -unsigned int buffer_size = 0; -char modname[128]; -char *modpath = NULL; -#define MAXMODOPTIONS 64 -char *modoptions[MAXMODOPTIONS]; -char *target_cmd = NULL; -char *outfile_name = NULL; -char *username = NULL; -uid_t cmd_uid; -gid_t cmd_gid; -int attach_mod = 0; -int load_only = 0; - -static void path_parse_modname (char *path) + +int inserted_module = 0; + +/* used in dbug, _err and _perr */ +char *__name__ = "staprun"; + +extern long delete_module(const char *, unsigned int); + +static int +run_as(uid_t uid, gid_t gid, const char *path, char *const argv[]) { - char *mptr = rindex (path, '/'); - if (mptr == NULL) - mptr = path; - else - mptr++; + pid_t pid; + int rstatus; - if (strlen(mptr) >= sizeof(modname)) { - err("Module name larger than modname buffer.\n"); - exit (-1); + if ((pid = fork()) < 0) { + _perr("fork"); + return -1; } - strcpy(modname, mptr); + else if (pid == 0) { + /* Make sure we run as the full user. If we're + * switching to a non-root user, this won't allow + * that process to switch back to root (since the + * original process is setuid). */ + if (uid != getuid()) { + if (do_cap(CAP_SETGID, setresgid, gid, gid, gid) < 0) { + _perr("setresgid"); + exit(1); + } + if (do_cap(CAP_SETUID, setresuid, uid, uid, uid) < 0) { + _perr("setresuid"); + exit(1); + } + } + + /* Actually run the command. */ + if (execv(path, argv) < 0) + perror(path); + _exit(1); + } + + if (waitpid(pid, &rstatus, 0) < 0) + return -1; + + if (WIFEXITED(rstatus)) + return WEXITSTATUS(rstatus); + return -1; +} + +/* Keep the uid and gid settings because we will likely */ +/* conditionally restore "-u" */ +static int run_stapio(char **argv) +{ + uid_t uid = getuid(); + gid_t gid = getgid(); + argv[0] = PKGLIBDIR "/stapio"; + + if (verbose >= 2) { + int i = 0; + err("execing: "); + while (argv[i]) { + err("%s ", argv[i]); + i++; + } + err("\n"); + } + return run_as(uid, gid, argv[0], argv); +} + + +int init_staprun(void) +{ + dbug(2, "init_staprun\n"); + + if (mountfs() < 0) + return -1; + + /* We're done with CAP_SYS_ADMIN. */ + drop_cap(CAP_SYS_ADMIN); + + if (!attach_mod) { + if (insert_module() < 0) + return -1; + else + inserted_module = 1; + } + + return 0; +} - mptr = rindex(modname, '.'); - if (mptr) - *mptr = '\0'; +static void cleanup(int rc) +{ + /* Only cleanup once. */ + static int done = 0; + if (done == 0) + done = 1; + else + return; + + dbug(2, "rc=%d, inserted_module=%d\n", rc, inserted_module); + + /* rc == 2 means disconnected */ + if (rc == 2) + return; + + /* If we inserted the module and did not get rc==2, then */ + /* we really want to remove it. */ + if (inserted_module || rc == 3) { + long ret; + dbug(2, "removing module %s\n", modname); + ret = do_cap(CAP_SYS_MODULE, delete_module, modname, 0); + if (ret != 0) + err("Error removing module '%s': %s\n", modname, moderror(errno)); + } } -static void usage(char *prog) +static void exit_cleanup(void) { - fprintf(stderr, "\n%s [-v] [-c cmd ] [-x pid] [-u user]\n" - "\t[-A modname]] [-L] [-b bufsize] [-o FILE] kmod-name [kmod-options]\n", prog); - fprintf(stderr, "-v increase Verbosity.\n"); - fprintf(stderr, "-c cmd. Command \'cmd\' will be run and staprun will exit when it does.\n"); - fprintf(stderr, " _stp_target will contain the pid for the command.\n"); - fprintf(stderr, "-x pid. Sets _stp_target to pid.\n"); - fprintf(stderr, "-o FILE. Send output to FILE.\n"); - fprintf(stderr, "-u username. Run commands as username.\n"); - fprintf(stderr, "-b buffer size. The systemtap module will specify a buffer size.\n"); - fprintf(stderr, " Setting one here will override that value. The value should be\n"); - fprintf(stderr, " an integer between 1 and 64 which be assumed to be the\n"); - fprintf(stderr, " buffer size in MB. That value will be per-cpu in bulk mode.\n"); - fprintf(stderr, "-L Load module and start probes, then detach.\n"); - fprintf(stderr, "-A modname. Attach to systemtap module modname.\n"); - exit(1); + dbug(2, "something exited...\n"); + cleanup(1); } int main(int argc, char **argv) { - int c; - - setup_signals(); + int rc; - while ((c = getopt(argc, argv, "ALvb:t:d:c:o:u:x:")) != EOF) { - switch (c) { - case 'v': - verbose++; - break; - case 'b': - { - int size = (unsigned)atoi(optarg); - if (!size) - usage(argv[0]); - if (size > 64) { - fprintf(stderr, "Maximum buffer size is 64 (MB)\n"); - exit(1); - } - buffer_size = size; - break; - } - case 't': - case 'x': - target_pid = atoi(optarg); - break; - case 'd': - /* obsolete internal option used by stap */ - break; - case 'c': - target_cmd = optarg; - break; - case 'o': - outfile_name = optarg; - break; - case 'u': - username = optarg; - break; - case 'A': - attach_mod = 1; - break; - case 'L': - load_only = 1; - break; - default: - usage(argv[0]); - } + if (atexit(exit_cleanup)) { + _perr("cannot set exit function"); + exit(1); } - if (verbose) { - if (buffer_size) - printf ("Using a buffer of %u bytes.\n", buffer_size); + if (!init_cap()) + return 1; + + /* Get rid of a few standard environment variables (which */ + /* might cause us to do unintended things). */ + rc = unsetenv("IFS") || unsetenv("CDPATH") || unsetenv("ENV") + || unsetenv("BASH_ENV"); + if (rc) { + _perr("unsetenv failed"); + exit(-1); } + setup_signals(); + + parse_args(argc, argv); + + if (buffer_size) + dbug(2, "Using a buffer of %u bytes.\n", buffer_size); + if (optind < argc) { - modpath = argv[optind++]; - path_parse_modname(modpath); + parse_modpath(argv[optind++]); dbug(2, "modpath=\"%s\", modname=\"%s\"\n", modpath, modname); } if (optind < argc) { if (attach_mod) { - fprintf(stderr, "Cannot have module options with attach (-A).\n"); + err("ERROR: Cannot have module options with attach (-A).\n"); usage(argv[0]); } else { - unsigned start_idx = 3; /* reserve three slots in modoptions[] */ + unsigned start_idx = 0; while (optind < argc && start_idx+1 < MAXMODOPTIONS) modoptions[start_idx++] = argv[optind++]; modoptions[start_idx] = NULL; } } - if (!modpath) { - fprintf (stderr, "Need a module to load.\n"); + if (modpath == NULL || *modpath == '\0') { + err("ERROR: Need a module name or path to load.\n"); usage(argv[0]); } - if (username) { - struct passwd *pw = getpwnam(username); - if (!pw) { - fprintf(stderr, "Cannot find user \"%s\".\n", username); - exit(1); - } - cmd_uid = pw->pw_uid; - cmd_gid = pw->pw_gid; - } else { - cmd_uid = getuid(); - cmd_gid = getgid(); - } + if (check_permissions() != 1) + usage(argv[0]); /* now bump the priority */ - setpriority (PRIO_PROCESS, 0, -10); + rc = do_cap(CAP_SYS_NICE, setpriority, PRIO_PROCESS, 0, -10); + /* failure is not fatal in this case */ + if (rc < 0) + _perr("setpriority"); + + /* We're done with CAP_SYS_NICE. */ + drop_cap(CAP_SYS_NICE); if (init_staprun()) exit(1); - if (stp_main_loop()) { - fprintf(stderr,"Couldn't enter main loop. Exiting.\n"); - exit(1); - } + setup_staprun_signals(); + if (!attach_mod) + handle_symbols(); + + rc = run_stapio(argv); + cleanup(rc); return 0; } diff --git a/runtime/staprun/staprun.h b/runtime/staprun/staprun.h index f8fcfe66..cde44922 100644 --- a/runtime/staprun/staprun.h +++ b/runtime/staprun/staprun.h @@ -1,6 +1,6 @@ /* -*- linux-c -*- * - * staprun.h - include file for staprun + * staprun.h - include file for staprun and stapio * * This file is part of systemtap, and is free software. You can * redistribute it and/or modify it under the terms of the GNU General @@ -33,32 +33,93 @@ #include <sys/wait.h> #include <sys/statfs.h> #include <linux/version.h> +#include <sys/capability.h> #define DEBUG #ifdef DEBUG -#define dbug(level, args...) {if (verbose>=level) {fprintf(stderr,"%s:%d ",__FUNCTION__, __LINE__); fprintf(stderr,args);}} +#define dbug(level, args...) {if (verbose>=level) {fprintf(stderr,"%s:%s:%d ",__name__,__FUNCTION__, __LINE__); fprintf(stderr,args);}} #else #define dbug(level, args...) ; #endif /* DEBUG */ -#define err(args...) {fprintf(stderr,"%s:%d ",__FUNCTION__, __LINE__); fprintf(stderr,args); } +extern char *__name__; + +/* print to stderr */ +#define err(args...) fprintf(stderr,args) + +/* better perror() */ +#define perr(args...) do { \ + int _errno = errno; \ + fputs("ERROR: ", stderr); \ + fprintf(stderr, args); \ + fprintf(stderr, ": %s\n", strerror(_errno)); \ + } while (0) + +/* Error messages. Use these for serious errors, not informational messages to stderr. */ +#define _err(args...) do {fprintf(stderr,"%s:%s:%d: ERROR: ",__name__, __FUNCTION__, __LINE__); fprintf(stderr,args);} while(0) +#define _perr(args...) do { \ + int _errno = errno; \ + _err(args); \ + fprintf(stderr, ": %s\n", strerror(_errno)); \ + } while (0) +#define overflow_error() _err("Internal buffer overflow. Please file a bug report.\n") + +#define do_cap(cap,func,args...) ({ \ + int _rc, _saved_errno; \ + add_cap(cap); \ + _rc = func(args); \ + _saved_errno = errno; \ + del_cap(cap); \ + errno = _saved_errno; \ + _rc; \ + }) \ + + +/* Error checking version of sprintf() - returns 1 if overflow error */ +#define sprintf_chk(str, args...) ({ \ + int _rc; \ + _rc = snprintf(str, sizeof(str), args); \ + if (_rc >= (int)sizeof(str)) { \ + overflow_error(); \ + _rc = 1; \ + } \ + else \ + _rc = 0; \ + _rc; \ +}) + +/* Error checking version of snprintf() - returns 1 if overflow error */ +#define snprintf_chk(str, size, args...) ({ \ + int _rc; \ + _rc = snprintf(str, size, args); \ + if (_rc >= (int)size) { \ + overflow_error(); \ + _rc = 1; \ + } \ + else \ + _rc = 0; \ + _rc; \ +}) + +/* Grabbed from linux/module.h kernel include. */ +#define MODULE_NAME_LEN (64 - sizeof(unsigned long)) /* we define this so we are compatible with old transport, but we don't have to use it. */ #define STP_OLD_TRANSPORT #include "../transport/transport_msgs.h" -/* command to check system's kernel version */ -/* KERNEL_VERSION(2.6.15) = 132623 */ -#define VERSION_CMD "uname -r | awk \'{split($1,a,\".\"); split(a[3],b,\"-\"); exit (a[1]*65536+a[2]*256+b[1] <= 132623)}\'" extern int use_old_transport; -#define RELAYFS_MAGIC 0xF0B4A981 -#define DEBUGFS_MAGIC 0x64626720 +#define RELAYFS_MAGIC 0xF0B4A981 +#define DEBUGFS_MAGIC 0x64626720 +#define DEBUGFSDIR "/sys/kernel/debug" +#define RELAYFSDIR "/mnt/relay" /* * function prototypes */ int init_staprun(void); +int init_stapio(void); int stp_main_loop(void); int send_request(int type, void *data, int len); void cleanup_and_exit (int); @@ -71,28 +132,50 @@ void close_relayfs(void); int init_oldrelayfs(void); void close_oldrelayfs(int); void setup_signals(void); +/* cap.c */ +void print_cap(char *text); +int init_cap(void); +void add_cap(cap_value_t cap); +void del_cap(cap_value_t cap); +void drop_cap(cap_value_t cap); +/* staprun_funcs.c */ +void setup_staprun_signals(void); +const char *moderror(int err); +int insert_module(void); +int mountfs(void); +int check_permissions(void); +void handle_symbols(void); + +/* common.c functions */ +void parse_args(int argc, char **argv); +void usage(char *prog); +void parse_modpath(const char *); +void setup_signals(void); /* * variables */ extern int control_channel; extern int ncpus; +extern int initialized; /* flags */ extern int verbose; extern unsigned int buffer_size; -extern char modname[]; +extern char *modname; extern char *modpath; -extern char *modoptions[]; +#define MAXMODOPTIONS 64 +extern char *modoptions[MAXMODOPTIONS]; extern int target_pid; extern char *target_cmd; extern char *outfile_name; extern int attach_mod; extern int load_only; -/* uid/gid to use when execing external programs */ -extern uid_t cmd_uid; -extern gid_t cmd_gid; +/* getopt variables */ +extern char *optarg; +extern int optopt; +extern int optind; /* maximum number of CPUs we can handle */ #define NR_CPUS 256 diff --git a/runtime/staprun/staprun_funcs.c b/runtime/staprun/staprun_funcs.c new file mode 100644 index 00000000..0747b530 --- /dev/null +++ b/runtime/staprun/staprun_funcs.c @@ -0,0 +1,443 @@ +/* -*- linux-c -*- + * + * staprun_funcs.c - staprun functions + * + * This file is part of systemtap, and is free software. You can + * redistribute it and/or modify it under the terms of the GNU General + * Public License (GPL); either version 2, or (at your option) any + * later version. + * + * Copyright (C) 2007 Red Hat Inc. + */ + +#include "staprun.h" +#include <sys/mount.h> +#include <sys/utsname.h> +#include <grp.h> +#include <pwd.h> + +void setup_staprun_signals(void) +{ + struct sigaction a; + memset(&a, 0, sizeof(a)); + sigfillset(&a.sa_mask); + a.sa_handler = SIG_IGN; + sigaction(SIGINT, &a, NULL); + sigaction(SIGTERM, &a, NULL); + sigaction(SIGHUP, &a, NULL); + sigaction(SIGQUIT, &a, NULL); +} + +extern long init_module(void *, unsigned long, const char *); + +/* Module errors get translated. */ +const char *moderror(int err) +{ + switch (err) { + case ENOEXEC: + return "Invalid module format"; + case ENOENT: + return "Unknown symbol in module"; + case ESRCH: + return "Module has wrong symbol version"; + case EINVAL: + return "Invalid parameters"; + default: + return strerror(err); + } +} + +int insert_module(void) +{ + int i; + long ret; + void *file; + char *opts; + int fd, saved_errno; + struct stat sbuf; + + dbug(2, "inserting module\n"); + + opts = malloc(128); + if (opts == NULL) { + _perr("allocating memory failed"); + return -1; + } + if (snprintf_chk(opts, 128, "_stp_bufsize=%d", buffer_size)) + return -1; + for (i = 0; modoptions[i] != NULL; i++) { + opts = realloc(opts, strlen(opts) + strlen(modoptions[i]) + 2); + if (opts == NULL) { + _perr("reallocating memory failed"); + return -1; + } + strcat(opts, " "); + strcat(opts, modoptions[i]); + } + dbug(2, "module options: %s\n", opts); + + /* Open the module file. */ + fd = open(modpath, O_RDONLY); + if (fd < 0) { + perr("Error opening '%s'", modpath); + return -1; + } + + /* Now that the file is open, figure out how big it is. */ + if (fstat(fd, &sbuf) < 0) { + _perr("Error stat'ing '%s'", modpath); + close(fd); + return -1; + } + + /* mmap in the entire module. */ + file = mmap(NULL, sbuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (file == MAP_FAILED) { + _perr("Error mapping '%s'", modpath); + close(fd); + free(opts); + return -1; + } + + /* Actually insert the module */ + ret = do_cap(CAP_SYS_MODULE, init_module, file, sbuf.st_size, opts); + saved_errno = errno; + + /* Cleanup. */ + free(opts); + munmap(file, sbuf.st_size); + close(fd); + + if (ret != 0) { + err("Error inserting module '%s': %s\n", modpath, moderror(saved_errno)); + return -1; + } + return 0; +} + +int mountfs(void) +{ + struct stat sb; + struct statfs st; + int rc; + + /* If the debugfs dir is already mounted correctly, we're done. */ + if (statfs(DEBUGFSDIR, &st) == 0 + && (int) st.f_type == (int) DEBUGFS_MAGIC) + return 0; + + /* If DEBUGFSDIR exists (and is a directory), try to mount + * DEBUGFSDIR. */ + rc = stat(DEBUGFSDIR, &sb); + if (rc == 0 && S_ISDIR(sb.st_mode)) { + /* If we can mount the debugfs dir correctly, we're done. */ + rc = do_cap(CAP_SYS_ADMIN, mount, "debugfs", DEBUGFSDIR, + "debugfs", 0, NULL); + if (rc == 0) + return 0; + /* If we got ENODEV, that means that debugfs isn't + * supported, so we'll need try try relayfs. If we + * didn't get ENODEV, we got a real error. */ + else if (errno != ENODEV) { + perr("Couldn't mount %s", DEBUGFSDIR); + return -1; + } + } + + /* DEBUGFSDIR couldn't be mounted. So, try RELAYFSDIR. */ + + /* If the relayfs dir is already mounted correctly, we're done. */ + if (statfs(RELAYFSDIR, &st) == 0 + && (int)st.f_type == (int)RELAYFS_MAGIC) + return 0; + + /* Ensure that RELAYFSDIR exists and is a directory. */ + rc = stat(RELAYFSDIR, &sb); + if (rc == 0 && ! S_ISDIR(sb.st_mode)) { + err("%s exists but isn't a directory.\n", RELAYFSDIR); + return -1; + } + else if (rc < 0) { + mode_t old_umask; + int saved_errno; + gid_t gid = getgid(); + uid_t uid = getuid(); + + /* To ensure the directory gets created with the proper + * permissions, set umask to a known value. */ + old_umask = umask(0002); + + /* To ensure the directory gets created with the + * proper group, we'll have to temporarily switch to + * root. */ + if (do_cap(CAP_SETUID, setuid, 0) < 0) { + _perr("Couldn't change user while creating %s", RELAYFSDIR); + return -1; + } + if (do_cap(CAP_SETGID, setgid, 0) < 0) { + _perr("Couldn't change group while creating %s", RELAYFSDIR); + return -1; + } + + /* Try to create the directory, saving the return + * status and errno value. */ + rc = mkdir(RELAYFSDIR, 0755); + saved_errno = errno; + + /* Restore everything we changed. */ + if (do_cap(CAP_SETGID, setgid, gid) < 0) { + _perr("Couldn't restore group while creating %s", RELAYFSDIR); + return -1; + } + if (do_cap(CAP_SETUID, setuid, uid) < 0) { + _perr("Couldn't restore user while creating %s", RELAYFSDIR); + return -1; + } + umask(old_umask); + + /* If creating the directory failed, error out. */ + if (rc < 0) { + err("Couldn't create %s: %s\n", RELAYFSDIR, strerror(saved_errno)); + return -1; + } + } + + /* Now that we're sure the directory exists, try mounting RELAYFSDIR. */ + if (do_cap(CAP_SYS_ADMIN, mount, "relayfs", RELAYFSDIR, "relayfs", 0, NULL) < 0) { + perr("Couldn't mount %s", RELAYFSDIR); + return -1; + } + return 0; +} + + +/* + * Members of the 'stapusr' group can only use "blessed" modules - + * ones in the '/lib/modules/KVER/systemtap' directory. Make sure the + * module path is in that directory. + * + * Returns: -1 on errors, 0 on failure, 1 on success. + */ +static int +check_path(void) +{ + struct utsname utsbuf; + struct stat sb; + char staplib_dir_path[PATH_MAX]; + char staplib_dir_realpath[PATH_MAX]; + char module_realpath[PATH_MAX]; + + /* First, we need to figure out what the kernel + * version is and build the '/lib/modules/KVER/systemtap' path. */ + if (uname(&utsbuf) != 0) { + _perr("ERROR: Unable to determine kernel version, uname failed"); + return -1; + } + if (sprintf_chk(staplib_dir_path, "/lib/modules/%s/systemtap", utsbuf.release)) + return -1; + + /* Validate /lib/modules/KVER/systemtap. */ + if (stat(staplib_dir_path, &sb) < 0) { + perr("Members of the \"stapusr\" group can only use modules within\n" + " the \"%s\" directory.\n" + " Error getting information on that directory", staplib_dir_path); + return -1; + } + /* Make sure it is a directory. */ + if (! S_ISDIR(sb.st_mode)) { + err("ERROR: Members of the \"stapusr\" group can only use modules within\n" + " the \"%s\" directory.\n" + " That path must refer to a directory.\n", staplib_dir_path); + return -1; + } + /* Make sure it is owned by root. */ + if (sb.st_uid != 0) { + err("ERROR: Members of the \"stapusr\" group can only use modules within\n" + " the \"%s\" directory.\n" + " That directory should be owned by root.\n", staplib_dir_path); + return -1; + } + /* Make sure it isn't world writable. */ + if (sb.st_mode & S_IWOTH) { + err("ERROR: Members of the \"stapusr\" group can only use modules within\n" + " the \"%s\" directory.\n" + " That directory should not be world writable.\n", staplib_dir_path); + return -1; + } + + /* Use realpath() to canonicalize the module directory + * path. */ + if (realpath(staplib_dir_path, staplib_dir_realpath) == NULL) { + perr("Members of the \"stapusr\" group can only use modules within\n" + " the \"%s\" directory.\n" + " Unable to canonicalize that directory", staplib_dir_path); + return -1; + } + + /* Use realpath() to canonicalize the module path. */ + if (realpath(modpath, module_realpath) == NULL) { + perr("Unable to canonicalize path \"%s\"",modpath); + return -1; + } + + /* Now we've got two canonicalized paths. Make sure + * module_realpath starts with staplib_dir_realpath. */ + if (strncmp(staplib_dir_realpath, module_realpath, + strlen(staplib_dir_realpath)) != 0) { + err("ERROR: Members of the \"stapusr\" group can only use modules within\n" + " the \"%s\" directory.\n" + " Module \"%s\" does not exist within that directory.\n", + staplib_dir_path, modpath); + return 0; + } + return 1; +} + +/* + * Check the user's permissions. Is he allowed to run staprun (or is + * he limited to "blessed" modules)? + * + * Returns: -1 on errors, 0 on failure, 1 on success. + */ +int check_permissions(void) +{ + gid_t gid, gidlist[NGROUPS_MAX]; + gid_t stapdev_gid, stapusr_gid; + int i, ngids; + struct group *stgr; + int path_check = 0; + + /* If we're root, we can do anything. */ + if (geteuid() == 0) + return 1; + + /* Lookup the gid for group "stapdev" */ + errno = 0; + stgr = getgrnam("stapdev"); + /* If we couldn't find the group, just set the gid to an + * invalid number. Just because this group doesn't exist + * doesn't mean the other group doesn't exist. */ + if (stgr == NULL) + stapdev_gid = (gid_t)-1; + else + stapdev_gid = stgr->gr_gid; + + /* Lookup the gid for group "stapusr" */ + errno = 0; + stgr = getgrnam("stapusr"); + /* If we couldn't find the group, just set the gid to an + * invalid number. Just because this group doesn't exist + * doesn't mean the other group doesn't exist. */ + if (stgr == NULL) + stapusr_gid = (gid_t)-1; + else + stapusr_gid = stgr->gr_gid; + + /* If neither group was found, just return an error. */ + if (stapdev_gid == (gid_t)-1 && stapusr_gid == (gid_t)-1) { + err("ERROR: unable to find either group \"stapdev\" or group \"stapusr\"\n"); + return -1; + } + + /* According to the getgroups() man page, getgroups() may not + * return the effective gid, so try to match it first. */ + gid = getegid(); + if (gid == stapdev_gid) + return 1; + else if (gid == stapusr_gid) + path_check = 1; + + /* Get the list of the user's groups. */ + ngids = getgroups(NGROUPS_MAX, gidlist); + if (ngids < 0) { + perr("Unable to retrieve group list"); + return -1; + } + + for (i = 0; i < ngids; i++) { + /* If the user is a member of 'stapdev', then we're + * done, since he can use staprun without any + * restrictions. */ + if (gidlist[i] == stapdev_gid) + return 1; + + /* If the user is a member of 'stapusr', then we'll + * need to check the module path. However, we'll keep + * checking groups since it is possible the user is a + * member of both groups and we haven't seen the + * 'stapdev' group yet. */ + if (gidlist[i] == stapusr_gid) + path_check = 1; + } + + /* If path_check is 0, then the user isn't a member of either + * group. Error out. */ + if (path_check == 0) { + err("ERROR: you must be a member of either group \"stapdev\" or group \"stapusr\"\n"); + return 0; + } + + /* At this point the user is only a member of the 'stapusr' + * group. Members of the 'stapusr' group can only use modules + * in /lib/modules/KVER/systemtap. Make sure the module path + * is in that directory. */ + return check_path(); +} + +/* wait for symbol requests and reply */ +void handle_symbols(void) +{ + ssize_t nb; + void *data; + int type; + char recvbuf[8192]; + + dbug(2, "waiting for symbol requests\n"); + + /* create control channel */ + if (init_ctl_channel() < 0) { + err("Failed to initialize control channel.\n"); + exit(1); + } + + while (1) { /* handle messages from control channel */ + nb = read(control_channel, recvbuf, sizeof(recvbuf)); + if (nb <= 0) { + if (errno != EINTR) + _perr("Unexpected EOF in read (nb=%ld)", (long)nb); + continue; + } + + type = *(int *)recvbuf; + data = (void *)(recvbuf + sizeof(int)); + + switch (type) { + case STP_MODULE: + { + dbug(2, "STP_MODULES request received\n"); + do_module(data); + goto done; + } + case STP_SYMBOLS: + { + struct _stp_msg_symbol *req = (struct _stp_msg_symbol *)data; + dbug(2, "STP_SYMBOLS request received\n"); + if (req->endian != 0x1234) { + err("ERROR: staprun is compiled with different endianess than the kernel!\n"); + exit(1); + } + if (req->ptr_size != sizeof(char *)) { + err("ERROR: staprun is compiled with %d-bit pointers and the kernel uses %d-bit.\n", + 8*(int)sizeof(char *), 8*req->ptr_size); + exit(1); + } + do_kernel_symbols(); + break; + } + default: + err("WARNING: ignored message of type %d\n", (type)); + } + } +done: + close_ctl_channel(); +} diff --git a/runtime/staprun/symbols.c b/runtime/staprun/symbols.c index 49755c5e..61b56b2e 100644 --- a/runtime/staprun/symbols.c +++ b/runtime/staprun/symbols.c @@ -40,42 +40,53 @@ static int get_sections(char *name, char *data_start, int datalen) res = snprintf(dir, sizeof(dir), SECDIR, name); if (res >= (int)sizeof(dir)) { - fprintf(stderr, "ERROR: couldn't fit module \"%s\" into dir buffer.\n", name); - fprintf(stderr, "This should never happen. Please file a bug report.\n"); - cleanup_and_exit(0); + _err("Couldn't fit module \"%s\" into dir buffer.\n" \ + "This should never happen. Please file a bug report.\n", name); + exit(1); } - + if ((secdir = opendir(dir)) == NULL) return 0; + /* Initialize mod. */ memset(mod, 0, sizeof(struct _stp_module)); + + /* Copy name in and check for overflow. */ strncpy(mod->name, name, STP_MODULE_NAME_LEN); + if (mod->name[STP_MODULE_NAME_LEN - 1] != '\0') { + _err("Couldn't fit module \"%s\" into mod->name buffer.\n" \ + "This should never happen. Please file a bug report.\n", name); + exit(1); + } while ((d = readdir(secdir))) { char *secname = d->d_name; + + /* Copy filename in and check for overflow. */ res = snprintf(filename, sizeof(filename), "/sys/module/%s/sections/%s", name, secname); if (res >= (int)sizeof(filename)) { - fprintf(stderr, "ERROR: couldn't fit secname \"%s\" into filename buffer.\n", secname); - fprintf(stderr, "This should never happen. Please file a bug report.\n"); + _err("Couldn't fit secname \"%s\" into filename buffer.\n" \ + "This should never happen. Please file a bug report.\n", secname); closedir(secdir); - cleanup_and_exit(0); + exit(1); + } + + /* filter out some non-useful stuff */ + if (!strncmp(secname,"__",2) + || !strcmp(secname,".") + || !strcmp(secname,"..") + || !strcmp(secname,".module_sig") + || !strcmp(secname,".modinfo") + || !strcmp(secname,".strtab") + || !strcmp(secname,".symtab") ) { + continue; } + if (!strncmp(secname, ".gnu.linkonce", 13) + && strcmp(secname, ".gnu.linkonce.this_module")) + continue; + if ((fd = open(filename,O_RDONLY)) >= 0) { if (read(fd, buf, 32) > 0) { - - /* filter out some non-useful stuff */ - if (!strncmp(secname,"__",2) - || !strcmp(secname,".module_sig") - || !strcmp(secname,".modinfo") - || !strcmp(secname,".strtab") - || !strcmp(secname,".symtab") ) { - close(fd); - continue; - } - if (!strncmp(secname, ".gnu.linkonce", 13) - && strcmp(secname, ".gnu.linkonce.this_module")) - continue; - /* create next section */ sec = (struct _stp_symbol *)data; if (data - data_start + (int)sizeof(struct _stp_symbol) > datalen) @@ -85,8 +96,10 @@ static int get_sections(char *name, char *data_start, int datalen) sec->symbol = (char *)(strdata - strdata_start); mod->num_sections++; - /* now create string data for the section */ - if (strdata - strdata_start + strlen(strdata) >= sizeof(strdata_start)) + /* now create string data for the + * section (checking for overflow) */ + if ((strdata - strdata_start + strlen(strdata)) + >= sizeof(strdata_start)) goto err1; strcpy(strdata, secname); strdata += strlen(secname) + 1; @@ -120,8 +133,9 @@ err1: close(fd); closedir(secdir); err0: - err("overflowed buffers.\n"); - cleanup_and_exit(0); + /* if this happens, something went seriously wrong. */ + _err("Unexpected error. Overflowed buffers.\n"); + exit(1); return 0; /* not reached */ } #undef SECDIR @@ -132,8 +146,8 @@ void send_module (char *mname) int len = get_sections(mname, data, sizeof(data)); if (len) { if (send_request(STP_MODULE, data, len) < 0) { - err("Loading of module %s failed. Exiting...\n", mname); - cleanup_and_exit(0); + _err("Loading of module %s failed. Exiting...\n", mname); + exit(1); } } } @@ -150,6 +164,7 @@ int do_module (void *data) send_module(d->d_name); closedir(moddir); } + send_request(STP_MODULE, data, 0); return 1; } @@ -166,34 +181,35 @@ static int compar(const void *p1, const void *p2) return 1; } -#define MAX_SYMBOLS 32768 +#define MAX_SYMBOLS 32*1024 void do_kernel_symbols(void) { - FILE *kallsyms; - char *sym_base, *data_base; + FILE *kallsyms=NULL; + char *sym_base=NULL, *data_base=NULL; char buf[128], *ptr, *name, *data, *dataptr, *datamax, type; unsigned long addr; struct _stp_symbol *syms; - int num_syms, i = 0; + int num_syms, i = 0, max_syms= MAX_SYMBOLS; + int data_basesize = MAX_SYMBOLS*32; - sym_base = malloc(MAX_SYMBOLS*sizeof(struct _stp_symbol)+sizeof(long)); - data_base = malloc(MAX_SYMBOLS*32); + sym_base = malloc(max_syms*sizeof(struct _stp_symbol)+sizeof(long)); + data_base = malloc(data_basesize); if (data_base == NULL || sym_base == NULL) { - fprintf(stderr,"Failed to allocate memory for symbols\n"); - cleanup_and_exit(0); + _err("Failed to allocate memory for symbols\n"); + goto err; } *(int *)data_base = STP_SYMBOLS; dataptr = data = data_base + sizeof(long); - datamax = dataptr + MAX_SYMBOLS*32 - sizeof(long); + datamax = data_base + data_basesize; *(int *)sym_base = STP_SYMBOLS; syms = (struct _stp_symbol *)(sym_base + sizeof(long)); kallsyms = fopen ("/proc/kallsyms", "r"); if (!kallsyms) { - perror("Fatal error: Unable to open /proc/kallsyms:"); - cleanup_and_exit(0); + _perr("Fatal error: Unable to open /proc/kallsyms"); + goto err; } /* put empty string in data */ @@ -216,8 +232,30 @@ void do_kernel_symbols(void) while (*name) *dataptr++ = *name++; *dataptr++ = 0; i++; - if (dataptr > datamax - 1000) - break; + if (i >= max_syms) { + char *s; + max_syms *= 2; + s = realloc(sym_base, max_syms*sizeof(struct _stp_symbol)+sizeof(long)); + if (s == NULL) { + _err("Could not allocate enough space for symbols.\n"); + goto err; + } + syms = (struct _stp_symbol *)(s + sizeof(long)); + sym_base = s; + } + if (dataptr > datamax - 1024) { + char *db; + data_basesize *= 2; + db = realloc(data_base, data_basesize); + if (db == NULL) { + _err("Could not allocate enough space for symbols.\n"); + goto err; + } + dataptr = db + (dataptr - data_base); + data = db + sizeof(long); + datamax = db + data_basesize; + data_base = db; + } } } num_syms = i; @@ -241,18 +279,16 @@ void do_kernel_symbols(void) free(data_base); free(sym_base); fclose(kallsyms); - - if (dataptr >= datamax) { - err("Error: overflowed symbol data area.\n"); - cleanup_and_exit(0); - } return; err: - free(data_base); - free(sym_base); - fclose(kallsyms); - - err("Loading of symbols failed. Exiting...\n"); - cleanup_and_exit(0); + if (data_base) + free(data_base); + if (sym_base) + free(sym_base); + if (kallsyms) + fclose(kallsyms); + + _err("Loading of symbols failed. Exiting...\n"); + exit(1); } |