273 lines
7.2 KiB
C
273 lines
7.2 KiB
C
/* Software-based Trusted Platform Module (TPM) Emulator
|
|
* Copyright (C) 2004-2010 Mario Strasser <mast@gmx.net>
|
|
*
|
|
* This module 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 2 of the License,
|
|
* or (at your option) any later version.
|
|
*
|
|
* This module 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.
|
|
*
|
|
* $Id: tpmd_dev.c 426 2010-02-22 17:11:58Z mast $
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/init.h>
|
|
#include <linux/miscdevice.h>
|
|
#include <linux/poll.h>
|
|
#include <linux/slab.h>
|
|
|
|
#include <linux/socket.h>
|
|
#include <linux/net.h>
|
|
#include <linux/un.h>
|
|
|
|
#include "config.h"
|
|
|
|
#define TPM_DEVICE_MINOR 224
|
|
#define TPM_DEVICE_ID "tpm"
|
|
#define TPM_MODULE_NAME "tpmd_dev"
|
|
|
|
#define TPM_STATE_IS_OPEN 0
|
|
|
|
#ifdef DEBUG
|
|
#define debug(fmt, ...) printk(KERN_DEBUG "%s %s:%d: Debug: " fmt "\n", \
|
|
TPM_MODULE_NAME, __FILE__, __LINE__, ## __VA_ARGS__)
|
|
#else
|
|
#define debug(fmt, ...)
|
|
#endif
|
|
#define info(fmt, ...) printk(KERN_INFO "%s %s:%d: Info: " fmt "\n", \
|
|
TPM_MODULE_NAME, __FILE__, __LINE__, ## __VA_ARGS__)
|
|
#define error(fmt, ...) printk(KERN_ERR "%s %s:%d: Error: " fmt "\n", \
|
|
TPM_MODULE_NAME, __FILE__, __LINE__, ## __VA_ARGS__)
|
|
#define alert(fmt, ...) printk(KERN_ALERT "%s %s:%d: Alert: " fmt "\n", \
|
|
TPM_MODULE_NAME, __FILE__, __LINE__, ## __VA_ARGS__)
|
|
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_AUTHOR("Mario Strasser <mast@gmx.net>");
|
|
MODULE_DESCRIPTION("Trusted Platform Module (TPM) Emulator");
|
|
MODULE_SUPPORTED_DEVICE(TPM_DEVICE_ID);
|
|
|
|
/* module parameters */
|
|
char *tpmd_socket_name = TPM_SOCKET_NAME;
|
|
module_param(tpmd_socket_name, charp, 0444);
|
|
MODULE_PARM_DESC(tpmd_socket_name, " Sets the name of the TPM daemon socket.");
|
|
|
|
/* TPM lock */
|
|
static struct semaphore tpm_mutex;
|
|
|
|
/* TPM command response */
|
|
static struct {
|
|
uint8_t *data;
|
|
uint32_t size;
|
|
} tpm_response;
|
|
|
|
/* module state */
|
|
static uint32_t module_state;
|
|
static struct socket *tpmd_sock;
|
|
static struct sockaddr_un addr;
|
|
|
|
static int tpmd_connect(char *socket_name)
|
|
{
|
|
int res;
|
|
res = sock_create(PF_UNIX, SOCK_STREAM, 0, &tpmd_sock);
|
|
if (res != 0) {
|
|
error("sock_create() failed: %d\n", res);
|
|
tpmd_sock = NULL;
|
|
return res;
|
|
}
|
|
addr.sun_family = AF_UNIX;
|
|
strncpy(addr.sun_path, socket_name, sizeof(addr.sun_path));
|
|
res = tpmd_sock->ops->connect(tpmd_sock,
|
|
(struct sockaddr*)&addr, sizeof(struct sockaddr_un), 0);
|
|
if (res != 0) {
|
|
error("sock_connect() failed: %d\n", res);
|
|
tpmd_sock->ops->release(tpmd_sock);
|
|
tpmd_sock = NULL;
|
|
return res;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void tpmd_disconnect(void)
|
|
{
|
|
if (tpmd_sock != NULL) tpmd_sock->ops->release(tpmd_sock);
|
|
tpmd_sock = NULL;
|
|
}
|
|
|
|
static int tpmd_handle_command(const uint8_t *in, uint32_t in_size)
|
|
{
|
|
int res;
|
|
mm_segment_t oldmm;
|
|
struct msghdr msg;
|
|
struct iovec iov;
|
|
/* send command to tpmd */
|
|
memset(&msg, 0, sizeof(msg));
|
|
iov.iov_base = (void*)in;
|
|
iov.iov_len = in_size;
|
|
msg.msg_iov = &iov;
|
|
msg.msg_iovlen = 1;
|
|
res = sock_sendmsg(tpmd_sock, &msg, in_size);
|
|
if (res < 0) {
|
|
error("sock_sendmsg() failed: %d\n", res);
|
|
return res;
|
|
}
|
|
/* receive response from tpmd */
|
|
tpm_response.size = TPM_CMD_BUF_SIZE;
|
|
tpm_response.data = kmalloc(tpm_response.size, GFP_KERNEL);
|
|
if (tpm_response.data == NULL) return -1;
|
|
memset(&msg, 0, sizeof(msg));
|
|
iov.iov_base = (void*)tpm_response.data;
|
|
iov.iov_len = tpm_response.size;
|
|
msg.msg_iov = &iov;
|
|
msg.msg_iovlen = 1;
|
|
oldmm = get_fs();
|
|
set_fs(KERNEL_DS);
|
|
res = sock_recvmsg(tpmd_sock, &msg, tpm_response.size, 0);
|
|
set_fs(oldmm);
|
|
if (res < 0) {
|
|
error("sock_recvmsg() failed: %d\n", res);
|
|
tpm_response.data = NULL;
|
|
return res;
|
|
}
|
|
tpm_response.size = res;
|
|
return 0;
|
|
}
|
|
|
|
static int tpm_open(struct inode *inode, struct file *file)
|
|
{
|
|
int res;
|
|
debug("%s()", __FUNCTION__);
|
|
if (test_and_set_bit(TPM_STATE_IS_OPEN, (void*)&module_state)) return -EBUSY;
|
|
down(&tpm_mutex);
|
|
res = tpmd_connect(tpmd_socket_name);
|
|
up(&tpm_mutex);
|
|
if (res != 0) {
|
|
clear_bit(TPM_STATE_IS_OPEN, (void*)&module_state);
|
|
return -EIO;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int tpm_release(struct inode *inode, struct file *file)
|
|
{
|
|
debug("%s()", __FUNCTION__);
|
|
down(&tpm_mutex);
|
|
if (tpm_response.data != NULL) {
|
|
kfree(tpm_response.data);
|
|
tpm_response.data = NULL;
|
|
}
|
|
tpmd_disconnect();
|
|
up(&tpm_mutex);
|
|
clear_bit(TPM_STATE_IS_OPEN, (void*)&module_state);
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t tpm_read(struct file *file, char *buf, size_t count, loff_t *ppos)
|
|
{
|
|
debug("%s(%zd)", __FUNCTION__, count);
|
|
down(&tpm_mutex);
|
|
if (tpm_response.data != NULL) {
|
|
count = min(count, (size_t)tpm_response.size - (size_t)*ppos);
|
|
count -= copy_to_user(buf, &tpm_response.data[*ppos], count);
|
|
*ppos += count;
|
|
if ((size_t)tpm_response.size == (size_t)*ppos) {
|
|
kfree(tpm_response.data);
|
|
tpm_response.data = NULL;
|
|
}
|
|
} else {
|
|
count = 0;
|
|
}
|
|
up(&tpm_mutex);
|
|
return count;
|
|
}
|
|
|
|
static ssize_t tpm_write(struct file *file, const char *buf, size_t count, loff_t *ppos)
|
|
{
|
|
debug("%s(%zd)", __FUNCTION__, count);
|
|
down(&tpm_mutex);
|
|
*ppos = 0;
|
|
if (tpm_response.data != NULL) {
|
|
kfree(tpm_response.data);
|
|
tpm_response.data = NULL;
|
|
}
|
|
if (tpmd_handle_command(buf, count) != 0) {
|
|
count = -EILSEQ;
|
|
tpm_response.data = NULL;
|
|
}
|
|
up(&tpm_mutex);
|
|
return count;
|
|
}
|
|
|
|
#define TPMIOC_CANCEL _IO('T', 0x00)
|
|
#define TPMIOC_TRANSMIT _IO('T', 0x01)
|
|
|
|
static int tpm_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
|
|
{
|
|
debug("%s(%d, %p)", __FUNCTION__, cmd, (char*)arg);
|
|
if (cmd == TPMIOC_TRANSMIT) {
|
|
uint32_t count = ntohl(*(uint32_t*)(arg + 2));
|
|
down(&tpm_mutex);
|
|
if (tpm_response.data != NULL) {
|
|
kfree(tpm_response.data);
|
|
tpm_response.data = NULL;
|
|
}
|
|
if (tpmd_handle_command((char*)arg, count) == 0) {
|
|
tpm_response.size -= copy_to_user((char*)arg, tpm_response.data, tpm_response.size);
|
|
kfree(tpm_response.data);
|
|
tpm_response.data = NULL;
|
|
} else {
|
|
tpm_response.size = 0;
|
|
tpm_response.data = NULL;
|
|
}
|
|
up(&tpm_mutex);
|
|
return tpm_response.size;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
struct file_operations fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = tpm_open,
|
|
.release = tpm_release,
|
|
.read = tpm_read,
|
|
.write = tpm_write,
|
|
.ioctl = tpm_ioctl,
|
|
};
|
|
|
|
static struct miscdevice tpm_dev = {
|
|
.minor = TPM_DEVICE_MINOR,
|
|
.name = TPM_DEVICE_ID,
|
|
.fops = &fops,
|
|
};
|
|
|
|
int __init init_tpm_module(void)
|
|
{
|
|
int res = misc_register(&tpm_dev);
|
|
if (res != 0) {
|
|
error("misc_register() failed for minor %d\n", TPM_DEVICE_MINOR);
|
|
return res;
|
|
}
|
|
/* initialize variables */
|
|
sema_init(&tpm_mutex, 1);
|
|
module_state = 0;
|
|
tpm_response.data = NULL;
|
|
tpm_response.size = 0;
|
|
tpmd_sock = NULL;
|
|
return 0;
|
|
}
|
|
|
|
void __exit cleanup_tpm_module(void)
|
|
{
|
|
misc_deregister(&tpm_dev);
|
|
tpmd_disconnect();
|
|
if (tpm_response.data != NULL) kfree(tpm_response.data);
|
|
}
|
|
|
|
module_init(init_tpm_module);
|
|
module_exit(cleanup_tpm_module);
|
|
|