summaryrefslogtreecommitdiffstats
path: root/runtime
diff options
context:
space:
mode:
Diffstat (limited to 'runtime')
-rw-r--r--runtime/staprun/mainloop.c10
-rw-r--r--runtime/staprun/modverify.c219
-rw-r--r--runtime/staprun/modverify.h1
-rw-r--r--runtime/staprun/staprun_funcs.c198
4 files changed, 360 insertions, 68 deletions
diff --git a/runtime/staprun/mainloop.c b/runtime/staprun/mainloop.c
index c80bbba4..205fdf37 100644
--- a/runtime/staprun/mainloop.c
+++ b/runtime/staprun/mainloop.c
@@ -7,7 +7,7 @@
* Public License (GPL); either version 2, or (at your option) any
* later version.
*
- * Copyright (C) 2005-2008 Red Hat Inc.
+ * Copyright (C) 2005-2009 Red Hat Inc.
*/
#include "staprun.h"
@@ -395,10 +395,10 @@ void cleanup_and_exit(int detach)
#define BUG9788_WORKAROUND
#ifndef BUG9788_WORKAROUND
dbug(2, "removing %s\n", modname);
- if (execlp(staprun, basename (staprun), "-d", modname, NULL) < 0) {
+ if (execlp(staprun, basename (staprun), "-d", modpath, NULL) < 0) {
if (errno == ENOEXEC) {
char *cmd;
- if (asprintf(&cmd, "%s -d '%s'", staprun, modname) > 0)
+ if (asprintf(&cmd, "%s -d '%s'", staprun, modpath) > 0)
execl("/bin/sh", "sh", "-c", cmd, NULL);
free(cmd);
}
@@ -427,10 +427,10 @@ void cleanup_and_exit(int detach)
if (pid == 0) { /* child process */
/* Run the command. */
- if (execlp(staprun, basename (staprun), "-d", modname, NULL) < 0) {
+ if (execlp(staprun, basename (staprun), "-d", modpath, NULL) < 0) {
if (errno == ENOEXEC) {
char *cmd;
- if (asprintf(&cmd, "%s -d '%s'", staprun, modname) > 0)
+ if (asprintf(&cmd, "%s -d '%s'", staprun, modpath) > 0)
execl("/bin/sh", "sh", "-c", cmd, NULL);
free(cmd);
}
diff --git a/runtime/staprun/modverify.c b/runtime/staprun/modverify.c
new file mode 100644
index 00000000..688734c0
--- /dev/null
+++ b/runtime/staprun/modverify.c
@@ -0,0 +1,219 @@
+/*
+ This program verifies the given file using the given signature, the named
+ certificate and public key in the given certificate database.
+
+ Copyright (C) 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 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
+*/
+
+#include <stdio.h>
+
+#include <nspr.h>
+#include <nss.h>
+#include <pk11pub.h>
+#include <cryptohi.h>
+#include <cert.h>
+#include <certt.h>
+
+#include "nsscommon.h"
+
+static int
+verify_it (const char *inputName, const char *signatureName, SECKEYPublicKey *pubKey)
+{
+ unsigned char buffer[4096];
+ PRFileInfo info;
+ PRStatus prStatus;
+ PRInt32 numBytes;
+ PRFileDesc *local_file_fd;
+ VFYContext *vfy;
+ SECItem signature;
+ SECStatus secStatus;
+
+ /* Get the size of the signature file. */
+ prStatus = PR_GetFileInfo (signatureName, &info);
+ if (prStatus != PR_SUCCESS || info.type != PR_FILE_FILE || info.size < 0)
+ {
+ fprintf (stderr, "Unable to obtain information on the signature file %s.\n", signatureName);
+ nssError ();
+ return -1;
+ }
+
+ /* Open the signature file. */
+ local_file_fd = PR_Open (signatureName, PR_RDONLY, 0);
+ if (local_file_fd == NULL)
+ {
+ fprintf (stderr, "Could not open the signature file %s\n.", signatureName);
+ nssError ();
+ return -1;
+ }
+
+ /* Allocate space to read the signature file. */
+ signature.data = PORT_Alloc (info.size);
+ if (! signature.data)
+ {
+ fprintf (stderr, "Unable to allocate memory for the signature in %s.\n", signatureName);
+ nssError ();
+ return -1;
+ }
+
+ /* Read the signature. */
+ numBytes = PR_Read (local_file_fd, signature.data, info.size);
+ if (numBytes == 0) /* EOF */
+ {
+ fprintf (stderr, "EOF reading signature file %s.\n", signatureName);
+ return -1;
+ }
+ if (numBytes < 0)
+ {
+ fprintf (stderr, "Error reading signature file %s.\n", signatureName);
+ nssError ();
+ return -1;
+ }
+ if (numBytes != info.size)
+ {
+ fprintf (stderr, "Incomplete data while reading signature file %s.\n", signatureName);
+ return -1;
+ }
+ signature.len = info.size;
+
+ /* Done with the signature file. */
+ PR_Close (local_file_fd);
+
+ /* Create a verification context. */
+ vfy = VFY_CreateContextDirect (pubKey, & signature, SEC_OID_PKCS1_RSA_ENCRYPTION,
+ SEC_OID_UNKNOWN, NULL, NULL);
+ if (! vfy)
+ {
+ fprintf (stderr, "Unable to create verification context while verifying %s using the signature in %s.\n",
+ inputName, signatureName);
+ nssError ();
+ return -1;
+ }
+
+ /* Begin the verification process. */
+ secStatus = VFY_Begin(vfy);
+ if (secStatus != SECSuccess)
+ {
+ fprintf (stderr, "Unable to initialize verification context while verifying %s using the signature in %s.\n",
+ inputName, signatureName);
+ nssError ();
+ return -1;
+ }
+
+ /* Now read the data and add it to the signature. */
+ local_file_fd = PR_Open (inputName, PR_RDONLY, 0);
+ if (local_file_fd == NULL)
+ {
+ fprintf (stderr, "Could not open module file %s.\n", inputName);
+ nssError ();
+ return -1;
+ }
+
+ for (;;)
+ {
+ numBytes = PR_Read (local_file_fd, buffer, sizeof (buffer));
+ if (numBytes == 0)
+ break; /* EOF */
+
+ if (numBytes < 0)
+ {
+ fprintf (stderr, "Error reading module file %s.\n", inputName);
+ nssError ();
+ return -1;
+ }
+
+ /* Add the data to the signature. */
+ secStatus = VFY_Update (vfy, buffer, numBytes);
+ if (secStatus != SECSuccess)
+ {
+ fprintf (stderr, "Error while verifying module file %s.\n", inputName);
+ nssError ();
+ return -1;
+ }
+ }
+
+ PR_Close(local_file_fd);
+
+ /* Complete the verification. */
+ secStatus = VFY_End (vfy);
+ if (secStatus != SECSuccess) {
+ fprintf (stderr, "Unable to verify signed module %s. It may have been altered since it was created.\n", inputName);
+ nssError ();
+ return 0;
+ }
+
+ return 1;
+}
+
+int verify_module (const char *module_name, const char *signature_name)
+{
+ const char *dbdir = SYSCONFDIR "/systemtap/staprun";
+ SECKEYPublicKey *pubKey;
+ SECStatus secStatus;
+ CERTCertList *certList;
+ CERTCertListNode *certListNode;
+ CERTCertificate *cert;
+ int rc = 0;
+
+ /* Call the NSPR initialization routines. */
+ PR_Init (PR_SYSTEM_THREAD, PR_PRIORITY_NORMAL, 1);
+
+ /* Initialize NSS. */
+ secStatus = NSS_Init (dbdir);
+ if (secStatus != SECSuccess)
+ {
+ fprintf (stderr, "Unable to initialize nss library using the database in %s.\n",
+ dbdir);
+ nssError ();
+ return -1;
+ }
+
+ certList = PK11_ListCerts (PK11CertListAll, NULL);
+ if (certList == NULL)
+ {
+ fprintf (stderr, "Unable to find certificates in the certificate database in %s.\n",
+ dbdir);
+ nssError ();
+ return -1;
+ }
+
+ /* We need to look at each certificate in the database. */
+ for (certListNode = CERT_LIST_HEAD (certList);
+ ! CERT_LIST_END (certListNode, certList);
+ certListNode = CERT_LIST_NEXT (certListNode))
+ {
+ cert = certListNode->cert;
+
+ pubKey = CERT_ExtractPublicKey (cert);
+ if (pubKey == NULL)
+ {
+ fprintf (stderr, "Unable to extract public key from the certificate with nickname %s from the certificate database in %s.\n",
+ cert->nickname, dbdir);
+ nssError ();
+ return -1;
+ }
+
+ /* Verify the file. */
+ rc = verify_it (module_name, signature_name, pubKey);
+ if (rc == 1)
+ break; /* Verified! */
+ }
+
+ /* Shutdown NSS and exit NSPR gracefully. */
+ nssCleanup ();
+
+ return rc;
+}
diff --git a/runtime/staprun/modverify.h b/runtime/staprun/modverify.h
new file mode 100644
index 00000000..ad212e2b
--- /dev/null
+++ b/runtime/staprun/modverify.h
@@ -0,0 +1 @@
+int verify_module (const char *module_name, const char *signature_name);
diff --git a/runtime/staprun/staprun_funcs.c b/runtime/staprun/staprun_funcs.c
index 49b37988..020bb312 100644
--- a/runtime/staprun/staprun_funcs.c
+++ b/runtime/staprun/staprun_funcs.c
@@ -7,10 +7,15 @@
* Public License (GPL); either version 2, or (at your option) any
* later version.
*
- * Copyright (C) 2007-2008 Red Hat Inc.
+ * Copyright (C) 2007-2009 Red Hat Inc.
*/
+#include "config.h"
#include "staprun.h"
+#if HAVE_NSS
+#include "modverify.h"
+#endif
+
#include <sys/mount.h>
#include <sys/utsname.h>
#include <grp.h>
@@ -199,6 +204,44 @@ int mountfs(void)
return 0;
}
+#if HAVE_NSS
+/*
+ * Modules which have been signed using a certificate and private key
+ * corresponding to a certificate and public key in the database in
+ * the '$sysconfdir/systemtap/staprun' directory may be loaded by
+ * anyone.
+ *
+ * Returns: -1 on errors, 0 on failure, 1 on success.
+ */
+static int
+check_signature(void)
+{
+ char module_realpath[PATH_MAX];
+ char signature_realpath[PATH_MAX];
+ int rc;
+
+ dbug(2, "checking signature for %s\n", modpath);
+
+ /* Use realpath() to canonicalize the module path. */
+ if (realpath(modpath, module_realpath) == NULL) {
+ perr("Unable to canonicalize signature path \"%s\"", modpath);
+ return -1;
+ }
+
+ /* Now add the .sgn suffix to get the signature file name. */
+ if (strlen (module_realpath) > PATH_MAX - 4) {
+ err("Path \"%s\" is too long.", modpath);
+ return -1;
+ }
+ sprintf (signature_realpath, "%s.sgn", module_realpath);
+
+ dbug(2, "verify_module (%s, %s)\n", module_realpath, signature_realpath);
+ rc = verify_module (module_realpath, signature_realpath);
+ dbug(2, "verify_module returns %d\n", rc);
+
+ return rc;
+}
+#endif /* HAVE_NSS */
/*
* Members of the 'stapusr' group can only use "blessed" modules -
@@ -302,35 +345,23 @@ check_path(void)
}
/*
- * Check the user's permissions. Is he allowed to run staprun (or is
- * he limited to "blessed" modules)?
+ * Check the user's group membership. Is he allowed to run staprun (or is
*
- * Returns: -1 on errors, 0 on failure, 1 on success.
+ * o members of stapdev can do anything
+ * o members of stapusr can load modules from /lib/modules/KVER/systemtap
+ *
+ * Returns: -2 if neither group exists
+ * -1 for other errors
+ * 0 on failure
+ * 1 on success
*/
-int check_permissions(void)
+static int
+check_groups (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 (getuid() == 0) {
- /* ... like overriding the real UID */
- const char *env_id = getenv("SYSTEMTAP_REAL_UID");
- if (env_id && setreuid(atoi(env_id), -1))
- err("WARNING: couldn't set staprun UID to '%s': %s",
- env_id, strerror(errno));
-
- /* ... or overriding the real GID */
- env_id = getenv("SYSTEMTAP_REAL_GID");
- if (env_id && setregid(atoi(env_id), -1))
- err("WARNING: couldn't set staprun GID to '%s': %s",
- env_id, strerror(errno));
-
- return 1;
- }
/* Lookup the gid for group "stapdev" */
errno = 0;
@@ -354,55 +385,42 @@ int check_permissions(void)
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: You are trying to run stap as a normal user.\n"
- "You should either be root, or be part of either "
- "group \"stapdev\" or group \"stapusr\".\n"
- "Your system doesn't seem to have either group.\n"
- "For more information, please consult the \"SAFETY AND SECURITY\" section of the \"stap(1)\" manpage\n");
- return -1;
- }
+ /* If neither group was found, then return -2. */
+ if (stapdev_gid == (gid_t)-1 && stapusr_gid == (gid_t)-1)
+ return -2;
/* 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;
- }
+ if (gid != stapusr_gid) {
+ /* 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;
- }
+ 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)
+ gid = stapusr_gid;
+ }
- /* If path_check is 0, then the user isn't a member of either
- * group. Error out. */
- if (path_check == 0) {
- err("ERROR: You are trying to run stap as a normal user.\n"
- "You must be a member of either group \"stapdev\" or group \"stapusr\".\n"
- "Please contact your system administrator to get yourself membership to either of those groups.\n"
- "For more information, please consult the \"SAFETY AND SECURITY\" section of the \"stap(1)\" manpage.\n");
- return 0;
+ if (gid != stapusr_gid)
+ return 0;
}
/* At this point the user is only a member of the 'stapusr'
@@ -411,3 +429,57 @@ int check_permissions(void)
* is in that directory. */
return check_path();
}
+
+/*
+ * Check the user's permissions. Is he allowed to run staprun (or is
+ * he limited to "blessed" modules)?
+ *
+ * There are several levels of possible permission:
+ *
+ * 1) root can do anything
+ * 2) members of stapdev can do anything
+ * 3) members of stapusr can load modules from /lib/modules/KVER/systemtap
+ * 4) anyone can load a module which has been signed by a trusted signer
+ *
+ * It is only an error if all 4 levels of checking fail
+ *
+ * Returns: -1 on errors, 0 on failure, 1 on success.
+ */
+int check_permissions(void)
+{
+ int check_groups_rc;
+ int check_signature_rc = 0;
+
+ /* If we're root, we can do anything. */
+ if (getuid() == 0)
+ return 1;
+
+ /* Check permissions for group membership. */
+ check_groups_rc = check_groups ();
+ if (check_groups_rc == 1)
+ return 1;
+
+#if HAVE_NSS
+ /* The user is an ordinary user. If the module has been signed with
+ * a "blessed" certificate and private key, then we will load it for
+ * anyone. */
+ check_signature_rc = check_signature ();
+ if (check_signature_rc == 1)
+ return 1;
+#endif
+
+ err("ERROR: You are trying to run stap as a normal user.\n"
+ "You should either be root, or be part of either "
+ "group \"stapdev\" or group \"stapusr\".\n");
+ if (check_groups_rc == -2) {
+ err("Your system doesn't seem to have either group.\n");
+ check_groups_rc = -1;
+ }
+#if HAVE_NSS
+ err("Alternatively, your module must be signed by a trusted signer.\n"
+ "For more information, please consult the \"SAFETY AND SECURITY\" section of the \"stap(1)\" manpage\n");
+#endif
+
+ /* Combine the return codes. They are either 0 or -1. */
+ return check_groups_rc | check_signature_rc;
+}