/* * 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 #include #include #include #include #include #include #include #include #include #include /* For logging functions: */ #include "pkcs11types.h" #include "defs.h" #include "host_defs.h" #include "h_extern.h" #include "trace.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) { 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; /* * 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; } /* 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; } } } /* * 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; } /* * 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 real_len indicates the new token data format is used. * If no application is attached to the shm (ref==1) it can be * safely expanded/recreated. Otherwise, fail. */ 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) { strcpy(ctx->name, name); 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 + 2] = { 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) { strncpy(name, ctx->name, SM_NAME_LEN + 2); 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; }