1
0
mirror of https://github.com/opencryptoki/opencryptoki.git synced 2026-10-24 05:39:17 +03:00
Files
opencryptoki/usr/lib/api/socket_client.c
Ingo Franzki 8d2c71c651 API: Limit the event payload size to EVENT_MAX_PAYLOAD_LENGTH
Don't accept an event with a payload larger than EVENT_MAX_PAYLOAD_LENGTH
(128K).

Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
2026-08-19 08:27:38 +02:00

516 lines
15 KiB
C

/*
* This program is provided under the terms of the Common Public License,
* version 1.0 (CPL-1.0). Any use, reproduction or distribution for this
* software constitutes recipient's acceptance of CPL-1.0 terms which can be
* found in the file LICENSE file or at
* https://opensource.org/licenses/cpl1.0.php
*/
/* (C) COPYRIGHT Google Inc. 2013 */
//
// Pkcs11 Api Socket client routines
//
#include <stdio.h>
#include <sys/un.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <syslog.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <grp.h>
#include <pwd.h>
#include <errno.h>
#include <stdlib.h>
#include <stdbool.h>
#include <poll.h>
#include "apiproto.h"
#include "slotmgr.h"
#include "apictl.h"
#include "trace.h"
#include "ock_syslog.h"
#include "events.h"
extern API_Proc_Struct_t *Anchor;
int connect_socket(const char *file_path)
{
int socketfd;
struct sockaddr_un daemon_address;
struct stat file_info;
struct group *grp;
struct passwd *pwd;
if (stat(file_path, &file_info)) {
OCK_SYSLOG(LOG_ERR,
"connect_socket: failed to find socket file, errno=%d",
errno);
return -1;
}
grp = getgrnam(PKCS_GROUP);
if (!grp) {
OCK_SYSLOG(LOG_ERR,
"connect_socket: %s group does not exist, errno=%d",
PKCS_GROUP, errno);
return -1;
}
pwd = getpwnam(PKCSSLOTD_USER);
if (!pwd) {
OCK_SYSLOG(LOG_ERR,
"connect_socket: %s user does not exist, errno=%d",
PKCSSLOTD_USER, errno);
return -1;
}
if (file_info.st_uid != pwd->pw_uid || file_info.st_gid != grp->gr_gid) {
OCK_SYSLOG(LOG_ERR,
"connect_socket: incorrect permissions on socket file");
return -1;
}
if ((socketfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
OCK_SYSLOG(LOG_ERR,
"connect_socket: failed to create socket, errno=%d",
errno);
return -1;
}
memset(&daemon_address, 0, sizeof(struct sockaddr_un));
daemon_address.sun_family = AF_UNIX;
strncpy(daemon_address.sun_path, file_path,
sizeof(daemon_address.sun_path) - 1);
daemon_address.sun_path[sizeof(daemon_address.sun_path) - 1] = '\0';
if (connect(socketfd, (struct sockaddr *) &daemon_address,
sizeof(struct sockaddr_un)) != 0) {
OCK_SYSLOG(LOG_ERR,
"connect_socket: failed to connect to slotmanager daemon, "
"errno=%d",
errno);
goto error;
}
return socketfd;
error:
close(socketfd);
return -1;
}
static ssize_t read_all(int socketfd, char *buffer, size_t size)
{
size_t bytes_received = 0;
ssize_t n;
while (bytes_received < size) {
n = read(socketfd, buffer + bytes_received, size - bytes_received);
if (n < 0) {
// read error
if (errno == EINTR)
continue;
return -errno;
}
if (n == 0)
break;
bytes_received += n;
}
return bytes_received;
}
static ssize_t send_all(int socketfd, char *buffer, size_t size)
{
size_t bytes_sent = 0;
ssize_t n;
while (bytes_sent < size) {
n = send(socketfd, buffer + bytes_sent, size - bytes_sent, 0);
if (n < 0) {
// send error
if (errno == EINTR)
continue;
return -errno;
}
if (n == 0)
break;
bytes_sent += n;
}
return bytes_sent;
}
//
// Will fill out the Slot_Mgr_Socket_t and Slot_Mgr_Client_Cred_t structures
// in the Anchor global data structure with the values passed by the pkcsslotd
// via a socket RPC.
int init_socket_data(int socketfd)
{
ssize_t n;
n = read_all(socketfd, (char *)&Anchor->ClientCred,
sizeof(Anchor->ClientCred));
if (n < 0) {
// read error
OCK_SYSLOG(LOG_ERR, "init_socket_data: read error \
on daemon socket, errno=%zd", -n);
return FALSE;
}
if (n != sizeof(Anchor->ClientCred)) {
// eof but we still expect some bytes
OCK_SYSLOG(LOG_ERR, "init_socket_data: read returned \
with eof but we still \
expect %lu bytes from daemon",
sizeof(Anchor->ClientCred) - n);
return FALSE;
}
n = read_all(socketfd, (char *)&Anchor->SocketDataP,
sizeof(Anchor->SocketDataP));
if (n < 0) {
// read error
OCK_SYSLOG(LOG_ERR, "init_socket_data: read error \
on daemon socket, errno=%zd", -n);
return FALSE;
}
if (n != sizeof(Anchor->SocketDataP)) {
// eof but we still expect some bytes
OCK_SYSLOG(LOG_ERR, "init_socket_data: read returned \
with eof but we still \
expect %lu bytes from daemon",
sizeof(Anchor->SocketDataP) - n);
return FALSE;
}
return TRUE;
}
static bool match_token_label_filter(event_msg_t *event, API_Slot_t *sltp)
{
if (event->token_label[0] == ' ' || event->token_label[0] == '\0')
return true;
return memcmp(event->token_label,
sltp->TokData->nv_token_data->token_info.label,
sizeof(event->token_label)) == 0;
}
struct type_model {
unsigned int type;
char model[member_size(CK_TOKEN_INFO_32, model) + 1];
};
static const struct type_model type_model_flt[] = {
{ .type = EVENT_TOK_TYPE_CCA, .model = "CCA " },
{ .type = EVENT_TOK_TYPE_EP11, .model = "EP11 " },
};
static bool match_token_type_filter(event_msg_t *event, API_Slot_t *sltp)
{
size_t i;
if (event->token_type == EVENT_TOK_TYPE_ALL)
return true;
for (i = 0; i < sizeof(type_model_flt) / sizeof(struct type_model); i++) {
if (memcmp(sltp->TokData->nv_token_data->token_info.model,
type_model_flt[i].model,
sizeof(sltp->TokData->nv_token_data->token_info.model))
== 0 &&
(event->token_type & type_model_flt[i].type) != 0)
return true;
}
return false;
}
static int handle_event(API_Proc_Struct_t *anchor, event_msg_t *event,
char *payload, event_reply_t *reply)
{
CK_SLOT_ID slotID;
API_Slot_t *sltp;
CK_RV rc;
/* If its not for our process, ignore it, don't increment reply counters */
if (event->process_id != 0 &&
event->process_id != anchor->ClientCred.real_pid)
return 0;
for (slotID = 0; slotID < NUMBER_SLOTS_MANAGED; slotID++) {
sltp = &anchor->SltList[slotID];
if (sltp->DLLoaded == FALSE || sltp->FcnList == NULL)
continue;
if (!match_token_label_filter(event, sltp))
continue;
if (!match_token_type_filter(event, sltp))
continue;
if (sltp->FcnList->ST_HandleEvent != NULL)
rc = sltp->FcnList->ST_HandleEvent(sltp->TokData, event->type,
event->flags, payload,
event->payload_len);
else
rc = CKR_FUNCTION_NOT_SUPPORTED;
TRACE_DEVEL("Slot %lu ST_HandleEvent rc: 0x%lx\n", slotID, rc);
switch (rc) {
case CKR_OK:
reply->positive_replies++;
break;
case CKR_FUNCTION_NOT_SUPPORTED:
reply->nothandled_replies++;
break;
default:
reply->negative_replies++;
break;
}
}
return 0;
}
struct cleanup_data {
API_Proc_Struct_t *anchor;
#if OPENSSL_VERSION_PREREQ(3, 0)
OSSL_LIB_CTX *prev_libctx;
#endif
};
static void event_thread_cleanup(void *arg)
{
struct cleanup_data *cleanup = arg;
#if OPENSSL_VERSION_PREREQ(3, 0)
OSSL_LIB_CTX_set0_default(cleanup->prev_libctx);
#else
UNUSED(cleanup);
#endif
TRACE_DEVEL("Event thread %lu terminating\n", pthread_self());
}
static void *event_thread(void *arg)
{
API_Proc_Struct_t *anchor = arg;
struct cleanup_data cleanup;
#if OPENSSL_VERSION_PREREQ(3, 0)
OSSL_LIB_CTX *prev_libctx;
#endif
int oldstate, oldtype;
struct pollfd pollfd;
event_msg_t event;
char *payload;
event_reply_t reply;
ssize_t num;
int rc;
UNUSED(arg);
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate);
TRACE_DEVEL("Event thread %lu running\n", pthread_self());
if (anchor->socketfd < 0) {
TRACE_ERROR("socket is already closed.\n");
TRACE_DEVEL("Event thread %lu terminating\n", pthread_self());
return NULL;
}
#if OPENSSL_VERSION_PREREQ(3, 0)
/* Ensure that the event thread uses Opencryptoki's own library context */
prev_libctx = OSSL_LIB_CTX_set0_default(Anchor->openssl_libctx);
if (prev_libctx == NULL) {
TRACE_ERROR("OSSL_LIB_CTX_set0_default failed\n");
TRACE_DEVEL("Event thread %lu terminating\n", pthread_self());
return NULL;
}
#endif
/* Enable cancellation */
cleanup.anchor = anchor;
#if OPENSSL_VERSION_PREREQ(3, 0)
cleanup.prev_libctx = prev_libctx;
#endif
pthread_cleanup_push(event_thread_cleanup, &cleanup);
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &oldtype);
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &oldstate);
pollfd.fd = anchor->socketfd;
pollfd.events = POLLIN | POLLHUP | POLLERR;
while (1) {
pollfd.revents = 0;
rc = poll(&pollfd, 1, -1);
if (rc < 0) {
if (errno == EINTR)
continue;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate);
TRACE_ERROR("poll failed: %d\n", errno);
break;
}
if (rc == 0)
continue;
if (pollfd.revents & (POLLHUP | POLLERR)) {
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate);
TRACE_ERROR("Error on socket, possibly closed by slot daemon\n");
break;
}
if ((pollfd.revents & POLLIN) == 0)
continue;
/* Disable for cancellation while we are working on an event */
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate);
TRACE_DEVEL("Receive new event ....\n");
num = read_all(anchor->socketfd, (char *)&event, sizeof(event));
if (num != sizeof(event)) {
TRACE_ERROR("Error receiving the event, rc: %zd\n", num);
break;
}
TRACE_DEBUG("Event version: %u\n", event.version);
TRACE_DEBUG("Event type: 0x%08x\n", event.type);
TRACE_DEBUG("Event flags: 0x%08x\n", event.flags);
TRACE_DEBUG("Event token_type: 0x%08x\n", event.token_type);
TRACE_DEBUG("Event token_name: '%.32s'\n", event.token_label);
TRACE_DEBUG("Event process_id: %u\n", event.process_id);
TRACE_DEBUG("Event payload_len: %u\n", event.payload_len);
if (event.version != EVENT_VERSION_1) {
TRACE_ERROR("Event version invalid: %u\n", event.version);
break;
}
payload = NULL;
if (event.payload_len > EVENT_MAX_PAYLOAD_LENGTH) {
TRACE_ERROR("Event payload_len too large: %u\n", event.payload_len);
break;
}
if (event.payload_len > 0) {
payload = malloc(event.payload_len);
if (payload == NULL) {
TRACE_ERROR("Failed to allocate buffer for event payload\n");
break;
}
num = read_all(anchor->socketfd, payload, event.payload_len);
if (num != (ssize_t)event.payload_len) {
TRACE_ERROR("Error receiving the event payload, rc: %zd\n", num);
if (payload != NULL)
free(payload);
break;
}
TRACE_DEBUG("Event payload:\n");
TRACE_DEBUG_DUMP(" ", payload, event.payload_len);
}
memset(&reply, 0, sizeof(reply));
reply.version = EVENT_VERSION_1;
rc = handle_event(anchor, &event, payload, &reply);
if (rc != 0) {
TRACE_ERROR("Error handling the event, rc: %d\n", rc);
if (payload != NULL)
free(payload);
break;
}
TRACE_DEBUG("Reply version: %u\n", reply.version);
TRACE_DEBUG("Reply positive: %u\n", reply.positive_replies);
TRACE_DEBUG("Reply negative: %u\n", reply.negative_replies);
TRACE_DEBUG("Reply not-handled: %u\n", reply.nothandled_replies);
if (event.flags & EVENT_FLAGS_REPLY_REQ) {
num = send_all(anchor->socketfd, (char *)&reply, sizeof(reply));
if (num != sizeof(reply)) {
TRACE_ERROR("Error sending the event reply, rc: %zd\n", num);
if (payload != NULL)
free(payload);
break;
}
}
if (payload != NULL)
free(payload);
/* Re-enable for and test if we got canceled in the meantime */
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &oldstate);
pthread_testcancel();
}
/*
* Close the socket if we encounter an unrecoverable error (e.g. received
* invalid event) and stop the thread because of that.
* If the thread is stopped via stop_event_thread(), then it gets canceled
* via pthread_cancel(), and will not reach this place, thus the socket is
* not closed. This is intended, and the socket will then be closed by
* C_Finalize(). The atfork 'prepare' handler in the parent process also
* stops the thread (via stop_event_thread()), and the socket must not be
* closed in this case, because the thread is restarted in the atfork
* 'parent' handler, and should continue to receive events from the
* socket.
*/
close(anchor->socketfd);
anchor->socketfd = -1;
#if OPENSSL_VERSION_PREREQ(3, 0)
OSSL_LIB_CTX_set0_default(prev_libctx);
#endif
pthread_cleanup_pop(1);
return NULL;
}
int start_event_thread(void)
{
int rc;
rc = pthread_create(&Anchor->event_thread, NULL, event_thread, Anchor);
if (rc != 0) {
OCK_SYSLOG(LOG_ERR, "start_event_thread: pthread_create failed, "
"errno=%d", rc);
TRACE_ERROR("Failed to start event thread, errno=%d\n", rc);
return rc;
}
TRACE_DEVEL("Event thread %lu has been started\n", Anchor->event_thread);
return 0;
}
int stop_event_thread(void)
{
int rc;
void *status;
TRACE_DEVEL("Canceling event thread %lu\n", Anchor->event_thread);
rc = pthread_cancel(Anchor->event_thread);
if (rc != 0 && rc != ESRCH)
return rc;
TRACE_DEVEL("Waiting for event thread %lu to terminate\n",
Anchor->event_thread);
rc = pthread_join(Anchor->event_thread, &status);
if (rc != 0)
return rc;
if (status != PTHREAD_CANCELED) {
TRACE_ERROR("Event thread was stopped, but did not return the "
"expected status\n");
}
TRACE_DEVEL("Event thread %lu has terminated\n", Anchor->event_thread);
Anchor->event_thread = 0;
return 0;
}