/* Software-based Trusted Platform Module (TPM) Emulator * Copyright (C) 2004-2010 Mario Strasser * * 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 #include #include #include #include #include #include #include #include #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 "); 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);