blob: 5dc923965c14195e315e0b72e9971ba7d8e392bc [file] [log] [blame]
/*
* Copyright (c) 2009-2010 Amit Singh. All Rights Reserved.
* http://osxbook.com
*
* TPM Emulator Device Bridge for Mac OS X
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. 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.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR “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 AUTHOR 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.
*/
#include <sys/types.h>
#include <sys/fcntl.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/ioctl.h>
#include <sys/param.h>
#include <sys/unistd.h>
#include <sys/malloc.h>
#include <sys/kpi_socket.h>
#include <sys/kpi_mbuf.h>
#include <sys/un.h>
#include <kern/locks.h>
#include <miscfs/devfs/devfs.h>
#include <mach/mach_types.h>
#include <libkern/OSAtomic.h>
#include "config.h"
/* configurable */
#define TPM_BRIDGE_NAME "tpm" /* bridge device file name (/dev/tpm) */
#define TPM_BRIDGE_MODE 0666 /* world readable/writable by default */
#define TPM_BRIDGE_UID UID_ROOT /* bridge device file owner ID */
#define TPM_BRIDGE_GID GID_WHEEL /* bridge device file group ID */
/* buffer */
static char tpm_buffer[TPM_CMD_BUF_SIZE] = { 0 };
/* locking */
static lck_grp_attr_t* tpm_mtx_grp_attr = NULL;
static lck_grp_t* tpm_mtx_grp = NULL;
static lck_attr_t* tpm_mtx_attr = NULL;
static lck_mtx_t* tpm_mtx = NULL;
/* user socket */
errno_t sock_nointerrupt(socket_t sock, int on);
static SInt32 tpm_activity = 0;
static UInt32 tpm_in_io = 0;
static socket_t tpmd_socket = 0;
static struct sockaddr_un tpmd_socket_addr = {
sizeof(struct sockaddr_un),
AF_LOCAL,
TPM_SOCKET_NAME,
};
/* device */
static int dev_tpm_index = -1;
static const int dev_tpm_minor = 0;
static void* dev_tpm_node = NULL;
d_open_t tpm_dev_open;
d_read_t tpm_dev_read;
d_write_t tpm_dev_write;
extern int seltrue(dev_t, int, struct proc*);
static struct cdevsw cdev_tpm = {
tpm_dev_open,
(d_close_t*)&nulldev,
tpm_dev_read,
tpm_dev_write,
(d_ioctl_t*)&enodev,
(d_stop_t*)&nulldev,
(d_reset_t*)&nulldev,
0,
(select_fcn_t*)seltrue,
eno_mmap,
eno_strat,
eno_getc,
eno_putc,
D_TTY,
};
static int tpmd_connect(void);
static void tpmd_disconnect(void);
kern_return_t tpm_bridge_start(kmod_info_t* ki, void* d);
kern_return_t tpm_bridge_stop(kmod_info_t* ki, void* d);
static int tpm_bridge_locking_start(void);
static int tpm_bridge_locking_stop(void);
static int tpm_bridge_devfs_start(void);
static int tpm_bridge_devfs_stop(void);
int
tpm_dev_open(dev_t dev, int flags, int devtype, struct proc* p)
{
(void)OSIncrementAtomic(&tpm_activity);
int error = 0;
lck_mtx_lock(tpm_mtx);
if ((tpmd_socket == NULL) || !sock_isconnected(tpmd_socket)) {
if (tpmd_connect() != 0) {
tpmd_socket = NULL;
lck_mtx_unlock(tpm_mtx);
error = ECONNREFUSED;
goto out;
}
}
lck_mtx_unlock(tpm_mtx);
out:
(void)OSDecrementAtomic(&tpm_activity);
return error;
}
int
tpm_dev_read(dev_t dev, struct uio* uio, int ioflag)
{
(void)OSIncrementAtomic(&tpm_activity);
errno_t error = 0;
size_t recvlen;
struct msghdr msg;
struct iovec aiov[1];
lck_mtx_lock(tpm_mtx);
if ((tpmd_socket == NULL) || !sock_isconnected(tpmd_socket)) {
lck_mtx_unlock(tpm_mtx);
error = ENOTCONN;
goto out;
}
if (tpm_in_io) {
error = msleep(&tpm_in_io, tpm_mtx, PCATCH, "tpm_in_io", NULL);
if (error != 0) {
lck_mtx_unlock(tpm_mtx);
error = EAGAIN;
goto out;
}
}
tpm_in_io = 1;
lck_mtx_unlock(tpm_mtx);
(void)sock_nointerrupt(tpmd_socket, 1);
recvlen = (uint32_t)uio_resid(uio);
memset(&msg, 0, sizeof(msg));
aiov[0].iov_base = (caddr_t)tpm_buffer;
aiov[0].iov_len = TPM_CMD_BUF_SIZE;
if (recvlen < TPM_CMD_BUF_SIZE) {
aiov[0].iov_len = recvlen;
}
msg.msg_iovlen = 1;
msg.msg_iov = aiov;
if ((error = sock_receive(tpmd_socket, &msg, 0, (size_t*)&recvlen)) == 0) {
error = uiomove64((addr64_t)(uintptr_t)tpm_buffer, (int)recvlen, uio);
}
lck_mtx_lock(tpm_mtx);
tpm_in_io = 0;
wakeup_one((caddr_t)&tpm_in_io);
lck_mtx_unlock(tpm_mtx);
out:
(void)OSDecrementAtomic(&tpm_activity);
return error;
}
int
tpm_dev_write(dev_t dev, struct uio* uio, int ioflag)
{
(void)OSIncrementAtomic(&tpm_activity);
errno_t error = 0;
size_t sentlen;
struct msghdr msg;
struct iovec aiov[1];
lck_mtx_lock(tpm_mtx);
if ((tpmd_socket == NULL) || !sock_isconnected(tpmd_socket)) {
lck_mtx_unlock(tpm_mtx);
error = ENOTCONN;
goto out;
}
if (tpm_in_io) {
error = msleep(&tpm_in_io, tpm_mtx, PCATCH, "tpm_in_io", NULL);
if (error != 0) {
lck_mtx_unlock(tpm_mtx);
error = EAGAIN;
goto out;
}
}
tpm_in_io = 1;
lck_mtx_unlock(tpm_mtx);
sentlen = min((uint32_t)uio_resid(uio), TPM_CMD_BUF_SIZE);
if ((error = uiomove64((addr64_t)(uintptr_t)tpm_buffer,
(int)sentlen, uio)) == 0) {
memset(&msg, 0, sizeof(msg));
aiov[0].iov_base = (caddr_t)tpm_buffer;
aiov[0].iov_len = sentlen;
msg.msg_iovlen = 1;
msg.msg_iov = aiov;
error = sock_send(tpmd_socket, &msg, 0, &sentlen);
}
lck_mtx_lock(tpm_mtx);
tpm_in_io = 0;
wakeup_one((caddr_t)&tpm_in_io);
lck_mtx_unlock(tpm_mtx);
out:
(void)OSDecrementAtomic(&tpm_activity);
return error;
}
static int
tpmd_connect(void)
{
errno_t error;
struct timeval tv;
error = sock_socket(PF_LOCAL, SOCK_STREAM, 0, NULL, NULL, &tpmd_socket);
if (error != 0) {
tpmd_socket = NULL;
return error;
}
tv.tv_sec = 10;
tv.tv_usec = 0;
error = sock_setsockopt(tpmd_socket, SOL_SOCKET, SO_RCVTIMEO, &tv,
sizeof(struct timeval));
if (error != 0) {
sock_close(tpmd_socket);
tpmd_socket = NULL;
return error;
}
error = sock_connect(tpmd_socket,
(const struct sockaddr*)&tpmd_socket_addr, 0);
if (error != 0) {
sock_close(tpmd_socket);
tpmd_socket = NULL;
return error;
}
return 0;
}
static void
tpmd_disconnect(void)
{
if (tpmd_socket != NULL) {
sock_shutdown(tpmd_socket, SHUT_RDWR);
sock_close(tpmd_socket);
tpmd_socket = NULL;
}
}
static int
tpm_bridge_locking_start(void)
{
tpm_mtx_grp_attr = lck_grp_attr_alloc_init();
if (tpm_mtx_grp_attr == NULL) {
goto failed;
}
tpm_mtx_grp = lck_grp_alloc_init("tpm_mtx", tpm_mtx_grp_attr);
if (tpm_mtx_grp == NULL) {
goto failed;
}
tpm_mtx_attr = lck_attr_alloc_init();
if (tpm_mtx_attr == NULL) {
goto failed;
}
tpm_mtx = lck_mtx_alloc_init(tpm_mtx_grp, tpm_mtx_attr);
if (tpm_mtx == NULL) {
goto failed;
}
return KERN_SUCCESS;
failed:
(void)tpm_bridge_locking_stop();
return KERN_FAILURE;
}
static int
tpm_bridge_locking_stop(void)
{
if (tpm_mtx != NULL) {
lck_mtx_free(tpm_mtx, tpm_mtx_grp);
tpm_mtx = NULL;
}
if (tpm_mtx_attr != NULL) {
lck_attr_free(tpm_mtx_attr);
tpm_mtx_attr = NULL;
}
if (tpm_mtx_grp != NULL) {
lck_grp_free(tpm_mtx_grp);
tpm_mtx_grp = NULL;
}
if (tpm_mtx_grp_attr != NULL) {
lck_grp_attr_free(tpm_mtx_grp_attr);
tpm_mtx_grp_attr = NULL;
}
return KERN_SUCCESS;
}
static int
tpm_bridge_devfs_start(void)
{
dev_tpm_index = cdevsw_add(-1, &cdev_tpm);
if (dev_tpm_index == -1) {
return KERN_FAILURE;
}
dev_tpm_node = devfs_make_node(makedev(dev_tpm_index, dev_tpm_minor),
DEVFS_CHAR, TPM_BRIDGE_UID, TPM_BRIDGE_GID,
TPM_BRIDGE_MODE, TPM_BRIDGE_NAME);
if (dev_tpm_node == NULL) {
(void)tpm_bridge_devfs_stop();
return KERN_FAILURE;
}
return KERN_SUCCESS;
}
static int
tpm_bridge_devfs_stop(void)
{
int ret = KERN_SUCCESS;
if (dev_tpm_node != NULL) {
devfs_remove(dev_tpm_node);
dev_tpm_node = NULL;
}
if (dev_tpm_index != -1) {
ret = cdevsw_remove(dev_tpm_index, &cdev_tpm);
if (ret != dev_tpm_index) {
ret = KERN_FAILURE;
} else {
dev_tpm_index = -1;
ret = KERN_SUCCESS;
}
}
return ret;
}
kern_return_t
tpm_bridge_start(kmod_info_t* ki, void* d)
{
if (tpm_bridge_locking_start() != KERN_SUCCESS) {
return KERN_FAILURE;
}
if (tpm_bridge_devfs_start() != KERN_SUCCESS) {
tpm_bridge_locking_stop();
return KERN_FAILURE;
}
#ifndef SUN_LEN
#define SUN_LEN(su) \
(sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path))
#define SUN_LEN_PRIVATELY_DEFINED 1
#endif
tpmd_socket_addr.sun_len = SUN_LEN(&tpmd_socket_addr);
#if SUN_LEN_PRIVATELY_DEFINED
#undef SUN_LEN
#endif
return KERN_SUCCESS;
}
kern_return_t
tpm_bridge_stop(kmod_info_t* ki, void* d)
{
lck_mtx_lock(tpm_mtx);
(void)tpm_bridge_devfs_stop();
if ((tpmd_socket != NULL) && sock_isconnected(tpmd_socket)) {
tpmd_disconnect();
tpmd_socket = NULL;
}
lck_mtx_unlock(tpm_mtx);
do {
struct timespec ts = { 1, 0 };
(void)msleep(&tpm_activity, NULL, PUSER, "tpm_activity", &ts);
} while (tpm_activity > 0);
(void)tpm_bridge_locking_stop();
return KERN_SUCCESS;
}