![Linus Torvalds](/assets/img/avatar_default.png)
Pull security subsystem updates from James Morris: "In this patchset, we finally get an SELinux update, with Paul Moore taking over as maintainer of that code. Also a significant update for the Keys subsystem, as well as maintenance updates to Smack, IMA, TPM, and Apparmor" and since I wanted to know more about the updates to key handling, here's the explanation from David Howells on that: "Okay. There are a number of separate bits. I'll go over the big bits and the odd important other bit, most of the smaller bits are just fixes and cleanups. If you want the small bits accounting for, I can do that too. (1) Keyring capacity expansion. KEYS: Consolidate the concept of an 'index key' for key access KEYS: Introduce a search context structure KEYS: Search for auth-key by name rather than target key ID Add a generic associative array implementation. KEYS: Expand the capacity of a keyring Several of the patches are providing an expansion of the capacity of a keyring. Currently, the maximum size of a keyring payload is one page. Subtract a small header and then divide up into pointers, that only gives you ~500 pointers on an x86_64 box. However, since the NFS idmapper uses a keyring to store ID mapping data, that has proven to be insufficient to the cause. Whatever data structure I use to handle the keyring payload, it can only store pointers to keys, not the keys themselves because several keyrings may point to a single key. This precludes inserting, say, and rb_node struct into the key struct for this purpose. I could make an rbtree of records such that each record has an rb_node and a key pointer, but that would use four words of space per key stored in the keyring. It would, however, be able to use much existing code. I selected instead a non-rebalancing radix-tree type approach as that could have a better space-used/key-pointer ratio. I could have used the radix tree implementation that we already have and insert keys into it by their serial numbers, but that means any sort of search must iterate over the whole radix tree. Further, its nodes are a bit on the capacious side for what I want - especially given that key serial numbers are randomly allocated, thus leaving a lot of empty space in the tree. So what I have is an associative array that internally is a radix-tree with 16 pointers per node where the index key is constructed from the key type pointer and the key description. This means that an exact lookup by type+description is very fast as this tells us how to navigate directly to the target key. I made the data structure general in lib/assoc_array.c as far as it is concerned, its index key is just a sequence of bits that leads to a pointer. It's possible that someone else will be able to make use of it also. FS-Cache might, for example. (2) Mark keys as 'trusted' and keyrings as 'trusted only'. KEYS: verify a certificate is signed by a 'trusted' key KEYS: Make the system 'trusted' keyring viewable by userspace KEYS: Add a 'trusted' flag and a 'trusted only' flag KEYS: Separate the kernel signature checking keyring from module signing These patches allow keys carrying asymmetric public keys to be marked as being 'trusted' and allow keyrings to be marked as only permitting the addition or linkage of trusted keys. Keys loaded from hardware during kernel boot or compiled into the kernel during build are marked as being trusted automatically. New keys can be loaded at runtime with add_key(). They are checked against the system keyring contents and if their signatures can be validated with keys that are already marked trusted, then they are marked trusted also and can thus be added into the master keyring. Patches from Mimi Zohar make this usable with the IMA keyrings also. (3) Remove the date checks on the key used to validate a module signature. X.509: Remove certificate date checks It's not reasonable to reject a signature just because the key that it was generated with is no longer valid datewise - especially if the kernel hasn't yet managed to set the system clock when the first module is loaded - so just remove those checks. (4) Make it simpler to deal with additional X.509 being loaded into the kernel. KEYS: Load *.x509 files into kernel keyring KEYS: Have make canonicalise the paths of the X.509 certs better to deduplicate The builder of the kernel now just places files with the extension ".x509" into the kernel source or build trees and they're concatenated by the kernel build and stuffed into the appropriate section. (5) Add support for userspace kerberos to use keyrings. KEYS: Add per-user_namespace registers for persistent per-UID kerberos caches KEYS: Implement a big key type that can save to tmpfs Fedora went to, by default, storing kerberos tickets and tokens in tmpfs. We looked at storing it in keyrings instead as that confers certain advantages such as tickets being automatically deleted after a certain amount of time and the ability for the kernel to get at these tokens more easily. To make this work, two things were needed: (a) A way for the tickets to persist beyond the lifetime of all a user's sessions so that cron-driven processes can still use them. The problem is that a user's session keyrings are deleted when the session that spawned them logs out and the user's user keyring is deleted when the UID is deleted (typically when the last log out happens), so neither of these places is suitable. I've added a system keyring into which a 'persistent' keyring is created for each UID on request. Each time a user requests their persistent keyring, the expiry time on it is set anew. If the user doesn't ask for it for, say, three days, the keyring is automatically expired and garbage collected using the existing gc. All the kerberos tokens it held are then also gc'd. (b) A key type that can hold really big tickets (up to 1MB in size). The problem is that Active Directory can return huge tickets with lots of auxiliary data attached. We don't, however, want to eat up huge tracts of unswappable kernel space for this, so if the ticket is greater than a certain size, we create a swappable shmem file and dump the contents in there and just live with the fact we then have an inode and a dentry overhead. If the ticket is smaller than that, we slap it in a kmalloc()'d buffer" * 'for-linus2' of git://git.kernel.org/pub/scm/linux/kernel/git/jmorris/linux-security: (121 commits) KEYS: Fix keyring content gc scanner KEYS: Fix error handling in big_key instantiation KEYS: Fix UID check in keyctl_get_persistent() KEYS: The RSA public key algorithm needs to select MPILIB ima: define '_ima' as a builtin 'trusted' keyring ima: extend the measurement list to include the file signature kernel/system_certificate.S: use real contents instead of macro GLOBAL() KEYS: fix error return code in big_key_instantiate() KEYS: Fix keyring quota misaccounting on key replacement and unlink KEYS: Fix a race between negating a key and reading the error set KEYS: Make BIG_KEYS boolean apparmor: remove the "task" arg from may_change_ptraced_domain() apparmor: remove parent task info from audit logging apparmor: remove tsk field from the apparmor_audit_struct apparmor: fix capability to not use the current task, during reporting Smack: Ptrace access check mode ima: provide hash algo info in the xattr ima: enable support for larger default filedata hash algorithms ima: define kernel parameter 'ima_template=' to change configured default ima: add Kconfig default measurement list template ...
437 lines
9.7 KiB
C
437 lines
9.7 KiB
C
/*
|
|
* Implementation of the Xen vTPM device frontend
|
|
*
|
|
* Author: Daniel De Graaf <dgdegra@tycho.nsa.gov>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2,
|
|
* as published by the Free Software Foundation.
|
|
*/
|
|
#include <linux/errno.h>
|
|
#include <linux/err.h>
|
|
#include <linux/interrupt.h>
|
|
#include <xen/xen.h>
|
|
#include <xen/events.h>
|
|
#include <xen/interface/io/tpmif.h>
|
|
#include <xen/grant_table.h>
|
|
#include <xen/xenbus.h>
|
|
#include <xen/page.h>
|
|
#include "tpm.h"
|
|
|
|
struct tpm_private {
|
|
struct tpm_chip *chip;
|
|
struct xenbus_device *dev;
|
|
|
|
struct vtpm_shared_page *shr;
|
|
|
|
unsigned int evtchn;
|
|
int ring_ref;
|
|
domid_t backend_id;
|
|
};
|
|
|
|
enum status_bits {
|
|
VTPM_STATUS_RUNNING = 0x1,
|
|
VTPM_STATUS_IDLE = 0x2,
|
|
VTPM_STATUS_RESULT = 0x4,
|
|
VTPM_STATUS_CANCELED = 0x8,
|
|
};
|
|
|
|
static u8 vtpm_status(struct tpm_chip *chip)
|
|
{
|
|
struct tpm_private *priv = TPM_VPRIV(chip);
|
|
switch (priv->shr->state) {
|
|
case VTPM_STATE_IDLE:
|
|
return VTPM_STATUS_IDLE | VTPM_STATUS_CANCELED;
|
|
case VTPM_STATE_FINISH:
|
|
return VTPM_STATUS_IDLE | VTPM_STATUS_RESULT;
|
|
case VTPM_STATE_SUBMIT:
|
|
case VTPM_STATE_CANCEL: /* cancel requested, not yet canceled */
|
|
return VTPM_STATUS_RUNNING;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static bool vtpm_req_canceled(struct tpm_chip *chip, u8 status)
|
|
{
|
|
return status & VTPM_STATUS_CANCELED;
|
|
}
|
|
|
|
static void vtpm_cancel(struct tpm_chip *chip)
|
|
{
|
|
struct tpm_private *priv = TPM_VPRIV(chip);
|
|
priv->shr->state = VTPM_STATE_CANCEL;
|
|
wmb();
|
|
notify_remote_via_evtchn(priv->evtchn);
|
|
}
|
|
|
|
static unsigned int shr_data_offset(struct vtpm_shared_page *shr)
|
|
{
|
|
return sizeof(*shr) + sizeof(u32) * shr->nr_extra_pages;
|
|
}
|
|
|
|
static int vtpm_send(struct tpm_chip *chip, u8 *buf, size_t count)
|
|
{
|
|
struct tpm_private *priv = TPM_VPRIV(chip);
|
|
struct vtpm_shared_page *shr = priv->shr;
|
|
unsigned int offset = shr_data_offset(shr);
|
|
|
|
u32 ordinal;
|
|
unsigned long duration;
|
|
|
|
if (offset > PAGE_SIZE)
|
|
return -EINVAL;
|
|
|
|
if (offset + count > PAGE_SIZE)
|
|
return -EINVAL;
|
|
|
|
/* Wait for completion of any existing command or cancellation */
|
|
if (wait_for_tpm_stat(chip, VTPM_STATUS_IDLE, chip->vendor.timeout_c,
|
|
&chip->vendor.read_queue, true) < 0) {
|
|
vtpm_cancel(chip);
|
|
return -ETIME;
|
|
}
|
|
|
|
memcpy(offset + (u8 *)shr, buf, count);
|
|
shr->length = count;
|
|
barrier();
|
|
shr->state = VTPM_STATE_SUBMIT;
|
|
wmb();
|
|
notify_remote_via_evtchn(priv->evtchn);
|
|
|
|
ordinal = be32_to_cpu(((struct tpm_input_header*)buf)->ordinal);
|
|
duration = tpm_calc_ordinal_duration(chip, ordinal);
|
|
|
|
if (wait_for_tpm_stat(chip, VTPM_STATUS_IDLE, duration,
|
|
&chip->vendor.read_queue, true) < 0) {
|
|
/* got a signal or timeout, try to cancel */
|
|
vtpm_cancel(chip);
|
|
return -ETIME;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
static int vtpm_recv(struct tpm_chip *chip, u8 *buf, size_t count)
|
|
{
|
|
struct tpm_private *priv = TPM_VPRIV(chip);
|
|
struct vtpm_shared_page *shr = priv->shr;
|
|
unsigned int offset = shr_data_offset(shr);
|
|
size_t length = shr->length;
|
|
|
|
if (shr->state == VTPM_STATE_IDLE)
|
|
return -ECANCELED;
|
|
|
|
/* In theory the wait at the end of _send makes this one unnecessary */
|
|
if (wait_for_tpm_stat(chip, VTPM_STATUS_RESULT, chip->vendor.timeout_c,
|
|
&chip->vendor.read_queue, true) < 0) {
|
|
vtpm_cancel(chip);
|
|
return -ETIME;
|
|
}
|
|
|
|
if (offset > PAGE_SIZE)
|
|
return -EIO;
|
|
|
|
if (offset + length > PAGE_SIZE)
|
|
length = PAGE_SIZE - offset;
|
|
|
|
if (length > count)
|
|
length = count;
|
|
|
|
memcpy(buf, offset + (u8 *)shr, length);
|
|
|
|
return length;
|
|
}
|
|
|
|
static const struct file_operations vtpm_ops = {
|
|
.owner = THIS_MODULE,
|
|
.llseek = no_llseek,
|
|
.open = tpm_open,
|
|
.read = tpm_read,
|
|
.write = tpm_write,
|
|
.release = tpm_release,
|
|
};
|
|
|
|
static DEVICE_ATTR(pubek, S_IRUGO, tpm_show_pubek, NULL);
|
|
static DEVICE_ATTR(pcrs, S_IRUGO, tpm_show_pcrs, NULL);
|
|
static DEVICE_ATTR(enabled, S_IRUGO, tpm_show_enabled, NULL);
|
|
static DEVICE_ATTR(active, S_IRUGO, tpm_show_active, NULL);
|
|
static DEVICE_ATTR(owned, S_IRUGO, tpm_show_owned, NULL);
|
|
static DEVICE_ATTR(temp_deactivated, S_IRUGO, tpm_show_temp_deactivated,
|
|
NULL);
|
|
static DEVICE_ATTR(caps, S_IRUGO, tpm_show_caps, NULL);
|
|
static DEVICE_ATTR(cancel, S_IWUSR | S_IWGRP, NULL, tpm_store_cancel);
|
|
static DEVICE_ATTR(durations, S_IRUGO, tpm_show_durations, NULL);
|
|
static DEVICE_ATTR(timeouts, S_IRUGO, tpm_show_timeouts, NULL);
|
|
|
|
static struct attribute *vtpm_attrs[] = {
|
|
&dev_attr_pubek.attr,
|
|
&dev_attr_pcrs.attr,
|
|
&dev_attr_enabled.attr,
|
|
&dev_attr_active.attr,
|
|
&dev_attr_owned.attr,
|
|
&dev_attr_temp_deactivated.attr,
|
|
&dev_attr_caps.attr,
|
|
&dev_attr_cancel.attr,
|
|
&dev_attr_durations.attr,
|
|
&dev_attr_timeouts.attr,
|
|
NULL,
|
|
};
|
|
|
|
static struct attribute_group vtpm_attr_grp = {
|
|
.attrs = vtpm_attrs,
|
|
};
|
|
|
|
static const struct tpm_vendor_specific tpm_vtpm = {
|
|
.status = vtpm_status,
|
|
.recv = vtpm_recv,
|
|
.send = vtpm_send,
|
|
.cancel = vtpm_cancel,
|
|
.req_complete_mask = VTPM_STATUS_IDLE | VTPM_STATUS_RESULT,
|
|
.req_complete_val = VTPM_STATUS_IDLE | VTPM_STATUS_RESULT,
|
|
.req_canceled = vtpm_req_canceled,
|
|
.attr_group = &vtpm_attr_grp,
|
|
.miscdev = {
|
|
.fops = &vtpm_ops,
|
|
},
|
|
};
|
|
|
|
static irqreturn_t tpmif_interrupt(int dummy, void *dev_id)
|
|
{
|
|
struct tpm_private *priv = dev_id;
|
|
|
|
switch (priv->shr->state) {
|
|
case VTPM_STATE_IDLE:
|
|
case VTPM_STATE_FINISH:
|
|
wake_up_interruptible(&priv->chip->vendor.read_queue);
|
|
break;
|
|
case VTPM_STATE_SUBMIT:
|
|
case VTPM_STATE_CANCEL:
|
|
default:
|
|
break;
|
|
}
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int setup_chip(struct device *dev, struct tpm_private *priv)
|
|
{
|
|
struct tpm_chip *chip;
|
|
|
|
chip = tpm_register_hardware(dev, &tpm_vtpm);
|
|
if (!chip)
|
|
return -ENODEV;
|
|
|
|
init_waitqueue_head(&chip->vendor.read_queue);
|
|
|
|
priv->chip = chip;
|
|
TPM_VPRIV(chip) = priv;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* caller must clean up in case of errors */
|
|
static int setup_ring(struct xenbus_device *dev, struct tpm_private *priv)
|
|
{
|
|
struct xenbus_transaction xbt;
|
|
const char *message = NULL;
|
|
int rv;
|
|
|
|
priv->shr = (void *)__get_free_page(GFP_KERNEL|__GFP_ZERO);
|
|
if (!priv->shr) {
|
|
xenbus_dev_fatal(dev, -ENOMEM, "allocating shared ring");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
rv = xenbus_grant_ring(dev, virt_to_mfn(priv->shr));
|
|
if (rv < 0)
|
|
return rv;
|
|
|
|
priv->ring_ref = rv;
|
|
|
|
rv = xenbus_alloc_evtchn(dev, &priv->evtchn);
|
|
if (rv)
|
|
return rv;
|
|
|
|
rv = bind_evtchn_to_irqhandler(priv->evtchn, tpmif_interrupt, 0,
|
|
"tpmif", priv);
|
|
if (rv <= 0) {
|
|
xenbus_dev_fatal(dev, rv, "allocating TPM irq");
|
|
return rv;
|
|
}
|
|
priv->chip->vendor.irq = rv;
|
|
|
|
again:
|
|
rv = xenbus_transaction_start(&xbt);
|
|
if (rv) {
|
|
xenbus_dev_fatal(dev, rv, "starting transaction");
|
|
return rv;
|
|
}
|
|
|
|
rv = xenbus_printf(xbt, dev->nodename,
|
|
"ring-ref", "%u", priv->ring_ref);
|
|
if (rv) {
|
|
message = "writing ring-ref";
|
|
goto abort_transaction;
|
|
}
|
|
|
|
rv = xenbus_printf(xbt, dev->nodename, "event-channel", "%u",
|
|
priv->evtchn);
|
|
if (rv) {
|
|
message = "writing event-channel";
|
|
goto abort_transaction;
|
|
}
|
|
|
|
rv = xenbus_printf(xbt, dev->nodename, "feature-protocol-v2", "1");
|
|
if (rv) {
|
|
message = "writing feature-protocol-v2";
|
|
goto abort_transaction;
|
|
}
|
|
|
|
rv = xenbus_transaction_end(xbt, 0);
|
|
if (rv == -EAGAIN)
|
|
goto again;
|
|
if (rv) {
|
|
xenbus_dev_fatal(dev, rv, "completing transaction");
|
|
return rv;
|
|
}
|
|
|
|
xenbus_switch_state(dev, XenbusStateInitialised);
|
|
|
|
return 0;
|
|
|
|
abort_transaction:
|
|
xenbus_transaction_end(xbt, 1);
|
|
if (message)
|
|
xenbus_dev_error(dev, rv, "%s", message);
|
|
|
|
return rv;
|
|
}
|
|
|
|
static void ring_free(struct tpm_private *priv)
|
|
{
|
|
if (!priv)
|
|
return;
|
|
|
|
if (priv->ring_ref)
|
|
gnttab_end_foreign_access(priv->ring_ref, 0,
|
|
(unsigned long)priv->shr);
|
|
else
|
|
free_page((unsigned long)priv->shr);
|
|
|
|
if (priv->chip && priv->chip->vendor.irq)
|
|
unbind_from_irqhandler(priv->chip->vendor.irq, priv);
|
|
|
|
kfree(priv);
|
|
}
|
|
|
|
static int tpmfront_probe(struct xenbus_device *dev,
|
|
const struct xenbus_device_id *id)
|
|
{
|
|
struct tpm_private *priv;
|
|
int rv;
|
|
|
|
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
|
|
if (!priv) {
|
|
xenbus_dev_fatal(dev, -ENOMEM, "allocating priv structure");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
rv = setup_chip(&dev->dev, priv);
|
|
if (rv) {
|
|
kfree(priv);
|
|
return rv;
|
|
}
|
|
|
|
rv = setup_ring(dev, priv);
|
|
if (rv) {
|
|
tpm_remove_hardware(&dev->dev);
|
|
ring_free(priv);
|
|
return rv;
|
|
}
|
|
|
|
tpm_get_timeouts(priv->chip);
|
|
|
|
return rv;
|
|
}
|
|
|
|
static int tpmfront_remove(struct xenbus_device *dev)
|
|
{
|
|
struct tpm_chip *chip = dev_get_drvdata(&dev->dev);
|
|
struct tpm_private *priv = TPM_VPRIV(chip);
|
|
tpm_remove_hardware(&dev->dev);
|
|
ring_free(priv);
|
|
TPM_VPRIV(chip) = NULL;
|
|
return 0;
|
|
}
|
|
|
|
static int tpmfront_resume(struct xenbus_device *dev)
|
|
{
|
|
/* A suspend/resume/migrate will interrupt a vTPM anyway */
|
|
tpmfront_remove(dev);
|
|
return tpmfront_probe(dev, NULL);
|
|
}
|
|
|
|
static void backend_changed(struct xenbus_device *dev,
|
|
enum xenbus_state backend_state)
|
|
{
|
|
int val;
|
|
|
|
switch (backend_state) {
|
|
case XenbusStateInitialised:
|
|
case XenbusStateConnected:
|
|
if (dev->state == XenbusStateConnected)
|
|
break;
|
|
|
|
if (xenbus_scanf(XBT_NIL, dev->otherend,
|
|
"feature-protocol-v2", "%d", &val) < 0)
|
|
val = 0;
|
|
if (!val) {
|
|
xenbus_dev_fatal(dev, -EINVAL,
|
|
"vTPM protocol 2 required");
|
|
return;
|
|
}
|
|
xenbus_switch_state(dev, XenbusStateConnected);
|
|
break;
|
|
|
|
case XenbusStateClosing:
|
|
case XenbusStateClosed:
|
|
device_unregister(&dev->dev);
|
|
xenbus_frontend_closed(dev);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static const struct xenbus_device_id tpmfront_ids[] = {
|
|
{ "vtpm" },
|
|
{ "" }
|
|
};
|
|
MODULE_ALIAS("xen:vtpm");
|
|
|
|
static DEFINE_XENBUS_DRIVER(tpmfront, ,
|
|
.probe = tpmfront_probe,
|
|
.remove = tpmfront_remove,
|
|
.resume = tpmfront_resume,
|
|
.otherend_changed = backend_changed,
|
|
);
|
|
|
|
static int __init xen_tpmfront_init(void)
|
|
{
|
|
if (!xen_domain())
|
|
return -ENODEV;
|
|
|
|
return xenbus_register_frontend(&tpmfront_driver);
|
|
}
|
|
module_init(xen_tpmfront_init);
|
|
|
|
static void __exit xen_tpmfront_exit(void)
|
|
{
|
|
xenbus_unregister_driver(&tpmfront_driver);
|
|
}
|
|
module_exit(xen_tpmfront_exit);
|
|
|
|
MODULE_AUTHOR("Daniel De Graaf <dgdegra@tycho.nsa.gov>");
|
|
MODULE_DESCRIPTION("Xen vTPM Driver");
|
|
MODULE_LICENSE("GPL");
|