You've already forked opencryptoki
mirror of
https://github.com/opencryptoki/opencryptoki.git
synced 2026-10-24 05:39:17 +03:00
The token shared memory segment contains an array of TOK_OBJ_ENTRY structures. The TOK_OBJ_ENTRY structure contains an unused field 'deleted'. Remove this to reduce the size of the array. This reduces the size of the shared memory segment, so the sm_open() function must be prepared to handle this size change. In case the shared memory segment exists already, but has a lager size (i.e. from a run before this code change), then truncate the shared memory segment and clear it. This can only be done when no other process is currently using the shared memory segment. Normally the very first PKCS#11 process that runs after migrating to a version that contains this code change is the one and only process that accesses the shared memory segment at that time. The process holds the token-lock when sm_open() is called, so no other process can perform sm_open() at the same time. If another process already did sm_open() then the shared memory segment size has already been adjusted by that process. While it at, also optimize the shared memory lookup for token objects that already exist when a token is initialized, but the shared memory segment is not yet populated, as well as for newly created token objects. Remember the index of the shared memory array element inside the object when function object_mgr_add_to_shm() adds the object to the array. That way a subsequent object_mgr_search_shm_for_obj() will find the object quickly in the array, unless the array has been updated by another process in the meantime. Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
477 lines
13 KiB
C
477 lines
13 KiB
C
/*
|
|
* COPYRIGHT (c) International Business Machines Corp. 2012-2017
|
|
*
|
|
* 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
|
|
*/
|
|
|
|
/*
|
|
* OpenCryptoki ICSF token - Shared memory abstraction for OpenCryptoki
|
|
*
|
|
* Author: Marcelo Cerri (mhcerri@br.ibm.com)
|
|
*
|
|
* Note: the functions in this files are implemented as an abstraction layer for
|
|
* POSIX shared memory functions but they can be extended to support other
|
|
* APIs of shared memory.
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/mman.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <stddef.h>
|
|
#include <string.h>
|
|
#include <syslog.h>
|
|
#include <grp.h>
|
|
|
|
/* For logging functions: */
|
|
#include "pkcs11types.h"
|
|
#include "defs.h"
|
|
#include "host_defs.h"
|
|
#include "h_extern.h"
|
|
#include "trace.h"
|
|
#include "ock_syslog.h"
|
|
|
|
#include "shared_memory.h"
|
|
|
|
/*
|
|
* Helper macros for logging.
|
|
*/
|
|
#define SYS_ERROR(_errno, _msg, ...) \
|
|
do { \
|
|
char _sys_error[1024]; \
|
|
if (strerror_r(_errno, _sys_error, sizeof(_sys_error))) \
|
|
strcpy(_sys_error, "Unknown error"); \
|
|
syslog(LOG_ERR, "Error: " _msg " %s (errno=%d)", \
|
|
__VA_ARGS__, _sys_error, _errno); \
|
|
TRACE_ERROR("Error: " _msg " %s (errno=%d)", \
|
|
__VA_ARGS__, _sys_error, _errno); \
|
|
} while (0)
|
|
|
|
/*
|
|
* Shared context used to keep meta data into the shared memory.
|
|
*
|
|
* The address to data field is the address visible outside this file.
|
|
*
|
|
* See shm_open for more details.
|
|
*/
|
|
struct shm_context {
|
|
/*
|
|
* `ref` is a counter that is used to detect mismatching between
|
|
* sm_open and sm_close calls.
|
|
*/
|
|
int ref;
|
|
|
|
/*
|
|
* `name` is the shared memory identifier and it is used to destroy
|
|
* an allocated shared memory region.
|
|
*/
|
|
char name[SM_NAME_LEN + 1];
|
|
|
|
/*
|
|
* `data_len` is the length of the variable length filed `data`.
|
|
*/
|
|
int data_len;
|
|
|
|
/*
|
|
* `data` points to the region that will actually be used by
|
|
* `sm_open`s caller.
|
|
*/
|
|
char data[];
|
|
};
|
|
|
|
/*
|
|
* Obtain the context pointer based on a data address.
|
|
*/
|
|
static inline struct shm_context *get_shm_context(void *addr)
|
|
{
|
|
struct shm_context *ctx;
|
|
|
|
ctx = (struct shm_context *)((unsigned char *)addr
|
|
- offsetof(struct shm_context, data));
|
|
return ctx;
|
|
}
|
|
|
|
/*
|
|
* If a complete path is informed, convert it to a shared memory name.
|
|
*/
|
|
static char *convert_path_to_shm_name(const char *file_path)
|
|
{
|
|
char *name = NULL;
|
|
size_t len = strlen(file_path) + 1;
|
|
int i;
|
|
char *it;
|
|
|
|
/* Need a starting '/' */
|
|
if (file_path[0] != '/')
|
|
len++;
|
|
|
|
if (len > SM_NAME_LEN) {
|
|
TRACE_ERROR("Error: path \"%s\" too long.\n", file_path);
|
|
return NULL;
|
|
}
|
|
|
|
it = name = malloc(len + 1);
|
|
if (name == NULL) {
|
|
TRACE_ERROR("Error: failed to allocate memory for "
|
|
"path \"%s\".\n", file_path);
|
|
return NULL;
|
|
}
|
|
|
|
i = 0;
|
|
*it++ = '/';
|
|
if (file_path[0] == '/')
|
|
i++;
|
|
|
|
for (; file_path[i]; i++, it++) {
|
|
if (file_path[i] == '/')
|
|
*it = '.';
|
|
else
|
|
*it = file_path[i];
|
|
}
|
|
*it = '\0';
|
|
|
|
TRACE_DEVEL("File path \"%s\" converted to \"%s\".\n", file_path, name);
|
|
|
|
return name;
|
|
}
|
|
|
|
/*
|
|
* Open a shared memory region identified by `sm_name` using permissions defined
|
|
* by `mode` with length `len`.
|
|
*
|
|
* If the shared memory already exists and doesn't match the given length an
|
|
* error is returned (if `force` is zero) or the shared memory is reinitialized
|
|
* (if `force` is non zero).
|
|
*/
|
|
int sm_open(const char *sm_name, int mode, void **p_addr, size_t len, int force,
|
|
const char *group)
|
|
{
|
|
int rc;
|
|
int fd = -1;
|
|
void *addr = NULL;
|
|
struct stat stat_buf;
|
|
char *name = NULL;
|
|
struct shm_context *ctx = NULL;
|
|
size_t real_len = sizeof(*ctx) + len;
|
|
int created = 0;
|
|
struct group *grp;
|
|
|
|
/*
|
|
* This is used for portability purpose. Please check `shm_open`
|
|
* man page for more details.
|
|
*/
|
|
if ((name = convert_path_to_shm_name(sm_name)) == NULL) {
|
|
rc = -EINVAL;
|
|
goto done;
|
|
}
|
|
|
|
if (group == NULL || group[0] == '\0')
|
|
group = PKCS_GROUP;
|
|
|
|
grp = getgrnam(group);
|
|
if (!grp) {
|
|
rc = -errno;
|
|
SYS_ERROR(errno, "getgrname(\"%s\"): %s\n", group,
|
|
strerror(errno));
|
|
goto done;
|
|
}
|
|
|
|
/* try and open first... */
|
|
fd = shm_open(name, O_RDWR, mode);
|
|
if (fd < 0) {
|
|
/* maybe it needs to be created ... */
|
|
fd = shm_open(name, O_RDWR | O_CREAT, mode);
|
|
if (fd < 0) {
|
|
rc = -errno;
|
|
SYS_ERROR(errno, "Failed to open shared memory \"%s\".\n", name);
|
|
goto done;
|
|
} else {
|
|
/* umask may have altered permissions if we created
|
|
* the shared memory in above call, so set proper
|
|
* permissions just in case.
|
|
*/
|
|
if (fchmod(fd, mode) == -1) {
|
|
rc = -errno;
|
|
SYS_ERROR(errno, "fchmod(%s): %s\n", name, strerror(errno));
|
|
goto done;
|
|
}
|
|
if (fchown(fd, -1, grp->gr_gid) != 0) {
|
|
rc = -errno;
|
|
SYS_ERROR(errno, "fchown of token shm segment: %s\n",
|
|
strerror(errno));
|
|
goto done;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* fstat is used here to check if the shared memory region already
|
|
* exists. When a shared memory region is first created, its size is
|
|
* always zero.
|
|
*/
|
|
if (fstat(fd, &stat_buf)) {
|
|
rc = -errno;
|
|
SYS_ERROR(errno, "Cannot stat \"%s\".\n", name);
|
|
goto done;
|
|
}
|
|
|
|
/*
|
|
* If the shared memory segment does not belong to the pkcs11 group or does
|
|
* not have correct permissions, do not use it.
|
|
*/
|
|
if (stat_buf.st_gid != grp->gr_gid ||
|
|
(stat_buf.st_mode & ~S_IFMT) != (unsigned int)mode) {
|
|
TRACE_ERROR("SHM segment '%s' has wrong gid/mode combination "
|
|
"(expected: %u/0%o; got: %u/0%o)\n",
|
|
name, grp->gr_gid, mode, stat_buf.st_gid, stat_buf.st_mode);
|
|
OCK_SYSLOG(LOG_ERR,
|
|
"SHM segment '%s' has wrong gid/mode combination "
|
|
"(expected: %u/0%o; got: %u/0%o)\n",
|
|
name, grp->gr_gid, mode, stat_buf.st_gid, stat_buf.st_mode);
|
|
rc = -EINVAL;
|
|
goto done;
|
|
}
|
|
|
|
/*
|
|
* The shared memory needs to be extended when created (when its length
|
|
* is zero). When its length is not zero and is not equal to the
|
|
* expected size, an error is returned if `force` is not set. If `force`
|
|
* is set, the existing shared memory is truncated and any data on it is
|
|
* lost.
|
|
*/
|
|
if (stat_buf.st_size == 0
|
|
|| (force && (size_t)stat_buf.st_size != real_len)) {
|
|
/*
|
|
* If the shared memory region was just created, it's necessary
|
|
* to extend it to the expected size using ftruncate.
|
|
*
|
|
* It's important to notice that it is resized to a length
|
|
* greater than the value requested (`len`). The extra space is
|
|
* used to store additional information related to the shared
|
|
* memory, such as its size and identifier.
|
|
*/
|
|
created = 1;
|
|
TRACE_DEVEL("Truncating \"%s\".\n", name);
|
|
if (ftruncate(fd, real_len) < 0) {
|
|
rc = -errno;
|
|
SYS_ERROR(errno, "Cannot truncate \"%s\".\n", name);
|
|
goto done;
|
|
}
|
|
} else if ((size_t)stat_buf.st_size != real_len) {
|
|
int ref;
|
|
|
|
/* get ref count */
|
|
addr = mmap(NULL, sizeof(*ctx), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
|
if (addr == NULL) {
|
|
rc = -errno;
|
|
SYS_ERROR(errno, "Failed to map \"%s\" to memory.\n", name);
|
|
goto done;
|
|
}
|
|
ctx = addr;
|
|
ref = ctx->ref;
|
|
if (munmap(addr, sizeof(*ctx))) {
|
|
rc = -errno;
|
|
SYS_ERROR(errno, "Failed to unmap \"%s\" (%p).\n", name, (void *)ctx);
|
|
goto done;;
|
|
}
|
|
|
|
/*
|
|
* Too big or too small real_len indicates the new token data format
|
|
* is used, or the desired size of the shared memory segment has
|
|
* changed by other means.
|
|
* If no application is attached to the shm (ref==1) it can be
|
|
* safely expanded/recreated. Otherwise, fail.
|
|
* The caller must have the token xproc-lock, so it is save to check
|
|
* the reference counter here.
|
|
*/
|
|
if (ref <= 1 && real_len != (size_t)stat_buf.st_size) {
|
|
created = 1;
|
|
TRACE_DEVEL("Truncating \"%s\".\n", name);
|
|
if (ftruncate(fd, real_len) < 0) {
|
|
rc = -errno;
|
|
SYS_ERROR(errno, "Cannot truncate \"%s\".\n", name);
|
|
goto done;
|
|
}
|
|
} else {
|
|
rc = -1;
|
|
TRACE_ERROR("Error: shared memory \"%s\" exists and does not "
|
|
"match the expected size.\n", name);
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
addr = mmap(NULL, real_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
|
if (addr == NULL) {
|
|
rc = -errno;
|
|
SYS_ERROR(errno, "Failed to map \"%s\" to memory.\n", name);
|
|
goto done;
|
|
}
|
|
|
|
/*
|
|
* `addr` points to the start of the shared memory region. The first
|
|
* bytes of it are used to store the additional information about the
|
|
* shared memory allocated. This info can be accessed using an pointer
|
|
* to a `shm_context` structure.
|
|
*/
|
|
ctx = addr;
|
|
if (created) {
|
|
strncpy(ctx->name, name, SM_NAME_LEN);
|
|
ctx->name[SM_NAME_LEN] = '\0';
|
|
ctx->data_len = len;
|
|
memset(ctx->data, 0, ctx->data_len);
|
|
ctx->ref = 0;
|
|
}
|
|
ctx->ref += 1;
|
|
|
|
/*
|
|
* The portion of the shared memory that will actually be used by the
|
|
* called is pointed by `data`.
|
|
*
|
|
* The address returned (in this case `ctx->data`) is equal to
|
|
* `addr + sizeof(struct shm_context)`, which means that the additional
|
|
* data is skipped off.
|
|
*/
|
|
*p_addr = ctx->data;
|
|
rc = created ? 0 : 1;
|
|
if (sm_sync(ctx->data)) {
|
|
rc = -errno;
|
|
SYS_ERROR(errno, "Failed to sync shared memory \"%s\".\n", name);
|
|
if (created)
|
|
sm_close(addr, 1, 0);
|
|
goto done;
|
|
}
|
|
TRACE_DEVEL("open: ref = %d\n", ctx->ref);
|
|
|
|
done:
|
|
if (fd >= 0)
|
|
close(fd);
|
|
if (name)
|
|
free(name);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* Close (unmap) a shared memory region. `destroy` indicates if the shared
|
|
* memory should be destroyed if no other processes are using it.
|
|
* 'ignore_ref_count' indicates that the reference count in the shared memory
|
|
* should not be decremented (used during termination after a fork).
|
|
*/
|
|
int sm_close(void *addr, int destroy, int ignore_ref_count)
|
|
{
|
|
int rc;
|
|
int ref;
|
|
char name[SM_NAME_LEN + 1] = { 0, };
|
|
struct shm_context *ctx = get_shm_context(addr);
|
|
|
|
if (ctx->ref <= 0) {
|
|
TRACE_ERROR("Error: invalid shared memory address %p (ref=%d).\n",
|
|
addr, ctx->ref);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!ignore_ref_count)
|
|
ctx->ref--;
|
|
ref = ctx->ref;
|
|
|
|
TRACE_DEVEL("close: ref = %d\n", ref);
|
|
if (ref == 0 && destroy) {
|
|
memcpy(name, ctx->name, SM_NAME_LEN);
|
|
name[SM_NAME_LEN] = '\0';
|
|
}
|
|
|
|
if (munmap(ctx, sizeof(*ctx) + ctx->data_len)) {
|
|
rc = -errno;
|
|
SYS_ERROR(errno, "Failed to unmap \"%s\" (%p).\n", name, (void *)ctx);
|
|
return rc;
|
|
}
|
|
|
|
if (ref == 0 && destroy) {
|
|
TRACE_DEVEL("Deleting shared memory \"%s\".\n", name);
|
|
if ((rc = sm_destroy(name)) != 0)
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Destroy a shared memory region.
|
|
*/
|
|
int sm_destroy(const char *name)
|
|
{
|
|
int rc;
|
|
|
|
if (shm_unlink(name)) {
|
|
rc = -errno;
|
|
SYS_ERROR(errno, "Failed to delete shared memory \"%s\".\n", name);
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Force sync for a shared memory region.
|
|
*/
|
|
int sm_sync(void *addr)
|
|
{
|
|
struct shm_context *ctx = get_shm_context(addr);
|
|
|
|
if (ctx->ref <= 0) {
|
|
TRACE_ERROR("Error: invalid shared memory address %p (ref=%d).\n",
|
|
addr, ctx->ref);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return msync(ctx, ctx->data_len, MS_SYNC);
|
|
}
|
|
|
|
/*
|
|
* Get the name of the shared memory indicated by `addr` and copy it to the
|
|
* given `buffer`.
|
|
*/
|
|
int sm_copy_name(void *addr, char *buffer, size_t len)
|
|
{
|
|
size_t name_len;
|
|
struct shm_context *ctx = get_shm_context(addr);
|
|
|
|
if (ctx->ref <= 0) {
|
|
TRACE_ERROR("Error: invalid shared memory address %p (ref=%d).\n",
|
|
addr, ctx->ref);
|
|
return -EINVAL;
|
|
}
|
|
|
|
name_len = strlen(ctx->name);
|
|
if (len <= name_len)
|
|
return -ENOSPC;
|
|
|
|
strcpy(buffer, ctx->name);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Return the reference count for the given shared memory.
|
|
*/
|
|
int sm_get_count(void *addr)
|
|
{
|
|
struct shm_context *ctx = get_shm_context(addr);
|
|
|
|
if (ctx->ref <= 0) {
|
|
TRACE_ERROR("Error: invalid shared memory address %p (ref=%d).\n",
|
|
addr, ctx->ref);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return ctx->ref;
|
|
}
|