diff options
author | Dave Brolley <brolley@redhat.com> | 2010-01-15 10:57:08 -0500 |
---|---|---|
committer | Dave Brolley <brolley@redhat.com> | 2010-01-15 10:57:08 -0500 |
commit | ceeefadd6874e3d315669a87ec0d05a0ce1f7094 (patch) | |
tree | fad469e3f31212afee4d1fb6aa6833b48e1724b7 | |
parent | 3f78f0208e1bfe8061d1898418882b5e2756f8a2 (diff) | |
parent | 86f99ad8206574dc6400d48563db58341cb50f52 (diff) | |
download | systemtap-steved-ceeefadd6874e3d315669a87ec0d05a0ce1f7094.tar.gz systemtap-steved-ceeefadd6874e3d315669a87ec0d05a0ce1f7094.tar.xz systemtap-steved-ceeefadd6874e3d315669a87ec0d05a0ce1f7094.zip |
Merge branch 'master' of ssh://sources.redhat.com/git/systemtap
Conflicts:
stap-client
-rw-r--r-- | Makefile.am | 2 | ||||
-rw-r--r-- | Makefile.in | 2 | ||||
-rw-r--r-- | main.cxx | 11 | ||||
-rw-r--r-- | runtime/stack.c | 3 | ||||
-rw-r--r-- | runtime/staprun/modverify.c | 10 | ||||
-rw-r--r-- | runtime/uprobes-common.c | 15 | ||||
-rw-r--r-- | runtime/uprobes-common.h | 1 | ||||
-rwxr-xr-x | stap-client | 26 | ||||
-rw-r--r-- | stap-server-connect.c | 499 | ||||
-rwxr-xr-x | stap-server-request | 512 | ||||
-rwxr-xr-x | stap-serverd | 2 | ||||
-rw-r--r-- | tapsets.cxx | 1 | ||||
-rw-r--r-- | testsuite/systemtap.server/client_args.exp | 4 |
13 files changed, 456 insertions, 632 deletions
diff --git a/Makefile.am b/Makefile.am index 3c2ddbe1..a7603fb2 100644 --- a/Makefile.am +++ b/Makefile.am @@ -43,7 +43,7 @@ if BUILD_SERVER man_MANS += stap-client.8 stap-server.8 stap-authorize-server-cert.8 pkglibexec_PROGRAMS += stap-client-connect stap-server-connect bin_SCRIPTS += stap-client stap-server stap-authorize-server-cert -pkglibexec_SCRIPTS += stap-serverd stap-server-request stap-find-servers \ +pkglibexec_SCRIPTS += stap-serverd stap-find-servers \ stap-start-server stap-find-or-start-server stap-stop-server endif endif diff --git a/Makefile.in b/Makefile.in index 016162ab..e655f8a2 100644 --- a/Makefile.in +++ b/Makefile.in @@ -47,7 +47,7 @@ pkglibexec_PROGRAMS = stapio$(EXEEXT) $(am__EXEEXT_2) $(am__EXEEXT_3) @BUILD_SERVER_TRUE@@BUILD_TRANSLATOR_TRUE@@HAVE_NSS_TRUE@am__append_8 = stap-client.8 stap-server.8 stap-authorize-server-cert.8 @BUILD_SERVER_TRUE@@BUILD_TRANSLATOR_TRUE@@HAVE_NSS_TRUE@am__append_9 = stap-client-connect stap-server-connect @BUILD_SERVER_TRUE@@BUILD_TRANSLATOR_TRUE@@HAVE_NSS_TRUE@am__append_10 = stap-client stap-server stap-authorize-server-cert -@BUILD_SERVER_TRUE@@BUILD_TRANSLATOR_TRUE@@HAVE_NSS_TRUE@am__append_11 = stap-serverd stap-server-request stap-find-servers \ +@BUILD_SERVER_TRUE@@BUILD_TRANSLATOR_TRUE@@HAVE_NSS_TRUE@am__append_11 = stap-serverd stap-find-servers \ @BUILD_SERVER_TRUE@@BUILD_TRANSLATOR_TRUE@@HAVE_NSS_TRUE@ stap-start-server stap-find-or-start-server stap-stop-server @BUILD_TRANSLATOR_FALSE@stap_DEPENDENCIES = @@ -1,5 +1,5 @@ // systemtap translator/driver -// Copyright (C) 2005-2009 Red Hat Inc. +// Copyright (C) 2005-2010 Red Hat Inc. // Copyright (C) 2005 IBM Corp. // Copyright (C) 2006 Intel Corporation. // @@ -877,6 +877,8 @@ main (int argc, char * const argv []) break; case LONG_OPT_UNPRIVILEGED: s.unprivileged = true; + /* NB: for server security, it is essential that once this flag is + set, no future flag be able to unset it. */ break; case LONG_OPT_CLIENT_OPTIONS: client_options = true; @@ -895,6 +897,10 @@ main (int argc, char * const argv []) // Check for options conflicts. + if (client_options && s.last_pass > 4) + { + s.last_pass = 4; /* Quietly downgrade. Server passed through -p5 naively. */ + } if (client_options && s.unprivileged && ! client_options_disallowed.empty ()) { cerr << "You can't specify " << client_options_disallowed << " when --unprivileged is specified." << endl; @@ -921,7 +927,6 @@ main (int argc, char * const argv []) if (s.kernel_symtab_path == PATH_TBD) s.kernel_symtab_path = string("/boot/System.map-") + s.kernel_release; } - // Warn in case the target kernel release doesn't match the running one. if (s.last_pass > 4 && (string(buf.release) != s.kernel_release || @@ -1369,6 +1374,8 @@ pass_5: else { if (s.keep_tmpdir) + // NB: the format of this message needs to match the expectations + // of stap-server-connect.c. clog << "Keeping temporary directory \"" << s.tmpdir << "\"" << endl; else { diff --git a/runtime/stack.c b/runtime/stack.c index 7dfeb76a..0e537a8e 100644 --- a/runtime/stack.c +++ b/runtime/stack.c @@ -212,7 +212,8 @@ void _stp_stack_print_tsk(struct task_struct *tsk, int verbose, int levels) for (i = 0; i < maxLevels; ++i) { if (backtrace[i] == 0 || backtrace[i] == ULONG_MAX) break; - _stp_printf("%lx ", backtrace[i]); + _stp_symbol_print(backtrace[i]); + _stp_print_char('\n'); } #endif } diff --git a/runtime/staprun/modverify.c b/runtime/staprun/modverify.c index a17bb2ec..5d442393 100644 --- a/runtime/staprun/modverify.c +++ b/runtime/staprun/modverify.c @@ -272,12 +272,18 @@ int verify_module (const char *signatureName, const char* module_name, /* Verify the permissions of the certificate database and its files. */ if (! check_cert_db_permissions (dbdir)) - return MODULE_UNTRUSTED; + { + if (verbose>1) fprintf (stderr, "Certificate db %s permissions too loose\n", dbdir); + return MODULE_UNTRUSTED; + } /* Get the size of the signature file. */ prStatus = PR_GetFileInfo (signatureName, &info); if (prStatus != PR_SUCCESS || info.type != PR_FILE_FILE || info.size < 0) - return MODULE_UNTRUSTED; /* Not signed */ + { + if (verbose>1) fprintf (stderr, "Signature file %s not found\n", signatureName); + return MODULE_UNTRUSTED; /* Not signed */ + } /* Open the signature file. */ local_file_fd = PR_Open (signatureName, PR_RDONLY, 0); diff --git a/runtime/uprobes-common.c b/runtime/uprobes-common.c index b0273ba4..58e3a05f 100644 --- a/runtime/uprobes-common.c +++ b/runtime/uprobes-common.c @@ -286,4 +286,19 @@ static int stap_uprobe_munmap_found (struct stap_task_finder_target *tgt, struct return stap_uprobe_change_minus (tsk, addr, length, stf); } +/* The task_finder_callback we use for ET_DYN targets. + This just forces an unmap of everything as the process exits. + (PR11151) */ +static int stap_uprobe_process_munmap (struct stap_task_finder_target *tgt, struct task_struct *tsk, int register_p, int process_p) { + const struct stap_uprobe_tf *stf = container_of(tgt, struct stap_uprobe_tf, finder); + if (! process_p) return 0; /* ignore threads */ + #ifdef DEBUG_TASK_FINDER_VMA + _stp_dbug (__FUNCTION__,__LINE__, "%cproc pid %d stf %p %p path %s\n", register_p?'+':'-', tsk->tgid, tgt, stf, stf->pathname); + #endif + /* Covering 0->TASK_SIZE means "unmap everything" */ + if (!register_p) + return stap_uprobe_change_minus (tsk, 0, TASK_SIZE, stf); + return 0; +} + #endif /* _UPROBE_COMMON_C_ */ diff --git a/runtime/uprobes-common.h b/runtime/uprobes-common.h index 68741f4d..990b473a 100644 --- a/runtime/uprobes-common.h +++ b/runtime/uprobes-common.h @@ -33,5 +33,6 @@ struct stap_uprobe_spec { static int stap_uprobe_process_found (struct stap_task_finder_target *tgt, struct task_struct *tsk, int register_p, int process_p); static int stap_uprobe_mmap_found (struct stap_task_finder_target *tgt, struct task_struct *tsk, char *path, unsigned long addr, unsigned long length, unsigned long offset, unsigned long vm_flags); static int stap_uprobe_munmap_found (struct stap_task_finder_target *tgt, struct task_struct *tsk, unsigned long addr, unsigned long length); +static int stap_uprobe_process_munmap (struct stap_task_finder_target *tgt, struct task_struct *tsk, int register_p, int process_p); #endif /* _UPROBE_COMMON_H_ */ diff --git a/stap-client b/stap-client index 4cb44b18..64452159 100755 --- a/stap-client +++ b/stap-client @@ -260,7 +260,7 @@ function parse_options { else local_name="-" fi - echo "script/$local_name" > "$tmpdir_client/argv$script_file_argc" + echo -n "script/$local_name" > "$tmpdir_client/argv$script_file_argc" fi # Processing based on final options settings @@ -500,17 +500,6 @@ function unpack_response { unzip -d $tmpdir_server $zip_server > /dev/null || \ fatal "Cannot unpack server response, $zip_server" - # Check the contents of the expanded directory. It should contain a - # single directory whose name matches $stap_tmpdir_prefix_server.?????? - local num_files=`ls $tmpdir_server | wc -l` - test $num_files = 1 || \ - fatal "Wrong number of files in server's temp directory" - test -d $tmpdir_server/$stap_tmpdir_prefix_server.?????? || \ - fatal "`ls $tmpdir_server` does not match the expected name or is not a directory" - # Move the contents of the directory down one level. - mv $tmpdir_server/$stap_tmpdir_prefix_server.??????/* $tmpdir_server - rm -fr $tmpdir_server/$stap_tmpdir_prefix_server.?????? - # Check the contents of the directory. It should contain: # 1) a file called stdout # 2) a file called stderr @@ -526,7 +515,8 @@ function unpack_response { test -f $tmpdir_server/rc || \ fatal "`pwd`/$tmpdir_server/rc does not exist or is not a regular file" - # See if there is a systemtap temp directory + # See if there is a systemtap temp directory. There should be at least an empty one. + # ls -l $tmpdir_server tmpdir_stap=`cd $tmpdir_server && ls | grep stap......\$ 2>/dev/null` if test "X$tmpdir_stap" != "X"; then test -d $tmpdir_server/$tmpdir_stap || \ @@ -549,6 +539,16 @@ function unpack_response { test $EUID = 0 && chown $EUID:$EUID $tmpdir_stap fi fi + + if test $keep_temps = 0; then + # Remove the output line due to the synthetic server-side -k + sed -i "/^Keeping temporary directory.*/ d" $tmpdir_server/stderr + fi + + if test $p_phase = 5; then + # Remove the output line due to the synthetic server-side -p4 + sed -i "/^.*\.ko$/ d" $tmpdir_server/stdout + fi } # function: find_and_connect_to_server diff --git a/stap-server-connect.c b/stap-server-connect.c index cf0e5a65..bbf5ade7 100644 --- a/stap-server-connect.c +++ b/stap-server-connect.c @@ -1,9 +1,9 @@ /* SSL server program listens on a port, accepts client connection, reads - the data into a temporary file, calls the systemtap server script and - then transmits the resulting fileback to the client. + the data into a temporary file, calls the systemtap translator and + then transmits the resulting file back to the client. - Copyright (C) 2008, 2009 Red Hat Inc. + Copyright (C) 2008-2010 Red Hat Inc. 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 @@ -23,7 +23,16 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <unistd.h> #include <errno.h> +#include <spawn.h> +#include <fcntl.h> +#include <glob.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <wordexp.h> +#include <sys/param.h> #include <ssl.h> #include <nspr.h> @@ -31,19 +40,21 @@ #include <nss.h> #include <pk11func.h> +#include "config.h" #include "nsscommon.h" -#define READ_BUFFER_SIZE (60 * 1024) /* Global variables */ static char *password = NULL; static CERTCertificate *cert = NULL; static SECKEYPrivateKey *privKey = NULL; static char *dbdir = NULL; -static char requestFileName[] = "/tmp/stap.server.client.zip.XXXXXX"; -static char responseDirName[] = "/tmp/stap.server.XXXXXX"; -static char responseZipName[] = "/tmp/stap.server.XXXXXX.zip.XXXXXX"; -static const char *stapOptions = ""; +static const char *stapOptions = ""; + + +static PRStatus spawn_and_wait (char **argv, + const char* fd0, const char* fd1, const char* fd2, const char *pwd); + static void Usage(const char *progName) @@ -72,24 +83,27 @@ exitErr(char *function) exit(1); } + + + /* Function: readDataFromSocket() * * Purpose: Read data from the socket into a temporary file. * */ -static SECStatus -readDataFromSocket(PRFileDesc *sslSocket) +static SECStatus readDataFromSocket(PRFileDesc *sslSocket, const char *requestFileName) { PRFileDesc *local_file_fd; PRFileInfo info; PRInt32 numBytesRead; PRInt32 numBytesWritten; PRInt32 totalBytes; +#define READ_BUFFER_SIZE 4096 char buffer[READ_BUFFER_SIZE]; /* Open the output file. */ local_file_fd = PR_Open(requestFileName, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, - PR_IRUSR | PR_IWUSR | PR_IRGRP | PR_IWGRP | PR_IROTH); + PR_IRUSR | PR_IWUSR); if (local_file_fd == NULL) { fprintf (stderr, "could not open output file %s\n", requestFileName); @@ -97,6 +111,7 @@ readDataFromSocket(PRFileDesc *sslSocket) } /* Read the number of bytes to be received. */ + /* XXX: impose a limit to prevent disk space consumption DoS */ numBytesRead = PR_Read(sslSocket, & info.size, sizeof (info.size)); if (numBytesRead == 0) /* EOF */ { @@ -123,10 +138,11 @@ readDataFromSocket(PRFileDesc *sslSocket) /* Write to stdout */ numBytesWritten = PR_Write(local_file_fd, buffer, numBytesRead); - if (numBytesWritten < 0) - fprintf (stderr, "could not write to output file %s\n", requestFileName); - if (numBytesWritten != numBytesRead) - fprintf (stderr, "could not write to output file %s\n", requestFileName); + if (numBytesWritten < 0 || (numBytesWritten != numBytesRead)) + { + fprintf (stderr, "could not write to output file %s\n", requestFileName); + break; + } #if DEBUG fprintf(stderr, "***** Connection read %d bytes.\n", numBytesRead); #if 0 @@ -317,27 +333,15 @@ authenticateSocket(PRFileDesc *sslSocket, PRBool requireCert) * */ static SECStatus -writeDataToSocket(PRFileDesc *sslSocket) +writeDataToSocket(PRFileDesc *sslSocket, const char *responseFileName) { int numBytes; PRFileDesc *local_file_fd; - PRFileInfo info; - PRStatus prStatus; - /* Try to open the local file named. - * If successful, then write it to the client. - */ - prStatus = PR_GetFileInfo(responseZipName, &info); - if (prStatus != PR_SUCCESS || info.type != PR_FILE_FILE || info.size < 0) - { - fprintf (stderr, "Input file %s not found\n", responseZipName); - return SECFailure; - } - - local_file_fd = PR_Open(responseZipName, PR_RDONLY, 0); + local_file_fd = PR_Open(responseFileName, PR_RDONLY, 0); if (local_file_fd == NULL) { - fprintf (stderr, "Could not open input file %s\n", responseZipName); + fprintf (stderr, "Could not open input file %s\n", responseFileName); return SECFailure; } @@ -357,7 +361,7 @@ writeDataToSocket(PRFileDesc *sslSocket) #if DEBUG /* Transmitted bytes successfully. */ fprintf(stderr, "PR_TransmitFile wrote %d bytes from %s\n", - numBytes, responseZipName); + numBytes, responseFileName); #endif PR_Close(local_file_fd); @@ -365,10 +369,303 @@ writeDataToSocket(PRFileDesc *sslSocket) return SECSuccess; } + +/* Run the translator on the data in the request directory, and produce output + in the given output directory. */ +static void handleRequest (const char* requestDirName, const char* responseDirName) +{ + char stapstdout[PATH_MAX]; + char stapstderr[PATH_MAX]; + char staprc[PATH_MAX]; +#define MAXSTAPARGC 1000 /* sorry, too lazy to dynamically allocate */ + char* stapargv[MAXSTAPARGC]; + int stapargc=0; + int rc; + wordexp_t words; + int i; + FILE* f; + int unprivileged = 0; + int stapargv_freestart = 0; + + stapargv[stapargc++]= STAP_PREFIX "/bin/stap"; + + /* Transcribe stapOptions. We use plain wordexp(3), since these + options are coming from the local trusted user, so malicious + content is not a concern. */ + + rc = wordexp (stapOptions, & words, WRDE_NOCMD|WRDE_UNDEF); + if (rc) + { + errWarn("cannot parse -s stap options"); + return; + } + if (words.we_wordc+10 >= MAXSTAPARGC) /* 10: padding for literal entries */ + { + errWarn("too many -s options; MAXSTAPARGC"); + return; + } + + for (i=0; i<words.we_wordc; i++) + stapargv[stapargc++] = words.we_wordv[i]; + + stapargv[stapargc++] = "-k"; /* Need to keep temp files in order to package them up again. */ + + /* Process the saved command line arguments. Avoid quoting/unquoting errors by + transcribing literally. */ + stapargv[stapargc++] = "--client-options"; + stapargv_freestart = stapargc; + + for (i=1 ; ; i++) + { + char stapargfile[PATH_MAX]; + FILE* argfile; + struct stat st; + char *arg; + + if (stapargc >= MAXSTAPARGC) + { + errWarn("too many stap options; MAXSTAPARGC"); + return; + } + + snprintf (stapargfile, PATH_MAX, "%s/argv%d", requestDirName, i); + + rc = stat(stapargfile, & st); + if (rc) break; + + arg = malloc (st.st_size+1); + if (!arg) + { + errWarn("stap arg malloc"); + return; + } + + argfile = fopen(stapargfile, "r"); + if (! argfile) + { + errWarn("stap arg fopen"); + return; + } + + rc = fread(arg, 1, st.st_size, argfile); + if (rc != st.st_size) + { + errWarn("stap arg fread"); + return; + } + + arg[st.st_size] = '\0'; + stapargv[stapargc++] = arg; /* freed later */ + fclose (argfile); + } + + snprintf (stapstdout, PATH_MAX, "%s/stdout", responseDirName); + snprintf (stapstderr, PATH_MAX, "%s/stderr", responseDirName); + + stapargv[stapargc] = NULL; /* spawn_and_wait expects NULL termination */ + + /* Check for the unprivileged flag; we need this so that we can decide to sign the module. */ + for (i=0; i<stapargc; i++) + if (strcmp (stapargv[i], "--unprivileged") == 0) + unprivileged=1; + /* NB: but it's not that easy! What if an attacker passes + --unprivileged as some sort of argument-parameter, so that the + translator does not interpret it as an --unprivileged mode flag, + but something else? Then it could generate unrestrained modules, + but silly we might still sign it, and let the attacker get away + with murder. And yet we don't want to fully getopt-parse the + args here for duplication of effort. + + So let's do a hack: forcefully add --unprivileged to stapargv[] + near the front in this case, something which a later option + cannot undo. */ + if (unprivileged) + { + if (stapargc+1 >= MAXSTAPARGC) + { + errWarn("too many stap options; MAXSTAPARGC"); + return; + } + + /* Shift all stapargv[] entries up one, including the NULL. */ + for (i=stapargc; i>=1; i--) + stapargv[i+1]=stapargv[i]; + stapargv_freestart ++; /* adjust for shift */ + + stapargv[1]="--unprivileged"; /* better not be resettable by later option */ + } + + /* All ready, let's run the translator! */ + rc = spawn_and_wait (stapargv, "/dev/null", stapstdout, stapstderr, requestDirName); + + /* Save the RC */ + snprintf (staprc, PATH_MAX, "%s/rc", responseDirName); + f = fopen(staprc, "w"); + if (f) + { + /* best effort basis */ + fprintf(f, "%d", rc); + fclose(f); + } + + /* Parse output to extract the -k-saved temprary directory. + XXX: bletch. */ + f = fopen(stapstderr, "r"); + if (!f) + { + errWarn("stap stderr open"); + return; + } + + while (1) + { + char line[PATH_MAX]; + char *l = fgets(line, PATH_MAX, f); /* NB: normally includes \n at end */ + if (!l) break; + char key[]="Keeping temporary directory \""; + + /* Look for line from main.cxx: s.keep_tmpdir */ + if (strncmp(l, key, strlen(key)) == 0 && + l[strlen(l)-2] == '"') /* "\n */ + { + /* Move this directory under responseDirName. We don't have to + preserve the exact stapXXXXXX suffix part, since stap-client + will accept anything ("stap......" regexp), and rewrite it + to a client-local string. + + We don't just symlink because then we'd have to + remember to delete it later anyhow. */ + char *mvargv[10]; + char *orig_staptmpdir = & l[strlen(key)]; + char new_staptmpdir[PATH_MAX]; + + orig_staptmpdir[strlen(orig_staptmpdir)-2] = '\0'; /* Kill the closing "\n */ + snprintf(new_staptmpdir, PATH_MAX, "%s/stap000000", responseDirName); + mvargv[0]="mv"; + mvargv[1]=orig_staptmpdir; + mvargv[2]=new_staptmpdir; + mvargv[3]=NULL; + rc = spawn_and_wait (mvargv, NULL, NULL, NULL, NULL); + if (rc != PR_SUCCESS) + errWarn("stap tmpdir move"); + + /* In unprivileged mode, if we have a module built, we need to + sign the sucker. */ + if (unprivileged) + { + glob_t globber; + char pattern[PATH_MAX]; + snprintf (pattern,PATH_MAX,"%s/*.ko", new_staptmpdir); + rc = glob (pattern, GLOB_ERR, NULL, &globber); + if (rc) + errWarn("stap tmpdir .ko glob"); + else if (globber.gl_pathc != 1) + errWarn("stap tmpdir too many .ko globs"); + else + { + char *signargv [10]; + signargv[0] = STAP_PREFIX "/libexec/systemtap/stap-sign-module"; + signargv[1] = globber.gl_pathv[0]; + signargv[2] = dbdir; + signargv[3] = NULL; + rc = spawn_and_wait (signargv, NULL, NULL, NULL, NULL); + if (rc != PR_SUCCESS) + errWarn("stap-sign-module"); + } + } + } + + /* XXX: What about uprobes.ko? */ + } + + /* Free up all the arg string copies. Note that the first few were alloc'd + by wordexp(), which wordfree() frees; others were hand-set to literal strings. */ + for (i= stapargv_freestart; i<stapargc; i++) + free (stapargv[i]); + wordfree (& words); + + /* Sorry about the inconvenience. C string/file processing is such a pleasure. */ +} + + +/* A frontend for posix_spawnp that waits for the child process and + returns overall success or failure. */ +static PRStatus spawn_and_wait (char ** argv, + const char* fd0, const char* fd1, const char* fd2, const char *pwd) +{ + pid_t pid; + int rc; + int status; + extern char** environ; + posix_spawn_file_actions_t actions; + int dotfd = -1; + +#define CHECKRC(msg) do { if (rc) { errWarn(msg); return PR_FAILURE; } } while (0) + + rc = posix_spawn_file_actions_init (& actions); + CHECKRC ("spawn file actions ctor"); + if (fd0) { + rc = posix_spawn_file_actions_addopen(& actions, 0, fd0, O_RDONLY, 0600); + CHECKRC ("spawn file actions fd0"); + } + if (fd1) { + rc = posix_spawn_file_actions_addopen(& actions, 1, fd1, O_WRONLY|O_CREAT, 0600); + CHECKRC ("spawn file actions fd1"); + } + if (fd2) { + rc = posix_spawn_file_actions_addopen(& actions, 2, fd2, O_WRONLY|O_CREAT, 0600); + CHECKRC ("spawn file actions fd2"); + } + + /* change temporarily to a directory if requested */ + if (pwd) + { + dotfd = open (".", O_RDONLY); + if (dotfd < 0) + { + errWarn ("spawn getcwd"); + return PR_FAILURE; + } + + rc = chdir (pwd); + CHECKRC ("spawn chdir"); + } + + rc = posix_spawnp (& pid, argv[0], & actions, NULL, argv, environ); + /* NB: don't react to rc!=0 right away; need to chdir back first. */ + + if (pwd && dotfd >= 0) + { + int subrc; + subrc = fchdir (dotfd); + subrc |= close (dotfd); + if (subrc) + errWarn("spawn unchdir"); + } + + CHECKRC ("spawn"); + + rc = waitpid (pid, &status, 0); + if ((rc!=pid) || !WIFEXITED(status)) + { + errWarn ("waitpid"); + return PR_FAILURE; + } + + rc = posix_spawn_file_actions_destroy (&actions); + CHECKRC ("spawn file actions dtor"); + + return WEXITSTATUS(status) ? PR_FAILURE : PR_SUCCESS; +#undef CHECKRC +} + + + /* Function: int handle_connection() * - * Purpose: Handle a connection to a socket. - * + * Purpose: Handle a connection to a socket. Copy in request zip + * file, process it, copy out response. Temporary directories are + * created & destroyed here. */ static SECStatus handle_connection(PRFileDesc *tcpSocket) @@ -377,11 +674,16 @@ handle_connection(PRFileDesc *tcpSocket) SECStatus secStatus = SECFailure; PRStatus prStatus; PRSocketOptionData socketOption; - PRFileInfo info; - char *cmdline; - char *stap_server_prefix; int rc; char *rc1; + char tmpdir[PATH_MAX]; + char requestFileName[PATH_MAX]; + char requestDirName[PATH_MAX]; + char responseDirName[PATH_MAX]; + char responseFileName[PATH_MAX]; + char *argv[10]; /* we use fewer than these in all the posix_spawn's below. */ + + tmpdir[0]='\0'; /* prevent cleanup-time /bin/rm of uninitialized directory */ /* Make sure the socket is blocking. */ socketOption.option = PR_SockOpt_Nonblocking; @@ -410,50 +712,49 @@ handle_connection(PRFileDesc *tcpSocket) goto cleanup; } - /* Create a temporary files and directories. */ - memcpy (requestFileName + sizeof (requestFileName) - 1 - 6, "XXXXXX", 6); - rc = mkstemp(requestFileName); - if (rc == -1) + snprintf(tmpdir, PATH_MAX, "%s/stap-server.XXXXXX", getenv("TMPDIR") ?: "/tmp"); + rc1 = mkdtemp(tmpdir); + if (! rc1) { - fprintf (stderr, "Could not create temporary file %s\n", requestFileName); + fprintf (stderr, "Could not create temporary directory %s\n", tmpdir); perror (""); secStatus = SECFailure; + tmpdir[0]=0; /* prevent /bin/rm */ goto cleanup; } - memcpy (responseDirName + sizeof (responseDirName) - 1 - 6, "XXXXXX", 6); - rc1 = mkdtemp(responseDirName); - if (! rc1) + /* Create a temporary files names and directories. */ + snprintf (requestFileName, PATH_MAX, "%s/request.zip", tmpdir); + + snprintf (requestDirName, PATH_MAX, "%s/request", tmpdir); + rc = mkdir(requestDirName, 0700); + if (rc) { - fprintf (stderr, "Could not create temporary directory %s\n", responseDirName); + fprintf (stderr, "Could not create temporary directory %s\n", requestDirName); perror (""); secStatus = SECFailure; goto cleanup; } - memcpy (responseZipName, responseDirName, sizeof (responseDirName) - 1); - memcpy (responseZipName + sizeof (responseZipName) - 1 - 6, "XXXXXX", 6); - rc = mkstemp(responseZipName); - if (rc == -1) + snprintf (responseDirName, PATH_MAX, "%s/response", tmpdir); + rc = mkdir(responseDirName, 0700); + if (rc) { - fprintf (stderr, "Could not create temporary file %s\n", responseZipName); + fprintf (stderr, "Could not create temporary directory %s\n", responseDirName); perror (""); secStatus = SECFailure; - - /* Remove this so that the other temp files will get removed in cleanup. */ - prStatus = PR_RmDir (responseDirName); - if (prStatus != PR_SUCCESS) - errWarn ("PR_RmDir"); goto cleanup; } + snprintf (responseFileName, PATH_MAX, "%s/response.zip", tmpdir); + /* Read data from the socket. * If the user is requesting/requiring authentication, authenticate * the socket. */ #if DEBUG fprintf(stdout, "\nReading data from socket...\n\n"); #endif - secStatus = readDataFromSocket(sslSocket); + secStatus = readDataFromSocket(sslSocket, requestFileName); if (secStatus != SECSuccess) goto cleanup; @@ -467,32 +768,42 @@ handle_connection(PRFileDesc *tcpSocket) } #endif - /* Call the stap-server-request script. */ - stap_server_prefix = getenv("SYSTEMTAP_SERVER_SCRIPTS") ?: PKGLIBDIR; - cmdline = PORT_Alloc(strlen (stap_server_prefix) + sizeof ("/stap-server-request") + 1 + - sizeof (requestFileName) + 1 + - sizeof (responseDirName) + 1 + - sizeof (responseZipName) + 1 + - strlen (dbdir) + 1 + - 1 + strlen (stapOptions) + 1 + - 1); - if (! cmdline) { - errWarn ("PORT_Alloc"); - secStatus = SECFailure; - goto cleanup; - } - - sprintf (cmdline, "%s/stap-server-request %s %s %s %s '%s'", stap_server_prefix, - requestFileName, responseDirName, responseZipName, dbdir, - stapOptions); - rc = system (cmdline); - - PR_Free (cmdline); + /* Unzip the request. */ + argv[0]="unzip"; + argv[1]="-d"; + argv[2]=requestDirName; + argv[3]=requestFileName; + rc = spawn_and_wait(argv, NULL, NULL, NULL, NULL); + if (rc != PR_SUCCESS) + { + errWarn ("request unzip"); + secStatus = SECFailure; + goto cleanup; + } + /* Handle the request zip file. An error therein should still result + in a response zip file (containing stderr etc.) so we don't have to + have a result code here. */ + handleRequest(requestDirName, responseDirName); + + /* Zip the response. */ + argv[0]="zip"; + argv[1]="-r"; + argv[2]=responseFileName; + argv[3]="."; + argv[4]=NULL; + rc = spawn_and_wait(argv, NULL, NULL, NULL, responseDirName); + if (rc != PR_SUCCESS) + { + errWarn ("response zip"); + secStatus = SECFailure; + goto cleanup; + } + #if DEBUG fprintf(stdout, "\nWriting data to socket...\n\n"); #endif - secStatus = writeDataToSocket(sslSocket); + secStatus = writeDataToSocket(sslSocket, responseFileName); cleanup: /* Close down the socket. */ @@ -500,17 +811,16 @@ cleanup: if (prStatus != PR_SUCCESS) errWarn("PR_Close"); - /* Attempt to remove temporary files, unless the temporary directory was - not deleted by the server script. */ - prStatus = PR_GetFileInfo(responseDirName, &info); - if (prStatus != PR_SUCCESS) + if (tmpdir[0]) { - prStatus = PR_Delete (requestFileName); - if (prStatus != PR_SUCCESS) - errWarn ("PR_Delete"); - prStatus = PR_Delete (responseZipName); - if (prStatus != PR_SUCCESS) - errWarn ("PR_Delete"); + /* Remove the whole tmpdir and all that lies beneath. */ + argv[0]="rm"; + argv[1]="-r"; + argv[2]=tmpdir; + argv[3]=NULL; + rc = spawn_and_wait(argv, NULL, NULL, NULL, NULL); + if (rc != PR_SUCCESS) + errWarn ("tmpdir cleanup"); } return secStatus; @@ -554,6 +864,9 @@ accept_connection(PRFileDesc *listenSocket) addr.inet.port); fflush (stdout); + /* XXX: alarm() or somesuch to set a timeout. */ + /* XXX: fork() or somesuch to handle concurrent requests. */ + /* Accepted the connection, now handle it. */ /*result =*/ handle_connection (tcpSocket); @@ -564,16 +877,6 @@ accept_connection(PRFileDesc *listenSocket) (addr.inet.ip >> 24) & 0xff, addr.inet.port); fflush (stdout); - -#if 0 /* Not necessary */ - if (result != SECSuccess) - { - prStatus = PR_Close(tcpSocket); - if (prStatus != PR_SUCCESS) - exitErr("PR_Close"); - break; - } -#endif } #if DEBUG diff --git a/stap-server-request b/stap-server-request deleted file mode 100755 index d1731895..00000000 --- a/stap-server-request +++ /dev/null @@ -1,512 +0,0 @@ -#!/bin/bash - -# Compile server for systemtap -# -# Copyright (C) 2008, 2009 Red Hat Inc. -# -# 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. - -# This script unpacks the tar file provided on stdin and uses the information -# contained in the unpacked tree to build the requested systemtap kernel module. -# This module is then written to stdout. - -# Catch ctrl-c and other termination signals -trap 'terminate' SIGTERM SIGINT - -# Initialize the environment -. ${PKGLIBEXECDIR}stap-env - -#----------------------------------------------------------------------------- -# Helper functions. -#----------------------------------------------------------------------------- -# function: initialization -function initialization { - # Initialization - stap_rc=0 - wd=`pwd` - - # Default options settings - p_phase=5 - keep_temps=0 - unprivileged=0 - stap_options= - - zip_client=$1 - tmpdir_server=$2 - zip_server=$3 - ssl_db=$4 - server_options="$5" - - # This script is not intended to be called directly, so if the arguments - # aren't specified correctly, just exit with non-zero - test "X$tmpdir_server" != "X" || exit 2 - test -d $tmpdir_server || exit 3 - tmpdir_env=`dirname $tmpdir_server` - - # Response file name. - test "X$zip_server" != "X" || exit 4 - # Make sure the specified .zip file exists. - test -f $zip_server || exit 5 - - # Request file name. - test "X$zip_client" != "X" || exit 6 - test -f $zip_client || exit 7 - - # Where is the ssl certificate/key database? - test "X$ssl_db" != "X" || exit 8 - test -d $ssl_db || exit 9 - nss_pw=$ssl_db/pw - test -f $nss_pw || exit 10 - - # What are the options that the server was started with? - eval parse_options "$server_options" - - nss_cert=stap-server - - touch $tmpdir_server/stdout - touch $tmpdir_server/stderr -} - -# function: unpack_request -# -# Unpack the zip file received from the client and make the contents -# available for use when running 'stap' -function unpack_request { - cd $tmpdir_server - - # Unpack the zip file. - unzip $zip_client > /dev/null || \ - fatal "Cannot unpack zip archive $zip_client" - - # Identify the client's request tree. The zip file should have expanded - # into a single directory named to match $stap_tmpdir_prefix_client.?????? - # which should now be the only item in the current directory. - test "`ls | wc -l`" = 3 || \ - fatal "Wrong number of files after expansion of client's zip file" - - tmpdir_client=`ls | grep $stap_tmpdir_prefix_client.......\$` - - test "X$tmpdir_client" != "X" || \ - fatal "Client zip file did not expand as expected" - - # Move the client's temp directory to a local temp location - local local_tmpdir_client=`mktemp -dt $stap_tmpdir_prefix_server.client.XXXXXX` || \ - fatal "Cannot create temporary client request directory " $local_tmpdir_client - mv $tmpdir_client/* $local_tmpdir_client - rm -fr $tmpdir_client - tmpdir_client=$local_tmpdir_client -} - -# function: check_request -# -# Examine the contents of the request to make sure that they are valid. -function check_request { - # Work in the temporary directory provided by the client - cd $tmpdir_client - - # Add the necessary info from files in our temporary directory. - cmdline=`read_data_file cmdline` - test "X$cmdline" != "X" || exit 1 - - eval parse_options "$cmdline" - - client_sysinfo=`read_data_file sysinfo` - test "X$client_sysinfo" != "X" || exit 1 - - check_compatibility "$client_sysinfo" "`server_sysinfo`" -} - -# function server_sysinfo -# -# Generate the server's sysinfo and echo it to stdout -function server_sysinfo { - if test "X$sysinfo_server" = "X"; then - # Add some info from uname - sysinfo_server="`uname -r` `stap_get_arch`" - fi - echo "$sysinfo_server" -} - -# function check_compaibility SYSINFO1 SYSINFO2 -# -# Make sure that systemtap as described by SYSINFO1 and SYSINFO2 are compaible -function check_compatibility { - # Compatibility is irrelevant. The client can choose any server - # it sees fit - return -} - -# function: read_data_file PREFIX -# -# Find a file whose name is '$1' and whose first line -# contents are '$1: .*'. Read and echo the data. -function read_data_file { - test -f $1 || \ - fatal "Data file $1 not found" - - # Open the file - exec 3< $1 - - # Verify the first line of the file. - read <&3 - line="$REPLY" - data=`expr "$line" : "$1: \\\(.*\\\)"` - if test "X$data" = "X"; then - fatal "Data in file $1 is incorrect" - return - fi - - # Close the file - exec 3<&- - - # Now read the entire file. - cat $1 | sed "s/$1: //" -} - -# function: parse_options [ STAP-OPTIONS ] -# -# Examine the command line. We need not do much checking, but we do need to -# parse all options in order to discover the ones we're interested in. -function parse_options { - # We need to know in advance if --unprivileged was specified. - all_options=" ""$@"" " - token=`expr "$all_options" : '.* \(--unprivileged\) .*'` - if test "X$token" = "X--unprivileged"; then - unprivileged=1 - fi - - while test $# != 0 - do - advance_p=0 - dash_seen=0 - - # Start of a new token. - first_token=$1 - - # Process the option. - until test $advance_p != 0 - do - # Identify the next option - first_char=`expr "$first_token" : '\(.\).*'` - if test $dash_seen = 0; then - if test "$first_char" = "-"; then - if test "$first_token" != "-"; then - # It's not a lone dash, so it's an option. - # Is it a long option (i.e. --option)? - second_char=`expr "$first_token" : '.\(.\).*'` - if test "X$second_char" = "X-"; then - advance_p=$(($advance_p + 1)) - stap_options="$stap_options $first_token" - break - fi - # It's not a lone dash, or a long option, so it's a short option string. - # Remove the dash. - first_token=`expr "$first_token" : '-\(.*\)'` - dash_seen=1 - first_char=`expr "$first_token" : '\(.\).*'` - fi - fi - if test $dash_seen = 0; then - # The dash has not been seen. This is either the script file - # name or an arument to be passed to the probe module. - # If this is the first time, and -e has not been specified, - # then it could be the name of the script file. - if test "X$e_script" = "X" -a "X$script_file" = "X"; then - script_file="$first_token" - fi - if test "$first_char" != "'"; then - stap_options="$stap_options '$first_token'" - else - stap_options="$stap_options $first_token" - fi - advance_p=$(($advance_p + 1)) - break - fi - fi - - # We are at the start of an option. Look at the first character. - case $first_char in - a) - get_arg $first_token $2 - if test $unprivileged = 1; then - fatal "You can't specify -$first_char and --unprivileged together." - else - stap_options="$stap_options -$first_char $stap_arg" - fi - ;; - B) - get_arg $first_token $2 - if test $unprivileged = 1; then - fatal "You can't specify -$first_char and --unprivileged together." - else - stap_options="$stap_options -$first_char $stap_arg" - fi - ;; - d) - get_arg $first_token $2 - stap_options="$stap_options -$first_char $stap_arg" - ;; - D) - get_arg $first_token $2 - if test $unprivileged = 1; then - fatal "You can't specify -$first_char and --unprivileged together." - else - stap_options="$stap_options -$first_char $stap_arg" - fi - ;; - e) - get_arg $first_token "$2" - stap_options="$stap_options -$first_char '$stap_arg'" - process_e "$stap_arg" - ;; - I) - get_arg $first_token $2 - if test $unprivileged = 1; then - fatal "You can't specify -$first_char and --unprivileged together." - else - stap_options="$stap_options -$first_char $stap_arg" - fi - ;; - k) - keep_temps=1 - ;; - l) - get_arg $first_token $2 - stap_options="$stap_options -$first_char $stap_arg" - process_p 2 - ;; - L) - get_arg $first_token $2 - stap_options="$stap_options -$first_char $stap_arg" - process_p 2 - ;; - m) - get_arg $first_token $2 - if test $unprivileged = 1; then - fatal "You can't specify -$first_char and --unprivileged together." - else - stap_options="$stap_options -$first_char $stap_arg" - fi - ;; - o) - get_arg $first_token $2 - stap_options="$stap_options -$first_char $stap_arg" - ;; - p) - get_arg $first_token $2 - process_p $stap_arg - ;; - r) - get_arg $first_token $2 - if test $unprivileged = 1; then - fatal "You can't specify -$first_char and --unprivileged together." - else - stap_options="$stap_options -$first_char $stap_arg" - fi - ;; - R) - get_arg $first_token $2 - if test $unprivileged = 1; then - fatal "You can't specify -$first_char and --unprivileged together." - else - stap_options="$stap_options -$first_char $stap_arg" - fi - ;; - s) - get_arg $first_token $2 - stap_options="$stap_options -$first_char $stap_arg" - ;; - S) - get_arg $first_token $2 - stap_options="$stap_options -$first_char $stap_arg" - ;; - x) - get_arg $first_token $2 - stap_options="$stap_options -$first_char $stap_arg" - ;; - *) - # An unknown flag. Ignore it. - ;; - esac - - if test $advance_p = 0; then - # Just another flag character. Consume it. - stap_options="$stap_options -$first_char" - first_token=`expr "$first_token" : '.\(.*\)'` - if test "X$first_token" = "X"; then - advance_p=$(($advance_p + 1)) - fi - fi - done - - # Consume the arguments we just processed. - while test $advance_p != 0 - do - shift - advance_p=$(($advance_p - 1)) - done - done -} - -# function: get_arg FIRSTWORD SECONDWORD -# -# Collect an argument to the given option -function get_arg { - # Remove first character. Advance to the next token, if the first one - # is exhausted. - local first=`expr "$1" : '.\(.*\)'` - if test "X$first" = "X"; then - shift - advance_p=$(($advance_p + 1)) - first=$1 - fi - stap_arg="$first" - advance_p=$(($advance_p + 1)) -} - -# function: process_e ARGUMENT -# -# Process the -e flag. -function process_e { - if test "X$e_script" = "X"; then - e_script="$1" - script_file= - fi -} - -# function: process_p ARGUMENT -# -# Process the -p flag. -function process_p { - if test $1 -ge 1 -a $1 -le 5; then - p_phase=$1 - fi -} - -# function: call_stap -# -# Call 'stap' with the options provided. Don't run past phase 4. -function call_stap { - # Invoke systemtap. - # Use -k so we can return results to the client - # Limit to -p4. i.e. don't run the module - cd $tmpdir_client - if test $p_phase -gt 4; then - server_p_phase=4 - else - server_p_phase=$p_phase - fi - - eval ${stap_exec_prefix}stap "$stap_options" -k -p $server_p_phase \ - >> $tmpdir_server/stdout \ - 2>> $tmpdir_server/stderr - - stap_rc=$? -} - -# function: create_response -# -# Add information to the server's temp directory representing the response -# to the client. -function create_response { - cd $tmpdir_server - - # Get the name of the stap temp directory, which was kept, from stderr. - tmpdir_line=`cat stderr | grep "Keeping temp"` - tmpdir_stap=`expr "$tmpdir_line" : '.*"\(.*\)".*'` - - # Remove the message about keeping the stap temp directory from stderr, unless - # the user did request to keep it. - if test "X$tmpdir_stap" != "X"; then - if test $keep_temps != 1; then - sed -i "/^Keeping temp/d" stderr - fi - - # Add the contents of the stap temp directory to the server output directory - ln -s $tmpdir_stap `basename $tmpdir_stap` - - # Sign the resulting module if --unprivileged was specified. - if test $unprivileged = 1 -a $p_phase -ge 4 -a $stap_rc = 0; then - modname=$tmpdir_stap/`grep -m1 '^.*\.ko$' stdout` - if test "X$modname" != "X"; then - ${stap_pkglibexecdir}stap-sign-module $modname $ssl_db - fi - fi - fi - - # If the user specified -p5, remove the name of the kernel module from stdout. - if test $p_phase = 5; then - sed -i '/\.ko$/d' stdout - fi - - # The return status of the stap command. - echo -n $stap_rc > rc -} - -# function: package_response -# -# Package the server's temp directory into a form suitable for sending to the -# client. -function package_response { - cd $tmpdir_env - - # Compress the server's temporary directory into a .zip archive. - (rm $zip_server && zip -r $zip_server `basename $tmpdir_server` > /dev/null) - return $? -} - -# function: fatal [ MESSAGE ] -# -# Fatal error -# Prints its arguments to stderr and exits -function fatal { - echo "$0: ERROR:" "$@" >> $tmpdir_server/stderr - echo -n 1 > $tmpdir_server/rc - package_response - cleanup - exit 1 -} - -# Non fatal error -# Prints its arguments to stderr but does not exit -function error { - echo "$0: ERROR:" "$@" >> $tmpdir_server/stderr -} - -# function cleanup -# -# Cleanup work files unless asked to keep them. -function cleanup { - # Clean up. - cd $tmpdir_env - if test $keep_temps != 1; then - rm -fr $tmpdir_server - rm -fr $tmpdir_client - rm -fr $tmpdir_stap - fi -} - -# function: terminate -# -# Terminate gracefully. -function terminate { - # Clean up - cleanup - exit 1 -} - -#----------------------------------------------------------------------------- -# Beginning of main line execution. -#----------------------------------------------------------------------------- -initialization "$@" -unpack_request -check_request -call_stap -create_response -package_response -cleanup - -exit 0 diff --git a/stap-serverd b/stap-serverd index f3df884d..4d8a10ce 100755 --- a/stap-serverd +++ b/stap-serverd @@ -38,6 +38,8 @@ function initialization { # Parse the arguments parse_options "$@" + echo ===== compile server pid $$ started >> $logfile + # What port will we listen on? test "X$port" = "X" && port=$((1024+$RANDOM%64000)) while netstat -atn | awk '{print $4}' | cut -f2 -d: | egrep -q "^$port\$"; diff --git a/tapsets.cxx b/tapsets.cxx index 7835b39a..071f92db 100644 --- a/tapsets.cxx +++ b/tapsets.cxx @@ -4632,6 +4632,7 @@ uprobe_derived_probe_group::emit_module_decls (systemtap_session& s) s.op->line() << " .procname=\"" << p->path << "\", "; s.op->line() << " .mmap_callback=&stap_uprobe_mmap_found, "; s.op->line() << " .munmap_callback=&stap_uprobe_munmap_found, "; + s.op->line() << " .callback=&stap_uprobe_process_munmap,"; } s.op->line() << " },"; diff --git a/testsuite/systemtap.server/client_args.exp b/testsuite/systemtap.server/client_args.exp index 348f8cea..f41b91cb 100644 --- a/testsuite/systemtap.server/client_args.exp +++ b/testsuite/systemtap.server/client_args.exp @@ -1,11 +1,11 @@ -set test "Valid Server Client Arguments" +set test "Invalid Server Client Arguments" # Test that stap on the server side will correctly accept/reject certain # arguments in unprivileged mode. set test_file $srcdir/systemtap.server/test.stp # Test invalid combinations. -set error_regexp ".*You can't specify (-\[aBDImRr\], )*-\[aBDImRr\] when --unprivileged is specified.*" +set error_regexp ".*You can't specify .* when --unprivileged is specified.*" set invalid_options [list \ "--unprivileged --client-options -a i386" \ |