From be7e131bdedb20ad690fdc83a52d74041abd0e54 Mon Sep 17 00:00:00 2001 From: Martin Cermak Date: Fri, 26 Apr 2024 17:09:45 +0200 Subject: [PATCH] PR30321 Privilege separation if invoked as root Provide new command line switch 'stap --build-as' that allows for running passes 1-4 under an unprivileged user. In case this switch is specified, systemtap forks and runs passes 1-4 under the specified user. At the RPM install time a new user 'stapunpriv' is created, and can be used with 'stap --build-as=stapunpriv'. If '--build-as' isn't specified, systemtap behaves the traditional way, no forking happens. This commit is a preparatory step. Further work is supposed to happen so that the privilege separation brings a true improvement from the security perspective. --- cmdline.cxx | 1 + cmdline.h | 1 + interactive.cxx | 101 ++++++++++++++++---- main.cxx | 249 +++++++++++++++++++++++++++++++++++++----------- man/stap.1.in | 7 ++ remote.cxx | 175 +++++++++++++++++++++++++++++++--- remote.h | 2 + session.cxx | 34 ++++++- session.h | 5 + systemtap.spec | 8 +- translate.cxx | 4 + util.cxx | 38 +++++++- util.h | 1 + 13 files changed, 537 insertions(+), 89 deletions(-) diff --git a/cmdline.cxx b/cmdline.cxx index 8bee6f3ab..85e94ae69 100644 --- a/cmdline.cxx +++ b/cmdline.cxx @@ -15,6 +15,7 @@ struct option stap_long_options[] = { { "skip-badvars", no_argument, NULL, LONG_OPT_SKIP_BADVARS }, { "vp", required_argument, NULL, LONG_OPT_VERBOSE_PASS }, { "unprivileged", no_argument, NULL, LONG_OPT_UNPRIVILEGED }, + { "build-as", optional_argument, NULL, LONG_OPT_BUILD_AS }, { "client-options", no_argument, NULL, LONG_OPT_CLIENT_OPTIONS }, { "help", no_argument, NULL, LONG_OPT_HELP }, { "disable-cache", no_argument, NULL, LONG_OPT_DISABLE_CACHE }, diff --git a/cmdline.h b/cmdline.h index c97c538a9..ac3b4ca01 100644 --- a/cmdline.h +++ b/cmdline.h @@ -20,6 +20,7 @@ enum { LONG_OPT_VERBOSE_PASS = 256, LONG_OPT_SKIP_BADVARS, LONG_OPT_UNPRIVILEGED, + LONG_OPT_BUILD_AS, LONG_OPT_CLIENT_OPTIONS, LONG_OPT_HELP, LONG_OPT_DISABLE_CACHE, diff --git a/interactive.cxx b/interactive.cxx index 2250b318c..bd6ddd71e 100644 --- a/interactive.cxx +++ b/interactive.cxx @@ -33,6 +33,7 @@ extern "C" { #include #include #include +#include #if HAVE_LIBSQLITE3 #include @@ -43,7 +44,9 @@ extern "C" { extern int passes_0_4 (systemtap_session &s); extern int -pass_5 (systemtap_session &s, vector targets); +pass_5_1 (systemtap_session &s, vector targets); +extern int +pass_5_2 (systemtap_session &s, vector targets); static int forked_passes_0_4 (systemtap_session &s); @@ -775,6 +778,9 @@ public: vector &tokens __attribute ((unused)), string &input __attribute ((unused))) { + int rc = 0; + // logging treshold + unsigned int lt = 2; if (script_vec.empty()) { clog << "No script specified." << endl; @@ -787,22 +793,83 @@ public: // just use the current session. s.cmdline_script = join(script_vec, "\n"); s.have_script = true; - int rc = forked_passes_0_4(s); -#if 0 - if (rc) - { - // Compilation failed. - // Try again using a server if appropriate. - if (s.try_server ()) - rc = passes_0_4_again_with_server (s); - } -#endif - if (rc || s.perpass_verbose[0] >= 1) - s.explain_auto_options (); - - // Run pass 5, if passes 0-4 worked. - if (rc == 0 && s.last_pass >= 5 && !pending_interrupts) - rc = pass_5 (s, *saved_targets); + if (s.build_as != "") + { + // PR30321: Privilege separation + // Fork the stap process in two: An unprivileged child, and a privileged parent + // Child will run passes 1-4 and part of pass 5 (up to preparing staprun cmdline + // Parent will wait, spawn staprun (second part of pass 5), and finish. + pid_t frkrc = fork(); + if (frkrc == -1) + { + clog << _("ERROR: Fork failed. Terminating...") << endl; + return EXIT_FAILURE; + } + else if (frkrc == 0) + { + // Child process (unprivileged) + rc = run_unprivileged(s.build_as, s.build_as_uid, s.build_as_gid, s.verbose); + if (rc != EXIT_SUCCESS) + return rc; + if (s.verbose >= lt) + clog << _F("Child pid=%d, uid=%d, euid=%d, gid=%d, egid=%d\n", + getpid(), getuid(), geteuid(), getgid(), getegid()); + + rc = forked_passes_0_4(s); +// #if 0 +// if (rc) +// { +// // Compilation failed. +// // Try again using a server if appropriate. +// if (s.try_server ()) +// rc = passes_0_4_again_with_server (s); +// } +// #endif + if (rc || s.perpass_verbose[0] >= 1) + s.explain_auto_options (); + + // Run pass 5, if passes 0-4 worked. + if (rc == 0 && s.last_pass >= 5 && !pending_interrupts) + rc = pass_5_1 (s, *saved_targets); + _exit(rc); + } + else + { + // Parent process (privileged) + if (s.verbose >= lt) + clog << _F("Parent pid=%d, uid=%d, euid=%d, gid=%d, egid=%d\n", + getpid(), getuid(), geteuid(), getgid(), getegid()); + int wstatus; + (void)waitpid(frkrc, &wstatus, 0); + rc = WEXITSTATUS(wstatus); + if (s.verbose >= lt) + clog << _("Child finished.") << endl; + } + rc = pass_5_2 (s, *saved_targets); + } + else + { + // --build-as wasn't specified, no need to fork + rc = forked_passes_0_4(s); +// #if 0 +// if (rc) +// { +// // Compilation failed. +// // Try again using a server if appropriate. +// if (s.try_server ()) +// rc = passes_0_4_again_with_server (s); +// } +// #endif + if (rc || s.perpass_verbose[0] >= 1) + s.explain_auto_options (); + + // Run pass 5, if passes 0-4 worked. + if (rc == 0 && s.last_pass >= 5 && !pending_interrupts) + { + rc = pass_5_1 (s, *saved_targets); + rc = pass_5_2 (s, *saved_targets); + } + } s.reset_tmp_dir(); return false; } diff --git a/main.cxx b/main.cxx index 50c7e3f1e..827a89329 100644 --- a/main.cxx +++ b/main.cxx @@ -60,6 +60,7 @@ extern "C" { #include #include #include +#include } using namespace std; @@ -1371,10 +1372,25 @@ passes_0_4 (systemtap_session &s) return rc; } +// Unprivileged (PR30321) part of pass 5 int -pass_5 (systemtap_session &s, vector targets) +pass_5_1 (systemtap_session &s, vector targets) { - // PASS 5: RUN + // PASS 5: RUN (part 1 - unprivileged) + s.verbose = s.perpass_verbose[4]; + int rc; + // Prepare the staprun cmdline, but don't spawn it. + // Store staprun cmdline in s->tmpdir + "/staprun_args". + // Run under unprivileged user. + rc = remote::run1(targets); + return rc; +} + +// Privileged (PR30321) part of pass 5 +int +pass_5_2 (systemtap_session &s, vector targets) +{ + // PASS 5: RUN (part 2 - privileged) s.verbose = s.perpass_verbose[4]; struct tms tms_before; times (& tms_before); @@ -1385,7 +1401,10 @@ pass_5 (systemtap_session &s, vector targets) // and don't take an indefinite amount of time. PROBE1(stap, pass5__start, &s); if (s.verbose) clog << _("Pass 5: starting run.") << endl; - int rc = remote::run(targets); + // Spawn staprun with already prepared commandline. + // Retreive staprun cmdline in s->tmpdir + "/staprun_args". + // Run under privileged user. + int rc = remote::run2(targets); struct tms tms_after; times (& tms_after); unsigned _sc_clk_tck = sysconf (_SC_CLK_TCK); @@ -1451,6 +1470,176 @@ passes_0_4_again_with_server (systemtap_session &s) return rc; } +static int +passes_1_5 (systemtap_session &s, vector targets) +{ + int rc = 0; + + // Discover and loop over each unique session created by the remote targets. + set sessions; + for (unsigned i = 0; i < targets.size(); ++i) + sessions.insert(targets[i]->get_session()); + + for (set::iterator it = sessions.begin(); + rc == 0 && !pending_interrupts && it != sessions.end(); ++it) + { + systemtap_session& ss = **it; + if (ss.verbose > 1) + clog << _F("Session arch: %s release: %s", + ss.architecture.c_str(), ss.kernel_release.c_str()) + << endl + << _F("Build tree: \"%s\"", + ss.kernel_build_tree.c_str()) + << endl; + +#if HAVE_NSS + // If requested, query server status. This is independent + // of other tasks. + nss_client_query_server_status (ss); + + // If requested, manage trust of servers. This is + // independent of other tasks. + nss_client_manage_server_trust (ss); +#endif + + // Run the passes only if a script has been specified or + // if we're dumping something. The requirement for a + // script has already been checked in + // systemtap_session::check_options. + if (ss.have_script || ss.dump_mode) + { + // Run passes 0-4 for each unique session, either + // locally or using a compile-server. + ss.init_try_server (); + if ((rc = passes_0_4 (ss))) + { + // Compilation failed. + // Try again using a server if appropriate. + if (ss.try_server ()) + rc = passes_0_4_again_with_server (ss); + } + if (rc || s.perpass_verbose[0] >= 1) + s.explain_auto_options (); + } + } + + if (rc == 0 && s.have_script && s.last_pass >= 5 && ! pending_interrupts) + { + rc = pass_5_1 (s, targets); + rc = pass_5_2 (s, targets); + } + return rc; +} + + + +static int +passes_1_5_build_as (systemtap_session &s, vector targets) +{ + int rc = 0; + // logging verbosity treshold + unsigned int vt = 2; + + // Discover and loop over each unique session created by the remote targets. + set sessions; + for (unsigned i = 0; i < targets.size(); ++i) + sessions.insert(targets[i]->get_session()); + + // PR30321: Privilege separation + // Fork the stap process in two: An unprivileged child, and a privileged parent + // Child will run passes 1-4 and part of pass 5 (up to preparing staprun cmdline + // Parent will wait, spawn staprun (second part of pass 5), and finish. + pid_t frkrc = fork(); + if (frkrc == -1) + { + clog << _("ERROR: Fork failed. Terminating...") << endl; + return EXIT_FAILURE; + } + else if (frkrc == 0) + { + // Child process + if (s.build_as != "") + { + // Start running under an unprivileged user + rc = run_unprivileged(s.build_as, s.build_as_uid, s.build_as_gid, s.verbose); + if (rc != EXIT_SUCCESS) + return rc; + + if (s.verbose >= vt) + clog << _F("Child pid=%d, uid=%d, euid=%d, gid=%d, egid=%d\n", + getpid(), getuid(), geteuid(), getgid(), getegid()); + } + + for (set::iterator it = sessions.begin(); + rc == 0 && !pending_interrupts && it != sessions.end(); ++it) + { + systemtap_session& ss = **it; + if (ss.verbose > 1) + clog << _F("Session arch: %s release: %s", + ss.architecture.c_str(), ss.kernel_release.c_str()) + << endl + << _F("Build tree: \"%s\"", ss.kernel_build_tree.c_str()) + << endl; + +#if HAVE_NSS + // If requested, query server status. This is independent + // of other tasks. + nss_client_query_server_status (ss); + + // If requested, manage trust of servers. This is + // independent of other tasks. + nss_client_manage_server_trust (ss); +#endif + + // Run the passes only if a script has been specified or + // if we're dumping something. The requirement for a + // script has already been checked in + // systemtap_session::check_options. + if (ss.have_script || ss.dump_mode) + { + // Run passes 0-4 for each unique session, either + // locally or using a compile-server. + ss.init_try_server (); + if ((rc = passes_0_4 (ss))) + { + // Compilation failed. + // Try again using a server if appropriate. + if (ss.try_server ()) + rc = passes_0_4_again_with_server (ss); + } + if (rc || s.perpass_verbose[0] >= 1) + s.explain_auto_options (); + } + } + + // Run pass 5, if requested (part 1/2 (unprivileged)) + if (rc == 0 && s.have_script && s.last_pass >= 5 && ! pending_interrupts) + rc = pass_5_1 (s, targets); + if (s.verbose >= vt) + clog << _F("Child finished running, tmpdir is %s", s.tmpdir.c_str()) + << endl; + _exit(rc); + } + else + { + // Parent process + if (s.verbose >= vt) + clog << _F("Parent pid=%d, uid=%d, euid=%d, gid=%d, egid=%d\n", + getpid(), getuid(), geteuid(), getgid(), getegid()); + int wstatus; + (void)waitpid(frkrc, &wstatus, 0); + rc = WEXITSTATUS(wstatus); + } + + // Run pass 5, if requested (part 2/2 (privileged)) + if (s.verbose >= vt) + clog << _F("Parent about to execute staprun, tmpdir is %s", s.tmpdir.c_str()) + << endl; + if (rc == 0 && s.have_script && s.last_pass >= 5 && ! pending_interrupts) + rc = pass_5_2 (s, targets); + return rc; +} + int main (int argc, char * const argv []) { @@ -1562,10 +1751,6 @@ main (int argc, char * const argv []) rc = 1; } - // Discover and loop over each unique session created by the remote targets. - set sessions; - for (unsigned i = 0; i < targets.size(); ++i) - sessions.insert(targets[i]->get_session()); #if HAVE_LANGUAGE_SERVER_SUPPORT if(s.language_server_mode){ @@ -1584,52 +1769,10 @@ main (int argc, char * const argv []) } else { - for (set::iterator it = sessions.begin(); - rc == 0 && !pending_interrupts && it != sessions.end(); ++it) - { - systemtap_session& ss = **it; - if (ss.verbose > 1) - clog << _F("Session arch: %s release: %s", - ss.architecture.c_str(), ss.kernel_release.c_str()) - << endl - << _F("Build tree: \"%s\"", - ss.kernel_build_tree.c_str()) - << endl; - -#if HAVE_NSS - // If requested, query server status. This is independent - // of other tasks. - nss_client_query_server_status (ss); - - // If requested, manage trust of servers. This is - // independent of other tasks. - nss_client_manage_server_trust (ss); -#endif - - // Run the passes only if a script has been specified or - // if we're dumping something. The requirement for a - // script has already been checked in - // systemtap_session::check_options. - if (ss.have_script || ss.dump_mode) - { - // Run passes 0-4 for each unique session, either - // locally or using a compile-server. - ss.init_try_server (); - if ((rc = passes_0_4 (ss))) - { - // Compilation failed. - // Try again using a server if appropriate. - if (ss.try_server ()) - rc = passes_0_4_again_with_server (ss); - } - if (rc || s.perpass_verbose[0] >= 1) - s.explain_auto_options (); - } - } - - // Run pass 5, if requested - if (rc == 0 && s.have_script && s.last_pass >= 5 && ! pending_interrupts) - rc = pass_5 (s, targets); + if (s.build_as != "") + rc = passes_1_5_build_as(s, targets); + else + rc = passes_1_5(s, targets); } // Pass 6. Cleanup diff --git a/man/stap.1.in b/man/stap.1.in index 75bca6253..7fff76196 100644 --- a/man/stap.1.in +++ b/man/stap.1.in @@ -877,6 +877,13 @@ variables at the end of a stap session. Language server mode. Start a language server which will communicate via stdio. The language server will respect stap verbosity. +.TP +.B \-\-build\-as +Execute passes 1-4 under specified user to increase safety. This approach +may have limitations. Unprivileged users might not have access to kallsyms +or /proc. At the RPM install time, the stapunpriv user is created, so stap +can be invoked with --build-as=stapunpriv. + .SH ARGUMENTS Any additional arguments on the command line are passed to the script diff --git a/remote.cxx b/remote.cxx index bffeaa9c7..8aef22f50 100644 --- a/remote.cxx +++ b/remote.cxx @@ -25,6 +25,7 @@ extern "C" { #include #include #include +#include #include "buildrun.h" #include "remote.h" @@ -81,7 +82,35 @@ class direct : public remote { vector args; direct(systemtap_session& s): remote(s), child(0) {} - int start() + // Save the staprun commandline to a file, where the privileged + // parent process can find it and spawn staprun. + void put_args() + { + ofstream f(s->tmpdir + "/staprun_args_direct"); + if(f.fail()) + cerr << "ERROR: Failed writing staprun_args_direct." << endl; + for (vector::iterator t=args.begin(); t!=args.end(); ++t) + f << *t << endl; + f.close(); + } + + // Retreive saved staprun commandline from the file. + // Feed the args vector. + void get_args() + { + ifstream f(s->tmpdir + "/staprun_args_direct"); + if(f.fail()) + cerr << "ERROR: Failed reading staprun_args_direct" << endl; + string l; + while(getline(f, l)) + args.insert(args.end(), l); + f.close(); + } + + // Define prepare() and move the initial part of pass_5 (i.e. the + // preparation of the staprun commandline) into it. The staprun + // invocation stays in start() below. + int prepare() { args = make_run_command(*s); if (! staprun_r_arg.empty()) // PR13354 @@ -93,6 +122,17 @@ class direct : public remote { // leave args empty for bpf_runtime } + // Dump args to a file, where parent can read it. + put_args(); + return 0; + } + + int start() + { + // If args is an empty vector, read it from file. + if (args.size() == 0) + get_args(); + pid_t pid = stap_spawn (s->verbose, args); if (pid <= 0) return 1; @@ -132,12 +172,38 @@ class stapsh : public remote { string remote_version; size_t data_size; string target_stream; + vector args; enum { STAPSH_READY, // ready to receive next command STAPSH_DATA // currently printing data from a 'data' command } stream_state; + // Save the staprun commandline to a file, where the privileged + // parent process can find it and spawn staprun. + void put_args() + { + ofstream f(s->tmpdir + "/staprun_args_stapsh"); + if(f.fail()) + cerr << "ERROR: Failed writing staprun_args_stapsh." << endl; + for (vector::iterator t=args.begin(); t!=args.end(); ++t) + f << *t << endl; + f.close(); + } + + // Retreive saved staprun commandline from the file. + // Feed the args vector. + void get_args() + { + ifstream f(s->tmpdir + "/staprun_args_stapsh"); + if(f.fail()) + cerr << "ERROR: Failed reading staprun_args_stapsh" << endl; + string l; + while(getline(f, l)) + args.insert(args.end(), l); + f.close(); + } + virtual void prepare_poll(vector& fds) { if (fdout >= 0 && OUT) @@ -481,29 +547,36 @@ class stapsh : public remote { return rc; } - return rc; - } - - virtual int start() - { - // Send the staprun args - // NB: The remote is left to decide its own staprun path - ostringstream run("run", ios::out | ios::ate); - vector cmd = make_run_command(*s, ".", remote_version); + args = make_run_command(*s, ".", remote_version); // PR13354: identify our remote index/url if (strverscmp("1.7", remote_version.c_str()) <= 0 && // -r supported? ! staprun_r_arg.empty()) { if (s->runtime_mode == systemtap_session::dyninst_runtime) - cmd.insert(cmd.end(), { "_stp_dyninst_remote=" + staprun_r_arg}); + args.insert(args.end(), { "_stp_dyninst_remote=" + staprun_r_arg}); else if (s->runtime_mode == systemtap_session::kernel_runtime) - cmd.insert(cmd.end(), { "-r", staprun_r_arg }); + args.insert(args.end(), { "-r", staprun_r_arg }); // leave args empty for bpf_runtime } - for (unsigned i = 1; i < cmd.size(); ++i) - run << ' ' << qpencode(cmd[i]); + // Dump args to a file, where parent can read it. + put_args(); + + return rc; + } + + virtual int start() + { + // Send the staprun args + // NB: The remote is left to decide its own staprun path + ostringstream run("run", ios::out | ios::ate); + + if (args.size() == 0) + get_args(); + + for (unsigned i = 1; i < args.size(); ++i) + run << ' ' << qpencode(args[i]); run << '\n'; int rc = send_command(run.str()); @@ -1297,5 +1370,79 @@ remote::run(const vector& remotes) return ret; } +int +remote::run1(const vector& remotes) +{ + // NB: the first failure "wins" + int ret = 0, rc = 0; + + // Only prepare the staprun commandline, don't spawn staprun. + // Run under unprivileged user. + + for (unsigned i = 0; i < remotes.size() && !pending_interrupts; ++i) + { + remote *r = remotes[i]; + r->s->verbose = r->s->perpass_verbose[4]; + if (r->s->use_remote_prefix) + r->prefix = lex_cast(i) + ": "; + rc = r->prepare(); + if (rc) + return rc; + } + return ret; +} + +int +remote::run2(const vector& remotes) +{ + // NB: the first failure "wins" + int ret = 0, rc = 0; + + + // The staprun commandline is already prepared. + // Spawn staprun using it. + // Run under privileged user. + + + for (unsigned i = 0; i < remotes.size() && !pending_interrupts; ++i) + { + rc = remotes[i]->start(); + if (!ret) + ret = rc; + } + + // mask signals while we're preparing to poll + { + stap_sigmasker masked; + + // polling loop for remotes that have fds to watch + for (;;) + { + vector fds; + for (unsigned i = 0; i < remotes.size(); ++i) + remotes[i]->prepare_poll (fds); + if (fds.empty()) + break; + + rc = ppoll (&fds[0], fds.size(), NULL, &masked.old); + if (rc < 0 && errno != EINTR) + break; + + for (unsigned i = 0; i < remotes.size(); ++i) + remotes[i]->handle_poll (fds); + } + } + + for (unsigned i = 0; i < remotes.size(); ++i) + { + rc = remotes[i]->finish(); + if (!ret) + ret = rc; + } + + return ret; +} + + /* vim: set sw=2 ts=8 cino=>4,n-2,{2,^-2,t0,(0,u0,w1,M1 : */ diff --git a/remote.h b/remote.h index b0d8b9dde..f456f6ddb 100644 --- a/remote.h +++ b/remote.h @@ -37,6 +37,8 @@ class remote { public: static remote* create(systemtap_session& s, const std::string& uri, int idx); static int run(const std::vector& remotes); + static int run1(const std::vector& remotes); + static int run2(const std::vector& remotes); systemtap_session* get_session() { return s; } diff --git a/session.cxx b/session.cxx index 6f4cf9809..3a217de3e 100644 --- a/session.cxx +++ b/session.cxx @@ -42,6 +42,7 @@ extern "C" { #include #include #include +#include } #if HAVE_NSS @@ -194,6 +195,9 @@ systemtap_session::systemtap_session (): symbol_resolver = 0; lang_server = 0; language_server_mode = false; + build_as = ""; + build_as_uid = 0; + build_as_gid = 0; // PR12443: put compiled-in / -I paths in front, to be preferred during // tapset duplicate-file elimination @@ -387,6 +391,7 @@ systemtap_session::systemtap_session (const systemtap_session& other, timeout = other.timeout; language_server_mode = other.language_server_mode; lang_server = other.lang_server; + build_as = other.build_as; // don't bother copy typequery_memo include_path = other.include_path; @@ -757,6 +762,8 @@ systemtap_session::usage (int exitcode) " --language-server\n" " starts a systemtap language server\n" #endif + " --build-as=VALUE\n" + " Set user running passes 1-4\n" , compatible.c_str()) << endl ; @@ -1710,6 +1717,23 @@ systemtap_session::parse_cmdline (int argc, char * const argv []) language_server_mode = true; break; + case LONG_OPT_BUILD_AS: + if (optarg) + { + build_as = optarg; + struct passwd *pwd; + if ((pwd = getpwnam(build_as.c_str())) == NULL) + { + cerr << _F("ERROR: Failed converting userid \"%s\" to userid. Terminating.", build_as.c_str()) << endl; + return 1; + } + build_as_uid = pwd->pw_uid; + build_as_gid = pwd->pw_gid; + } + else + build_as = ""; + break; + case '?': // Invalid/unrecognized option given or argument required, but // not given. In both cases getopt_long() will have printed the @@ -2714,8 +2738,14 @@ systemtap_session::create_tmp_dir() //TRANSLATORS: we can't make the directory due to the error throw runtime_error(_F("cannot create temporary directory (\" %s \"): %s", tmpdirt.c_str(), e)); } - else - tmpdir = tmpdir_name; + //else + tmpdir = tmpdir_name; + if ((getuid() == 0) && (build_as != "")) + if(chown(tmpdirt.c_str(), build_as_uid, build_as_gid) != 0) + { + cout << "ERROR: Failed to chown. Terminating." << endl; + exit(1); + } } void diff --git a/session.h b/session.h index c01678085..9c54925c8 100644 --- a/session.h +++ b/session.h @@ -27,6 +27,7 @@ extern "C" { #include #include +#include } #include "privilege.h" @@ -492,6 +493,10 @@ public: bool language_server_mode; language_server* lang_server; + std::string build_as; + uid_t build_as_uid; + gid_t build_as_gid; + // NB: It is very important for all of the above (and below) fields // to be cleared in the systemtap_session ctor (session.cxx). diff --git a/systemtap.spec b/systemtap.spec index 44348cafc..e741b2096 100644 --- a/systemtap.spec +++ b/systemtap.spec @@ -92,7 +92,10 @@ \ g stapusr 156\ g stapsys 157\ -g stapdev 158 +g stapdev 158\ +g stapunpriv 159\ +u stapunpriv 159 "systemtap unprivileged user" /var/lib/stapunpriv /sbin/nologin\ +m stapunpriv stapunpriv %define _systemtap_server_preinstall \ # See systemd-sysusers(8) sysusers.d(5)\ @@ -842,6 +845,9 @@ echo '%_systemtap_runtime_preinstall' | systemd-sysusers --replace=%{_sysusersdi getent group stapusr >/dev/null || groupadd -f -g 156 -r stapusr getent group stapsys >/dev/null || groupadd -f -g 157 -r stapsys getent group stapdev >/dev/null || groupadd -f -g 158 -r stapdev +getent passwd stapunpriv >/dev/null || \ + useradd -c "Systemtap Unprivileged User" -u 159 -g stapunpriv -d %{_localstatedir}/lib/stapunpriv -r -s /sbin/nologin stapunpriv 2>/dev/null || \ + useradd -c "Systemtap Unprivileged User" -g stapunpriv -d %{_localstatedir}/lib/stapunpriv -r -s /sbin/nologin stapunpriv %endif exit 0 diff --git a/translate.cxx b/translate.cxx index bd3b6074f..9940534a6 100644 --- a/translate.cxx +++ b/translate.cxx @@ -7946,6 +7946,10 @@ static void dump_kallsyms(unwindsym_dump_context *c) prev = addr; } + // PR30321 apply privilege separation for passes 2/3/4, esp. if invoked as root + if ((getuid() != 0) && (size == 0)) + c->session.print_warning (_F("No kallsyms found. Your uid=%d.", getuid())); + c->output << "};\n"; c->output << "static struct _stp_section _stp_module_" << stpmod_idx << "_sections[] = {\n"; c->output << "{\n" diff --git a/util.cxx b/util.cxx index 7a1081654..f90c74fce 100644 --- a/util.cxx +++ b/util.cxx @@ -46,6 +46,7 @@ extern "C" { #include #include #include +#include #ifdef HAVE_LIBDEBUGINFOD #include @@ -191,7 +192,9 @@ copy_file(const string& src, const string& dest, bool verbose) error: cerr << _F("Copy failed (\"%s\" to \"%s\"): %s", src.c_str(), - dest.c_str(), strerror(errno)) << endl; + dest.c_str(), strerror(errno)) + << ((getuid() != 0) ? + _F(". Your uid=%d.", getuid()) : "") << endl; return false; } @@ -224,6 +227,7 @@ create_dir(const char *dir, int mode) for (unsigned ix = 0; ix < limit; ++ix) { path += components[ix] + '/'; + umask(0); mode=0777; if (mkdir(path.c_str (), mode) != 0 && errno != EEXIST) return 1; } @@ -1505,7 +1509,10 @@ is_valid_pid (pid_t pid, string& err_msg) } else if (kill(pid, 0) == -1) { - err_msg = _F("cannot probe pid %d: %s", pid, strerror(errno)); + if (getuid() != 0) + err_msg = _F("cannot probe pid %d: %s. Your uid=%d.", pid, strerror(errno), getuid()); + else + err_msg = _F("cannot probe pid %d: %s", pid, strerror(errno)); return false; } return true; @@ -1895,4 +1902,31 @@ get_distro_info(vector &info) return false; } +// PR30321: Privilege separation +int +run_unprivileged(const std::string& build_as, uid_t build_as_uid, gid_t build_as_gid, int verbosity) +{ + if (build_as == "") + return EXIT_SUCCESS; + + int ret; + ret = setregid(build_as_gid, build_as_gid); + if (ret != 0) { + clog << "ERROR: setregid() failed" << endl; + clog << strerror (errno) << endl; + return EXIT_FAILURE; + } + ret = setreuid(build_as_uid, build_as_uid); + if (ret != 0) { + clog << "ERROR: setreuid() failed" << endl; + clog << strerror (errno) << endl; + return EXIT_FAILURE; + } + if (verbosity > 2) + cout << _F("Running passes 1-4 using user \"%s\" userid \"%d\" group id \"%d\"", + build_as.c_str(), build_as_uid, build_as_gid) << "<<<" << endl; + return EXIT_SUCCESS; +} + + /* vim: set sw=2 ts=8 cino=>4,n-2,{2,^-2,t0,(0,u0,w1,M1 : */ diff --git a/util.h b/util.h index c708aa669..8ec61b9ce 100644 --- a/util.h +++ b/util.h @@ -135,6 +135,7 @@ std::string autosprintf(const char* format, ...) __attribute__ ((format (printf, const std::set& localization_variables(); std::string get_self_path(); bool is_valid_pid (pid_t pid, std::string& err_msg); +int run_unprivileged(const std::string& build_as, uid_t build_as_uid, gid_t build_as_gid, int verbosity); // stringification generics -- 2.43.5