The attached patch allows to use p11-kit to run and use an isolated PKCS #11 module. The performance cost seems to be quite limited. I've tested it with softhsm (isolated) + lighttpd2 and a pseudo-benchmark (run in the same pc) shows:
softhsm: TRAFFIC: 63450 avg bytes, 235 avg overhead, 634500000 bytes, 2350000 overhead TIMING: 33.201 seconds, 301 rps, 18731 kbps, 13.3 ms avg req time direct: TRAFFIC: 63450 avg bytes, 235 avg overhead, 634500000 bytes, 2350000 overhead TIMING: 29.981 seconds, 333 rps, 20743 kbps, 12.0 ms avg req time regards, Nikos
>From e72170bffb0ad89daad997a96a4be6afc159c168 Mon Sep 17 00:00:00 2001 From: Nikos Mavrogiannopoulos <[email protected]> Date: Thu, 30 Oct 2014 14:17:42 +0100 Subject: [PATCH] Added the ability to use a PKCS #11 server process via a socket When the remote option is specified with a file starting with '@' a unix socket will be used for PKCS #11 operations. That way one can connect to a p11-kit isolated module (run as different user in a different process). The isolated module can be started using the "p11-kit server" command. --- common/Makefile.am | 1 + common/compat.h | 4 - common/unix-peer.c | 92 +++++++++ common/unix-peer.h | 42 ++++ configure.ac | 5 + p11-kit/Makefile.am | 4 + p11-kit/p11-kit.c | 143 ++++++++++++++ p11-kit/remote.h | 11 ++ p11-kit/rpc-server.c | 407 +++++++++++++++++++++++++++++++++++++- p11-kit/rpc-transport.c | 146 ++++++++++++-- p11-kit/rpc.h | 3 +- p11-kit/test-log.c | 2 +- p11-kit/test-managed.c | 2 +- p11-kit/test-mock.c | 28 ++- p11-kit/test-proxy.c | 2 +- p11-kit/test-rpc.c | 4 +- p11-kit/test-transport-isolated.c | 329 ++++++++++++++++++++++++++++++ p11-kit/test-transport.c | 2 +- 18 files changed, 1198 insertions(+), 29 deletions(-) create mode 100644 common/unix-peer.c create mode 100644 common/unix-peer.h create mode 100644 p11-kit/test-transport-isolated.c diff --git a/common/Makefile.am b/common/Makefile.am index b053ec0..534bb38 100644 --- a/common/Makefile.am +++ b/common/Makefile.am @@ -26,6 +26,7 @@ libp11_common_la_SOURCES = \ common/path.c common/path.h \ common/pkcs11.h common/pkcs11x.h common/pkcs11i.h \ common/url.c common/url.h \ + common/unix-peer.c common/unix-peer.h \ $(NULL) libp11_library_la_SOURCES = \ diff --git a/common/compat.h b/common/compat.h index 6483d4f..f5e2a11 100644 --- a/common/compat.h +++ b/common/compat.h @@ -40,10 +40,6 @@ #include <sys/types.h> #include <sys/stat.h> -#ifdef _GNU_SOURCE -#error Make the crap stop. _GNU_SOURCE is completely unportable and breaks all sorts of behavior -#endif - #if !defined(__cplusplus) && (__GNUC__ > 2) #define GNUC_PRINTF(x, y) __attribute__((__format__(__printf__, x, y))) #else diff --git a/common/unix-peer.c b/common/unix-peer.c new file mode 100644 index 0000000..689159d --- /dev/null +++ b/common/unix-peer.c @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2013 Nikos Mavrogiannopoulos + * + * This file is part of ocserv. + * + * ocserv 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 3 of the License, or + * (at your option) any later version. + * + * ocserv 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, see <http://www.gnu.org/licenses/>. + */ + +#ifdef __linux__ + /* needed for struct ucred */ +# define _GNU_SOURCE +#endif + +#include "config.h" +#include "debug.h" +#include "message.h" +#include <unistd.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <sys/uio.h> +#include <sys/errno.h> + +#include "unix-peer.h" + +/* Returns the unix domain socket peer information. + * Returns zero on success. + */ +int p11_get_upeer_id(int cfd, uid_t *uid, uid_t *gid, pid_t *pid) +{ + int e, ret; +#if defined(SO_PEERCRED) + struct ucred cr; + socklen_t cr_len; + + cr_len = sizeof(cr); + ret = getsockopt(cfd, SOL_SOCKET, SO_PEERCRED, &cr, &cr_len); + if (ret == -1) { + e = errno; + p11_message("getsockopt SO_PEERCRED error: %s", + strerror(e)); + return -1; + } + + if (uid) + *uid = cr.uid; + + if (gid) + *gid = cr.gid; + + if (pid) + *pid = cr.pid; + +#elif defined(HAVE_GETPEEREID) + /* *BSD/MacOSX */ + uid_t euid; + gid_t egid; + + ret = getpeereid(cfd, &euid, &egid); + + if (ret == -1) { + e = errno; + p11_message("getpeereid error: %s", + strerror(e)); + return -1; + } + + if (uid) + *uid = euid; + + if (gid) + *gid = egid; + + if (pid) + *pid = -1; + +#else +#error "Unsupported UNIX variant" +#endif + return 0; +} diff --git a/common/unix-peer.h b/common/unix-peer.h new file mode 100644 index 0000000..0840c08 --- /dev/null +++ b/common/unix-peer.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2014 Red Hat Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * * The names of contributors to this software may not be + * used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * Author: Nikos Mavrogiannopoulos + */ + +#ifndef P11_UNIX_PEER_H_ +#define P11_UNIX_PEER_H_ + +#include <getopt.h> + +int p11_get_upeer_id(int cfd, uid_t *uid, uid_t *gid, pid_t *pid); + +#endif /* P11_UNIX_PEER_H_ */ diff --git a/configure.ac b/configure.ac index 8a1939f..acf02d1 100644 --- a/configure.ac +++ b/configure.ac @@ -68,6 +68,11 @@ AC_C_BIGENDIAN AC_HEADER_STDBOOL if test "$os_unix" = "yes"; then + AC_CHECK_TYPES([sighandler_t, sig_t, __sighandler_t],,, + [#include <sys/types.h> + #include <signal.h> + ]) + AC_CHECK_FUNC([pthread_create], , [ AC_CHECK_LIB(pthread, pthread_create, , [ AC_MSG_ERROR([could not find pthread_create]) diff --git a/p11-kit/Makefile.am b/p11-kit/Makefile.am index 3ef70f9..eecdb6e 100644 --- a/p11-kit/Makefile.am +++ b/p11-kit/Makefile.am @@ -202,6 +202,7 @@ CHECK_PROGS += \ test-managed \ test-log \ test-transport \ + test-transport-isolated \ $(NULL) test_log_SOURCES = p11-kit/test-log.c @@ -213,6 +214,9 @@ test_managed_LDADD = $(p11_kit_LIBS) test_transport_SOURCES = p11-kit/test-transport.c test_transport_LDADD = $(p11_kit_LIBS) +test_transport_isolated_SOURCES = p11-kit/test-transport-isolated.c +test_transport_isolated_LDADD = $(p11_kit_LIBS) + test_virtual_SOURCES = p11-kit/test-virtual.c test_virtual_LDADD = $(p11_kit_LIBS) diff --git a/p11-kit/p11-kit.c b/p11-kit/p11-kit.c index a7b9212..8b9271f 100644 --- a/p11-kit/p11-kit.c +++ b/p11-kit/p11-kit.c @@ -48,7 +48,11 @@ #include <stdio.h> #include <stdlib.h> #include <unistd.h> +#include <sys/types.h> +#include <grp.h> +#include <pwd.h> +#include "remote.h" #include "tool.h" int p11_kit_list_modules (int argc, @@ -60,9 +64,13 @@ int p11_kit_trust (int argc, int p11_kit_external (int argc, char *argv[]); +int p11_kit_server (int argc, + char *argv[]); + static const p11_tool_command commands[] = { { "list-modules", p11_kit_list_modules, "List modules and tokens" }, { "remote", p11_kit_external, "Run a specific PKCS#11 module remotely" }, + { "server", p11_kit_server, "Run a specific PKCS#11 module as an isolated server" }, { P11_TOOL_FALLBACK, p11_kit_external, NULL }, { 0, } }; @@ -128,6 +136,141 @@ p11_kit_external (int argc, } int +p11_kit_server (int argc, + char *argv[]) +{ + char *socket_file = NULL; + CK_FUNCTION_LIST *module; + uid_t uid = -1, run_as_uid = -1; + gid_t gid = -1, run_as_gid = -1; + int opt; + int ret; + const struct passwd* pwd; + const struct group* grp; + unsigned foreground = 0; + unsigned timeout = 0; + mode_t mode = 0; + + enum { + opt_verbose = 'v', + opt_help = 'h', + opt_socket = 's', + opt_mode = 'm', + opt_user = 'u', + opt_group = 'g', + opt_run_as_user = 'a', + opt_run_as_group = 'z', + opt_foreground = 'f', + opt_timeout = 't', + }; + + struct option options[] = { + { "verbose", no_argument, NULL, opt_verbose }, + { "help", no_argument, NULL, opt_help }, + { "foreground", no_argument, NULL, opt_foreground }, + { "socket", required_argument, NULL, opt_socket }, + { "user", required_argument, NULL, opt_user }, + { "group", required_argument, NULL, opt_group }, + { "run-as-user", required_argument, NULL, opt_run_as_user }, + { "run-as-group", required_argument, NULL, opt_run_as_group }, + { "timeout", required_argument, NULL, opt_timeout }, + { "mode", required_argument, NULL, opt_mode }, + { 0 }, + }; + + p11_tool_desc usages[] = { + { 0, "usage: p11-kit server --help" }, + { 0, "usage: p11-kit server <module> -s <socket-file>" }, + { 0, "usage: p11-kit server <module> -s <socket-file> -u <allowed-user> -g <allowed-group> --run-as-user <user> --run-as-group <group> -m <mode>" }, + { 0 }, + }; + + while ((opt = p11_tool_getopt (argc, argv, options)) != -1) { + switch (opt) { + case opt_verbose: + p11_kit_be_loud (); + break; + case opt_socket: + socket_file = strdup(optarg); + break; + case opt_timeout: + timeout = atoi(optarg); + break; + case opt_group: + grp = getgrnam(optarg); + if (grp == NULL) { + p11_message ("unknown group: %s", optarg); + return 2; + } + gid = grp->gr_gid; + break; + case opt_user: + pwd = getpwnam(optarg); + if (pwd == NULL) { + p11_message ("unknown user: %s", optarg); + return 2; + } + uid = pwd->pw_uid; + break; + case opt_mode: + mode = strtol(optarg, NULL, 8); + break; + case opt_run_as_group: + grp = getgrnam(optarg); + if (grp == NULL) { + p11_message ("unknown group: %s", optarg); + return 2; + } + run_as_gid = grp->gr_gid; + break; + case opt_run_as_user: + pwd = getpwnam(optarg); + if (pwd == NULL) { + p11_message ("unknown user: %s", optarg); + return 2; + } + run_as_uid = pwd->pw_uid; + break; + case opt_foreground: + foreground = 1; + break; + case opt_help: + case '?': + p11_tool_usage (usages, options); + return 0; + default: + assert_not_reached (); + break; + } + } + + argc -= optind; + argv += optind; + + if (socket_file == NULL) { + p11_tool_usage (usages, options); + return 2; + } + + if (argc != 1) { + p11_message ("specify the module to run as isolated server"); + return 2; + } + + module = p11_kit_module_load (argv[0], 0); + if (module == NULL) + return 1; + + ret = p11_kit_remote_isolate_module (module, socket_file, mode, + uid, gid, + run_as_uid, run_as_gid, + foreground, timeout); + p11_kit_module_release (module); + + return ret; +} + +int main (int argc, char *argv[]) { diff --git a/p11-kit/remote.h b/p11-kit/remote.h index 12cbe6d..a854f9b 100644 --- a/p11-kit/remote.h +++ b/p11-kit/remote.h @@ -47,6 +47,17 @@ int p11_kit_remote_serve_module (CK_FUNCTION_LIST *m int in_fd, int out_fd); +int +p11_kit_remote_isolate_module (CK_FUNCTION_LIST *module, + const char *socket_file, + mode_t mode, + uid_t uid, + gid_t gid, + uid_t run_as_uid, + gid_t run_as_gid, + unsigned foreground, + unsigned timeout); + #endif #ifdef __cplusplus diff --git a/p11-kit/rpc-server.c b/p11-kit/rpc-server.c index a2562e9..de61c90 100644 --- a/p11-kit/rpc-server.c +++ b/p11-kit/rpc-server.c @@ -50,9 +50,46 @@ #include <assert.h> #include <errno.h> #include <stdlib.h> +#include <stdio.h> #include <string.h> #include <unistd.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/wait.h> +#include <sys/un.h> +#include <grp.h> + +#include "unix-peer.h" + +#ifdef HAVE_SIGHANDLER_T +# define SIGHANDLER_T sighandler_t +#elif HAVE_SIG_T +# define SIGHANDLER_T sig_t +#elif HAVE___SIGHANDLER_T +# define SIGHANDLER_T __sighandler_t +#else +typedef void (*sighandler_t)(int); +# define SIGHANDLER_T sighandler_t +#endif + +static unsigned need_children_cleanup = 0; +static unsigned terminate = 0; +static unsigned children_avail = 0; + +static +SIGHANDLER_T p11_signal(int signum, SIGHANDLER_T handler) +{ + struct sigaction new_action, old_action; + + new_action.sa_handler = handler; + sigemptyset (&new_action.sa_mask); + new_action.sa_flags = 0; + + sigaction (signum, &new_action, &old_action); + return old_action.sa_handler; +} + /* The error returned on protocol failures */ #define PARSE_ERROR CKR_DEVICE_ERROR #define PREP_ERROR CKR_DEVICE_MEMORY @@ -708,15 +745,32 @@ rpc_C_Finalize (CK_X_FUNCTION_LIST *self, END_CALL; } +static void fix_info(const char *id, CK_INFO *info) +{ + unsigned len; + unsigned i; + + /* replace description */ + snprintf((char*)info->manufacturerID, sizeof(info->manufacturerID), "V:%s", id); + len = strlen((char*)info->manufacturerID); + + for (i=len;i<sizeof(info->manufacturerID);i++) + info->manufacturerID[i] = ' '; +} + static CK_RV -rpc_C_GetInfo (CK_X_FUNCTION_LIST *self, +rpc_C_GetInfo (const char *id, + CK_X_FUNCTION_LIST *self, p11_rpc_message *msg) { CK_INFO info; BEGIN_CALL (GetInfo); PROCESS_CALL ((self, &info)); - OUT_INFO (info); + if (id) + fix_info (id, &info); + + OUT_INFO (info); END_CALL; } @@ -1764,7 +1818,8 @@ rpc_C_GenerateRandom (CK_X_FUNCTION_LIST *self, } bool -p11_rpc_server_handle (CK_X_FUNCTION_LIST *self, +p11_rpc_server_handle (const char *name, + CK_X_FUNCTION_LIST *self, p11_buffer *request, p11_buffer *response) { @@ -1796,9 +1851,13 @@ p11_rpc_server_handle (CK_X_FUNCTION_LIST *self, case P11_RPC_CALL_##name: \ ret = rpc_##name (self, &msg); \ break; + #define CASE_CALL_ID(id, name) \ + case P11_RPC_CALL_##name: \ + ret = rpc_##name (id, self, &msg); \ + break; CASE_CALL (C_Initialize) CASE_CALL (C_Finalize) - CASE_CALL (C_GetInfo) + CASE_CALL_ID (name, C_GetInfo) CASE_CALL (C_GetSlotList) CASE_CALL (C_GetSlotInfo) CASE_CALL (C_GetTokenInfo) @@ -1970,7 +2029,7 @@ p11_kit_remote_serve_module (CK_FUNCTION_LIST *module, goto out; } - if (!p11_rpc_server_handle (&virt.funcs, &buffer, &buffer)) { + if (!p11_rpc_server_handle (NULL, &virt.funcs, &buffer, &buffer)) { p11_message ("unexpected error handling rpc message"); goto out; } @@ -2002,3 +2061,341 @@ out: return ret; } + +static int +serve_isolated_module (const char *name, + CK_FUNCTION_LIST *module, + p11_buffer *options, p11_buffer *buffer, + p11_virtual *virt, + int fd) +{ + p11_rpc_status status; + unsigned char version; + uint32_t pid; + size_t state; + int ret = 1; + int code; + struct iovec iov[2]; + + switch (read (fd, &version, 1)) { + case 0: + status = P11_RPC_EOF; + goto out; + case 1: + if (version != 1) { + p11_message ("unspported version received: %d", (int)version); + goto out; + } + break; + default: + p11_message_err (errno, "couldn't read credential byte"); + goto out; + } + + version = 0; + pid = getpid(); + + iov[0].iov_base = &version; + iov[0].iov_len = 1; + + iov[1].iov_base = &pid; + iov[1].iov_len = 4; + + switch (writev (fd, iov, 2)) { + case 5: + break; + default: + p11_message_err (errno, "couldn't write credential bytes"); + goto out; + } + + status = P11_RPC_OK; + while (status == P11_RPC_OK) { + state = 0; + code = 0; + + do { + status = p11_rpc_transport_read (fd, &state, &code, + options, buffer); + } while (status == P11_RPC_AGAIN); + + switch (status) { + case P11_RPC_OK: + break; + case P11_RPC_EOF: + ret = 0; + continue; + case P11_RPC_AGAIN: + assert_not_reached (); + case P11_RPC_ERROR: + p11_message_err (errno, "failed to read rpc message"); + goto out; + } + + if (!p11_rpc_server_handle (name, &virt->funcs, buffer, buffer)) { + p11_message ("unexpected error handling rpc message"); + goto out; + } + + state = 0; + options->len = 0; + do { + status = p11_rpc_transport_write (fd, &state, code, + options, buffer); + } while (status == P11_RPC_AGAIN); + + switch (status) { + case P11_RPC_OK: + break; + case P11_RPC_EOF: + case P11_RPC_AGAIN: + assert_not_reached (); + case P11_RPC_ERROR: + p11_message_err (errno, "failed to write rpc message"); + goto out; + } + } + +out: + return ret; +} + +static void cleanup_children(void) +{ +int status; +pid_t pid; + + while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { + if (children_avail > 0) + children_avail--; + if (WIFSIGNALED(status)) { + if (WTERMSIG(status) == SIGSEGV) + p11_message("child %u died with sigsegv\n", (unsigned)pid); + else + p11_message("child %u died with signal %d\n", (unsigned)pid, (int)WTERMSIG(status)); + } + } + need_children_cleanup = 0; +} + +static void handle_children(int signo) +{ + need_children_cleanup = 1; +} + +static void handle_term(int signo) +{ + terminate = 1; +} + + +int +p11_kit_remote_isolate_module (CK_FUNCTION_LIST *module, + const char *socket_file, + mode_t mode, + uid_t uid, + gid_t gid, + uid_t run_as_uid, + gid_t run_as_gid, + unsigned foreground, + unsigned timeout) +{ + p11_virtual virt; + p11_buffer options; + p11_buffer buffer; + int ret = 1, rc, sd; + int e, cfd; + pid_t pid; + socklen_t sa_len; + struct sockaddr_un sa; + fd_set rd_set; + sigset_t emptyset, blockset; + uid_t tuid; + gid_t tgid; + struct timespec ts; + + sigemptyset(&blockset); + sigemptyset(&emptyset); + sigaddset(&blockset, SIGCHLD); + sigaddset(&blockset, SIGTERM); + sigaddset(&blockset, SIGINT); + p11_signal(SIGCHLD, handle_children); + p11_signal(SIGTERM, handle_term); + p11_signal(SIGINT, handle_term); + + return_val_if_fail (module != NULL, 1); + + /* listen to unix socket */ + memset(&sa, 0, sizeof(sa)); + sa.sun_family = AF_UNIX; + snprintf(sa.sun_path, sizeof(sa.sun_path), "%s", socket_file); + + remove(socket_file); + + sd = socket(AF_UNIX, SOCK_STREAM, 0); + if (sd == -1) { + e = errno; + p11_message ("could not create socket %s: %s", socket_file, strerror(e)); + return 1; + } + + umask(066); + rc = bind(sd, (struct sockaddr *)&sa, SUN_LEN(&sa)); + if (rc == -1) { + e = errno; + p11_message ("could not create socket %s: %s", socket_file, strerror(e)); + return 1; + } + + if (uid != -1 && gid != -1) { + if (uid == getuid()) { + p11_message ("server is running using the same uid as client, this may not provide the required isolation"); + } + + rc = chown(socket_file, uid, gid); + if (rc == -1) { + e = errno; + p11_message ("could not chown socket %s: %s", socket_file, strerror(e)); + return 1; + } + } + + if (mode != 0) { + rc = chmod(socket_file, mode); + if (rc == -1) { + e = errno; + p11_message ("could not chmod socket %s to %o: %s", socket_file, mode, strerror(e)); + } + } + + if (run_as_gid != -1) { + if (setgid(run_as_gid) == -1) { + e = errno; + p11_message("cannot set gid to %u: %s\n", (unsigned)run_as_gid, strerror(e)); + return 1; + } + + if (setgroups(1, &run_as_gid) == -1) { + e = errno; + p11_message("cannot setgroups to %u: %s\n", (unsigned)run_as_gid, strerror(e)); + return 1; + } + } + + if (run_as_uid != -1) { + if (setuid(run_as_uid) == -1) { + e = errno; + p11_message("cannot set uid to %u: %s\n", (unsigned)run_as_uid, strerror(e)); + return 1; + } + } + + /* run as daemon */ + if (foreground == 0) { + if (daemon(0,0) == -1) { + e = errno; + p11_message ("could not daemonize: %s", strerror(e)); + } + } + + rc = listen(sd, 1024); + if (rc == -1) { + e = errno; + p11_message ("could not listen to socket %s: %s", socket_file, strerror(e)); + return 1; + } + + p11_buffer_init (&options, 0); + p11_buffer_init (&buffer, 0); + + p11_virtual_init (&virt, &p11_virtual_base, module, NULL); + + sigprocmask(SIG_BLOCK, &blockset, NULL); + /* accept connections */ + for (;;) { + if (need_children_cleanup) + cleanup_children(); + + if (terminate != 0) + goto exit; + + FD_ZERO(&rd_set); + FD_SET(sd, &rd_set); + + ts.tv_sec = timeout; + ts.tv_nsec = 0; + if (timeout > 0) + ret = pselect(sd + 1, &rd_set, NULL, NULL, &ts, &emptyset); + else + ret = pselect(sd + 1, &rd_set, NULL, NULL, NULL, &emptyset); + if (ret == -1 && errno == EINTR) + continue; + + if (ret == 0 && children_avail == 0) { /* timeout */ + p11_message ("no connections to %s for %u secs, exiting", socket_file, timeout); + goto exit; + } + + if (FD_ISSET(sd, &rd_set)) { + sa_len = sizeof(sa); + cfd = accept(sd, (struct sockaddr *)&sa, &sa_len); + if (cfd == -1) { + e = errno; + if (e != EINTR) { + p11_message ("could not accept from socket %s: %s", socket_file, strerror(e)); + } + continue; + } + + /* check the uid of the peer */ + rc = p11_get_upeer_id(cfd, &tuid, &tgid, NULL); + if (rc == -1) { + e = errno; + p11_message ("could not check uid from socket %s: %s", socket_file, strerror(e)); + goto cont; + } + + if (uid != -1) { + if (uid != tuid) { + p11_message ("connecting uid (%u) doesn't match expected (%u)", + (unsigned)tuid, (unsigned)uid); + goto cont; + } + } + + if (gid != -1) { + if (gid != tgid) { + p11_message ("connecting gid (%u) doesn't match expected (%u)", + (unsigned)tgid, (unsigned)gid); + goto cont; + } + } + + pid = fork(); + switch(pid) { + case -1: + p11_message_err (errno, "failed to fork for accept"); + continue; + case 0: + /* child */ + sigprocmask(SIG_UNBLOCK, &blockset, NULL); + serve_isolated_module (socket_file, module, &options, &buffer, &virt, cfd); + _exit(0); + default: + children_avail++; + break; + } + cont: + close(cfd); + } + } + + p11_buffer_uninit (&buffer); + p11_buffer_uninit (&options); + + p11_virtual_uninit (&virt); + return ret; + exit: + remove(socket_file); + exit(0); +} diff --git a/p11-kit/rpc-transport.c b/p11-kit/rpc-transport.c index b3651ad..1382082 100644 --- a/p11-kit/rpc-transport.c +++ b/p11-kit/rpc-transport.c @@ -52,9 +52,14 @@ #include <fcntl.h> #include <stdint.h> #include <stdlib.h> +#include <stdio.h> #include <string.h> +#include <limits.h> #ifdef OS_UNIX +#ifdef __linux__ +# include <sys/prctl.h> +#endif #include <sys/socket.h> #include <sys/wait.h> #include <sys/un.h> @@ -73,6 +78,7 @@ typedef struct { /* Never changes */ int fd; + pid_t pid; /* Protected by the lock */ p11_mutex_t write_lock; @@ -108,6 +114,56 @@ rpc_socket_new (int fd) return sock; } +static rpc_socket * +rpc_socket_file_new (const char *file) +{ + rpc_socket *sock; + struct sockaddr_un sa; + int ret, e; + + memset(&sa, 0, sizeof(sa)); + sa.sun_family = AF_UNIX; + + /* skip a potential '@' */ + if (file[0] == '@') + file++; + snprintf(sa.sun_path, sizeof(sa.sun_path), "%s", file); + + sock = calloc (1, sizeof (rpc_socket)); + return_val_if_fail (sock != NULL, NULL); + + sock->fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (sock->fd == -1) { + free(sock); + p11_message ("could not open socket"); + return NULL; + } + + p11_debug ("connecting to: %s", file); + + /* try to connect to child */ + ret = connect(sock->fd, (struct sockaddr *)&sa, sizeof(sa)); + if (ret == -1) { + e = errno; + close(sock->fd); + free(sock); + p11_message ("could not connect to socket: '%s': %s", file, strerror(e)); + return NULL; + } + + p11_debug ("connected to: %s", file); + + sock->last_code = 0x10; + sock->read_creds = false; + sock->sent_creds = false; + sock->refs = 1; + + p11_mutex_init (&sock->write_lock); + p11_mutex_init (&sock->read_lock); + + return sock; +} + #if 0 static rpc_socket * rpc_socket_ref (rpc_socket *sock) @@ -636,6 +692,8 @@ typedef struct { p11_rpc_transport base; p11_array *argv; pid_t pid; + bool is_socket; /* in that case sfile should be used */ + char sfile[_POSIX_PATH_MAX]; } rpc_exec; static void @@ -679,7 +737,7 @@ rpc_exec_wait_or_terminate (pid_t pid) } static void -rpc_exec_disconnect (p11_rpc_client_vtable *vtable, +rpc_disconnect (p11_rpc_client_vtable *vtable, void *fini_reserved) { rpc_exec *rex = (rpc_exec *)vtable; @@ -687,9 +745,11 @@ rpc_exec_disconnect (p11_rpc_client_vtable *vtable, if (rex->base.socket) rpc_socket_close (rex->base.socket); - if (rex->pid) - rpc_exec_wait_or_terminate (rex->pid); - rex->pid = 0; + if (rex->is_socket == 0) { + if (rex->pid) + rpc_exec_wait_or_terminate (rex->pid); + rex->pid = 0; + } /* Do the common disconnect stuff */ rpc_transport_disconnect (vtable, fini_reserved); @@ -764,13 +824,54 @@ rpc_exec_connect (p11_rpc_client_vtable *vtable, return CKR_OK; } +static CK_RV +rpc_socket_file_connect (p11_rpc_client_vtable *vtable, + void *init_reserved) +{ + rpc_exec *rex = (rpc_exec *)vtable; + int errn; + unsigned char dummy = 1; + uint32_t upid; + struct iovec iov[2]; + + p11_debug ("connecting to socket: %s", (char *)rex->sfile); + + rex->base.socket = rpc_socket_file_new (rex->sfile); + return_val_if_fail (rex->base.socket != NULL, CKR_GENERAL_ERROR); + + /* this is read as version from the peer --nmav */ + if (write_all (rex->base.socket->fd, &dummy, 1) != 1) { + p11_message_err (errno, "couldn't send version"); + return CKR_DEVICE_ERROR; + } + rex->base.socket->sent_creds = true; + + iov[0].iov_base = &dummy; + iov[0].iov_len = 1; + iov[1].iov_base = &upid; + iov[1].iov_len = 4; + + errn = readv(rex->base.socket->fd, iov, 2); + if (errn != 5) { + p11_message_err (errno, "couldn't read version: %d", errn); + return CKR_DEVICE_ERROR; + } + rex->base.socket->read_creds = true; + rex->base.socket->pid = upid; + + return CKR_OK; +} + static void -rpc_exec_free (void *data) +rpc_free (void *data) { rpc_exec *rex = data; - rpc_exec_disconnect (data, NULL); + rpc_disconnect (data, NULL); rpc_transport_uninit (&rex->base); - p11_array_free (rex->argv); + if (rex->is_socket) + remove(rex->sfile); + else + p11_array_free (rex->argv); free (rex); } @@ -805,14 +906,36 @@ rpc_exec_init (const char *remote, rex->argv = argv; rex->base.vtable.connect = rpc_exec_connect; - rex->base.vtable.disconnect = rpc_exec_disconnect; + rex->base.vtable.disconnect = rpc_disconnect; rex->base.vtable.transport = rpc_transport_buffer; - rpc_transport_init (&rex->base, name, rpc_exec_free); + rex->is_socket = 0; + rpc_transport_init (&rex->base, name, rpc_free); p11_debug ("initialized rpc exec: %s", remote); return &rex->base; } +static p11_rpc_transport * +rpc_socket_file_init (const char *remote, + const char *name) +{ + rpc_exec *rex; + + rex = calloc (1, sizeof (rpc_exec)); + return_val_if_fail (rex != NULL, NULL); + + snprintf(rex->sfile, sizeof(rex->sfile), "%s", remote); + + rex->base.vtable.connect = rpc_socket_file_connect; + rex->base.vtable.disconnect = rpc_disconnect; + rex->base.vtable.transport = rpc_transport_buffer; + rex->is_socket = 1; + rpc_transport_init (&rex->base, name, rpc_free); + + p11_debug ("initialized rpc socket: %s", rex->sfile); + return &rex->base; +} + #endif /* OS_UNIX */ p11_rpc_transport * @@ -834,9 +957,10 @@ p11_rpc_transport_new (p11_virtual *virt, /* This is a command we can execute */ if (remote[0] == '|') { rpc = rpc_exec_init (remote + 1, name); - + } else if (remote[0] == '@') { + rpc = rpc_socket_file_init (remote + 1, name); } else { - p11_message ("remote not supported: %s", remote); + p11_message ("remote option not supported: %s", remote); return NULL; } diff --git a/p11-kit/rpc.h b/p11-kit/rpc.h index b129e61..4b169dc 100644 --- a/p11-kit/rpc.h +++ b/p11-kit/rpc.h @@ -59,7 +59,8 @@ struct _p11_rpc_client_vtable { bool p11_rpc_client_init (p11_virtual *virt, p11_rpc_client_vtable *vtable); -bool p11_rpc_server_handle (CK_X_FUNCTION_LIST *funcs, +bool p11_rpc_server_handle (const char *name, + CK_X_FUNCTION_LIST *funcs, p11_buffer *request, p11_buffer *response); diff --git a/p11-kit/test-log.c b/p11-kit/test-log.c index e7dab70..8af5ef6 100644 --- a/p11-kit/test-log.c +++ b/p11-kit/test-log.c @@ -103,7 +103,7 @@ main (int argc, p11_library_init (); mock_module_init (); - test_mock_add_tests ("/log"); + test_mock_add_tests ("/log", 0); p11_kit_be_quiet (); p11_log_output = false; diff --git a/p11-kit/test-managed.c b/p11-kit/test-managed.c index fc673ea..616aff1 100644 --- a/p11-kit/test-managed.c +++ b/p11-kit/test-managed.c @@ -263,7 +263,7 @@ main (int argc, p11_test (test_fork_and_reinitialize, "/managed/fork-and-reinitialize"); #endif - test_mock_add_tests ("/managed"); + test_mock_add_tests ("/managed", 0); p11_kit_be_quiet (); diff --git a/p11-kit/test-mock.c b/p11-kit/test-mock.c index 8454f1f..4171ff5 100644 --- a/p11-kit/test-mock.c +++ b/p11-kit/test-mock.c @@ -67,6 +67,27 @@ test_get_info (void) } static void +test_get_info_no_mid (void) +{ + CK_FUNCTION_LIST_PTR module; + CK_INFO info; + CK_RV rv; + + module = setup_mock_module (NULL); + + rv = (module->C_GetInfo) (&info); + assert_num_eq (rv, CKR_OK); + assert_num_eq (MOCK_INFO.cryptokiVersion.major, info.cryptokiVersion.major); + assert_num_eq (MOCK_INFO.cryptokiVersion.minor, info.cryptokiVersion.minor); + assert_num_eq (MOCK_INFO.flags, info.flags); + assert (memcmp (MOCK_INFO.libraryDescription, info.libraryDescription, sizeof (info.libraryDescription)) == 0); + assert_num_eq (MOCK_INFO.libraryVersion.major, info.libraryVersion.major); + assert_num_eq (MOCK_INFO.libraryVersion.minor, info.libraryVersion.minor); + + teardown_mock_module (module); +} + +static void test_get_slot_list (void) { CK_FUNCTION_LIST_PTR module; @@ -1638,10 +1659,13 @@ test_random (void) } static void -test_mock_add_tests (const char *prefix) +test_mock_add_tests (const char *prefix, unsigned no_mid) { p11_fixture (NULL, NULL); - p11_test (test_get_info, "%s/test_get_info", prefix); + if (no_mid == 0) + p11_test (test_get_info, "%s/test_get_info", prefix); + else + p11_test (test_get_info_no_mid, "%s/test_get_info", prefix); p11_test (test_get_slot_list, "%s/test_get_slot_list", prefix); p11_test (test_get_slot_info, "%s/test_get_slot_info", prefix); p11_test (test_get_token_info, "%s/test_get_token_info", prefix); diff --git a/p11-kit/test-proxy.c b/p11-kit/test-proxy.c index e4998be..63ffa4f 100644 --- a/p11-kit/test-proxy.c +++ b/p11-kit/test-proxy.c @@ -189,7 +189,7 @@ main (int argc, p11_test (test_initialize_finalize, "/proxy/initialize-finalize"); p11_test (test_initialize_multiple, "/proxy/initialize-multiple"); - test_mock_add_tests ("/proxy"); + test_mock_add_tests ("/proxy", 0); return p11_test_run (argc, argv); } diff --git a/p11-kit/test-rpc.c b/p11-kit/test-rpc.c index c9f8333..b238a6a 100644 --- a/p11-kit/test-rpc.c +++ b/p11-kit/test-rpc.c @@ -394,7 +394,7 @@ rpc_transport (p11_rpc_client_vtable *vtable, assert_str_eq (vtable->data, "vtable-data"); /* Just pass directly to the server code */ - ret = p11_rpc_server_handle (&base.funcs, request, response); + ret = p11_rpc_server_handle (NULL, &base.funcs, request, response); assert (ret == true); return CKR_OK; @@ -1055,7 +1055,7 @@ main (int argc, p11_test (test_fork_and_reinitialize, "/rpc/fork-and-reinitialize"); #endif - test_mock_add_tests ("/rpc"); + test_mock_add_tests ("/rpc", 0); return p11_test_run (argc, argv); } diff --git a/p11-kit/test-transport-isolated.c b/p11-kit/test-transport-isolated.c new file mode 100644 index 0000000..b689fd9 --- /dev/null +++ b/p11-kit/test-transport-isolated.c @@ -0,0 +1,329 @@ +/* + * Copyright (c) 2012 Stefan Walter + * Copyright (c) 2012-2014 Red Hat Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * * The names of contributors to this software may not be + * used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * Author: Stef Walter <[email protected]> + */ + +#include "config.h" +#include "test.h" + +#include "library.h" +#include "mock.h" +#include "path.h" +#include "private.h" +#include "p11-kit.h" +#include "rpc.h" + +#include <sys/types.h> +#ifdef OS_UNIX +#include <sys/wait.h> +#endif +#include <stdlib.h> +#include <stdio.h> +#include <sys/time.h> + +static int initialized = 0; + +struct { + char *directory; + char *user_config; + char *user_modules; + pid_t pid; +} test; + +static void +setup_remote (void *unused) +{ + char *data; + char name[64+sizeof(BUILDDIR)]; + pid_t pid; + struct timeval tv; + + test.pid = -1; + + test.directory = p11_test_directory ("p11-test-config"); + test.user_modules = p11_path_build (test.directory, "modules", NULL); +#ifdef OS_UNIX + if (mkdir (test.user_modules, 0700) < 0) +#else + if (mkdir (test.user_modules) < 0) +#endif + assert_not_reached (); + + data = "user-config: only\n"; + test.user_config = p11_path_build (test.directory, "pkcs11.conf", NULL); + p11_test_file_write (NULL, test.user_config, data, strlen (data)); + + setenv ("P11_KIT_PRIVATEDIR", BUILDDIR, 1); + if (initialized == 0) { + gettimeofday(&tv, NULL); + snprintf(name, sizeof(name), "sock.%u.%u", + (unsigned)tv.tv_sec, (unsigned)tv.tv_usec); + data = p11_path_build (test.directory, name, NULL); + + pid = fork(); + switch(pid) { + case -1: + assert_not_reached (); + case 0: + if (execl(BUILDDIR"/p11-kit/p11-kit", BUILDDIR"/p11-kit/p11-kit", "server", "-f", + BUILDDIR"/.libs/mock-two.so", "-s", name, "-t", "30", NULL) == -1) + assert_not_reached (); + exit(0); + default: + break; + } + + test.pid = pid; + if (asprintf(&data, "remote: @%s", name) == -1) + assert_not_reached(); + p11_test_file_write (test.user_modules, "remote.module", data, strlen (data)); + initialized = 1; + } + + p11_config_user_modules = test.user_modules; + p11_config_user_file = test.user_config; +} + +static void +teardown_remote (void *unused) +{ + p11_test_directory_delete (test.user_modules); + p11_test_directory_delete (test.directory); + + free (test.directory); + free (test.user_config); + free (test.user_modules); +} + +static CK_FUNCTION_LIST * +setup_mock_module (CK_SESSION_HANDLE *session) +{ + CK_FUNCTION_LIST **modules; + CK_FUNCTION_LIST *module; + CK_RV rv; + int i; + + setup_remote (NULL); + + modules = p11_kit_modules_load (NULL, 0); + + module = p11_kit_module_for_name (modules, "remote"); + assert (module != NULL); + + rv = p11_kit_module_initialize (module); + assert_num_eq (rv, CKR_OK); + + if (session) { + rv = (module->C_OpenSession) (MOCK_SLOT_ONE_ID, CKF_RW_SESSION | CKF_SERIAL_SESSION, + NULL, NULL, session); + assert (rv == CKR_OK); + } + + /* Release all the other modules */ + for (i = 0; modules[i] != NULL; i++) { + if (modules[i] != module) + p11_kit_module_release (modules[i]); + } + + free (modules); + return module; +} + +static void +teardown_mock_module (CK_FUNCTION_LIST *module) +{ + p11_kit_module_finalize (module); + teardown_remote (NULL); +} + +static void +test_basic_exec (void) +{ + CK_FUNCTION_LIST **modules; + CK_FUNCTION_LIST *module; + CK_RV rv; + + modules = p11_kit_modules_load (NULL, 0); + + module = p11_kit_module_for_name (modules, "remote"); + assert (module != NULL); + + rv = p11_kit_module_initialize (module); + assert_num_eq (rv, CKR_OK); + + rv = p11_kit_module_finalize (module); + assert_num_eq (rv, CKR_OK); + + p11_kit_modules_release (modules); + initialized = 0; +} + +static void * +invoke_in_thread (void *arg) +{ + CK_FUNCTION_LIST *rpc_module = arg; + CK_INFO info; + CK_RV rv; + + rv = (rpc_module->C_GetInfo) (&info); + assert_num_eq (rv, CKR_OK); + + assert (memcmp (info.libraryDescription, MOCK_INFO.libraryDescription, + sizeof (info.libraryDescription)) == 0); + + return NULL; +} + +static void +test_simultaneous_functions (void) +{ + CK_FUNCTION_LIST **modules; + CK_FUNCTION_LIST *module; + const int num_threads = 128; + p11_thread_t threads[num_threads]; + int i, ret; + CK_RV rv; + + modules = p11_kit_modules_load (NULL, 0); + + module = p11_kit_module_for_name (modules, "remote"); + assert (module != NULL); + + rv = p11_kit_module_initialize (module); + assert_num_eq (rv, CKR_OK); + + for (i = 0; i < num_threads; i++) { + ret = p11_thread_create (threads + i, invoke_in_thread, module); + assert_num_eq (0, ret); + } + + for (i = 0; i < num_threads; i++) + p11_thread_join (threads[i]); + + rv = p11_kit_module_finalize (module); + assert_num_eq (rv, CKR_OK); + + p11_kit_modules_release (modules); + initialized = 0; +} + +#ifdef OS_UNIX + +static void +test_fork_and_reinitialize (void) +{ + CK_FUNCTION_LIST **modules; + CK_FUNCTION_LIST *module; + CK_INFO info; + int status; + CK_RV rv; + pid_t pid; + int i; + + modules = p11_kit_modules_load (NULL, 0); + + module = p11_kit_module_for_name (modules, "remote"); + assert (module != NULL); + + rv = p11_kit_module_initialize (module); + assert_num_eq (rv, CKR_OK); + + pid = fork (); + assert_num_cmp (pid, >=, 0); + + /* The child */ + if (pid == 0) { + rv = (module->C_Initialize) (NULL); + assert_num_eq (CKR_OK, rv); + + for (i = 0; i < 32; i++) { + rv = (module->C_GetInfo) (&info); + assert_num_eq (CKR_OK, rv); + } + + rv = (module->C_Finalize) (NULL); + assert_num_eq (CKR_OK, rv); + + _exit (66); + } + + for (i = 0; i < 128; i++) { + rv = (module->C_GetInfo) (&info); + assert_num_eq (CKR_OK, rv); + } + + assert_num_eq (waitpid (pid, &status, 0), pid); + assert_num_eq (WEXITSTATUS (status), 66); + + rv = p11_kit_module_finalize (module); + assert_num_eq (rv, CKR_OK); + + p11_kit_modules_release (modules); + initialized = 0; +} + +#endif /* OS_UNIX */ + +#include "test-mock.c" + +int +main (int argc, + char *argv[]) +{ + CK_MECHANISM_TYPE mechanisms[] = { + CKM_MOCK_CAPITALIZE, + CKM_MOCK_PREFIX, + CKM_MOCK_GENERATE, + CKM_MOCK_WRAP, + CKM_MOCK_DERIVE, + CKM_MOCK_COUNT, + 0, + }; + + p11_library_init (); + + /* Override the mechanisms that the RPC mechanism will handle */ + p11_rpc_mechanisms_override_supported = mechanisms; + + p11_fixture (setup_remote, teardown_remote); + p11_test (test_basic_exec, "/transport/basic"); + p11_test (test_simultaneous_functions, "/transport/simultaneous-functions"); + +#ifdef OS_UNIX + p11_test (test_fork_and_reinitialize, "/transport/fork-and-reinitialize"); +#endif + + test_mock_add_tests ("/transport", 1); + + return p11_test_run (argc, argv); +} diff --git a/p11-kit/test-transport.c b/p11-kit/test-transport.c index 397a65a..5fc7c47 100644 --- a/p11-kit/test-transport.c +++ b/p11-kit/test-transport.c @@ -288,7 +288,7 @@ main (int argc, p11_test (test_fork_and_reinitialize, "/transport/fork-and-reinitialize"); #endif - test_mock_add_tests ("/transport"); + test_mock_add_tests ("/transport", 0); return p11_test_run (argc, argv); } -- 1.9.3
_______________________________________________ p11-glue mailing list [email protected] http://lists.freedesktop.org/mailman/listinfo/p11-glue
