From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1753038AbcBZAOU (ORCPT ); Thu, 25 Feb 2016 19:14:20 -0500 Received: from prdiron-3.llnl.gov ([128.15.143.173]:59773 "EHLO prdiron-3.llnl.gov" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1752810AbcBZANB (ORCPT ); Thu, 25 Feb 2016 19:13:01 -0500 X-Greylist: delayed 569 seconds by postgrey-1.27 at vger.kernel.org; Thu, 25 Feb 2016 19:12:59 EST X-Attachments: X-Attachments: IronPort-PHdr: =?us-ascii?q?9a23=3AyE2i8xCykIjCh++5QMs5UyQJP3N1i/DPJgcQr6Af?= =?us-ascii?q?oPdwSP74oMbcNUDSrc9gkEXOFd2CrakU1KyI6eu5CCQp2tWojjMrSNR0TRgLiM?= =?us-ascii?q?EbzUQLIfWuLgnFFsPsdDEwB89YVVVorDmROElRH9viNRWJ+iXhpQAbFhi3Dwdp?= =?us-ascii?q?POO9QteU1JTokb7osMSCKyxzxxODIppKZC2sqgvQssREyaBDEY0WjiXzn31TZu?= =?us-ascii?q?5NznlpL1/A1zz158O34YIxu38I46Fp8s9bSqT8cOwEavRWDTkiPyh1v5Wq5lHt?= =?us-ascii?q?VwyKs14VSHkH2j9DLQHJ4Bz8FsP0uSXzuuZw3nmyOcnwSfY7Xir0vPQjcwPhlC?= =?us-ascii?q?pSb21xy2rQkMEl1/pW?= X-IronPort-Anti-Spam-Filtered: true X-IronPort-Anti-Spam-Result: =?us-ascii?q?A2ExBQBWlc9W/zk7CYZeGQEBAg8BAQEBB?= =?us-ascii?q?gEBAQGEMwyqGY4TghMBDYFmhg0CgXoUAQEBAQEBAQECYSeCLYIMCQEBBCdSEFF?= =?us-ascii?q?XGYVygi0FvRgBAQEHAQEBAQEbg0+OQoEqAQSHU4ZLPYgtb4xwAoFch2mFLVyNb?= =?us-ascii?q?R4BAUKBfgICARmBaRsuiBgBAQU?= X-IPAS-Result: =?us-ascii?q?A2ExBQBWlc9W/zk7CYZeGQEBAg8BAQEBBgEBAQGEMwyqGY4?= =?us-ascii?q?TghMBDYFmhg0CgXoUAQEBAQEBAQECYSeCLYIMCQEBBCdSEFFXGYVygi0FvRgBA?= =?us-ascii?q?QEHAQEBAQEbg0+OQoEqAQSHU4ZLPYgtb4xwAoFch2mFLVyNbR4BAUKBfgICARm?= =?us-ascii?q?BaRsuiBgBAQU?= From: Marty McFadden Cc: ak@linux.intel.com, andriy.shevchenko@linux.intel.com, bp@alien8.de, bp@suse.de, brgerst@gmail.com, dan.j.williams@intel.com, dyoung@redhat.com, hpa@zytor.com, linux@horizon.com, linux-kernel@vger.kernel.org, luto@kernel.org, mcfadden8@llnl.gov, mingo@redhat.com, pavel@ucw.cz, tglx@linutronix.de, viro@zeniv.linux.org.uk, x86@kernel.org, yu.c.chen@intel.com Subject: [PATCH 3/4] MSR: msr Whitelist Implementation Date: Thu, 25 Feb 2016 16:02:58 -0800 Message-Id: <1456444979-224547-4-git-send-email-mcfadden8@llnl.gov> X-Mailer: git-send-email 1.7.1 In-Reply-To: <1456444979-224547-1-git-send-email-mcfadden8@llnl.gov> References: <1456444979-224547-1-git-send-email-mcfadden8@llnl.gov> To: unlisted-recipients:; (no To-header on input) Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Allows the administrator to configure a set of bit masks for MSRs where access is permitted. Whitelist Administration: To configure whitelist (as root): cat whitelistfile > /dev/cpu/msr_whitelist This operation will cause the previous whitelist to be replaced by the specified whitelist. To enumerate current whitelist (as root): cat < /dev/cpu/msr_whitelist To remove whitelist (as root): echo > /dev/cpu/msr_whitelist Security model: If user has CAP_SYS_RAWIO privileges, they will enjoy full access to MSRs like they do today. Otherwise, if the user is able to open the /dev/cpu/*/msr file, they will have access to MSR operations as follows: If the write mask exists for a particular MSR, then rdmsr access to that MSR access is granted. If the write mask is set to all ones (0xffffffffffffffff), then the user may perform a "raw" wrmsr operation with all 64 bits being overwritten to that MSR. If the write mask is not 0xffffffffffffffff, then a rdmsr will be performed first and only the bits set in the write mask will be affected in the MSR. Signed-off-by: Marty McFadden --- arch/x86/kernel/Makefile | 2 +- arch/x86/kernel/msr_entry.c | 59 ++++++- arch/x86/kernel/msr_whitelist.c | 344 +++++++++++++++++++++++++++++++++++++++ arch/x86/kernel/msr_whitelist.h | 38 +++++ 4 files changed, 438 insertions(+), 5 deletions(-) create mode 100644 arch/x86/kernel/msr_whitelist.c create mode 100644 arch/x86/kernel/msr_whitelist.h diff --git a/arch/x86/kernel/Makefile b/arch/x86/kernel/Makefile index 7e96dff..7ceed68 100644 --- a/arch/x86/kernel/Makefile +++ b/arch/x86/kernel/Makefile @@ -54,7 +54,7 @@ obj-$(CONFIG_STACKTRACE) += stacktrace.o obj-y += cpu/ obj-y += acpi/ obj-y += reboot.o -msr-y += msr_entry.o +msr-y += msr_entry.o msr_whitelist.o obj-$(CONFIG_X86_MSR) += msr.o obj-$(CONFIG_X86_CPUID) += cpuid.o obj-$(CONFIG_PCI) += early-quirks.o diff --git a/arch/x86/kernel/msr_entry.c b/arch/x86/kernel/msr_entry.c index 64f9616..216cd87 100644 --- a/arch/x86/kernel/msr_entry.c +++ b/arch/x86/kernel/msr_entry.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -42,8 +43,12 @@ #include #include +#include "msr_whitelist.h" static struct class *msr_class; +struct msr_session_info { + int rawio_allowed; +}; static ssize_t msr_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) @@ -54,10 +59,14 @@ static ssize_t msr_read(struct file *file, char __user *buf, int cpu = iminor(file_inode(file)); int err = 0; ssize_t bytes = 0; + struct msr_session_info *myinfo = file->private_data; if (count % 8) return -EINVAL; /* Invalid chunk size */ + if (!myinfo->rawio_allowed && !msr_whitelist_maskexists(reg)) + return -EACCES; + for (; count; count -= 8) { err = rdmsr_safe_on_cpu(cpu, reg, &data[0], &data[1]); if (err) @@ -77,20 +86,41 @@ static ssize_t msr_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { const u32 __user *tmp = (const u32 __user *)buf; + u32 curdata[2]; u32 data[2]; u32 reg = *ppos; + u64 mask; int cpu = iminor(file_inode(file)); int err = 0; ssize_t bytes = 0; + struct msr_session_info *myinfo = file->private_data; if (count % 8) return -EINVAL; /* Invalid chunk size */ + mask = myinfo->rawio_allowed ? 0xffffffffffffffff : + msr_whitelist_writemask(reg); + + if (!myinfo->rawio_allowed && mask == 0) + return -EACCES; + for (; count; count -= 8) { if (copy_from_user(&data, tmp, 8)) { err = -EFAULT; break; } + + if (mask != 0xffffffffffffffff) { + err = rdmsr_safe_on_cpu(cpu, reg, + &curdata[0], &curdata[1]); + if (err) + break; + + *(u64 *)&curdata[0] &= ~mask; + *(u64 *)&data[0] &= mask; + *(u64 *)&data[0] |= *(u64 *)&curdata[0]; + } + err = wrmsr_safe_on_cpu(cpu, reg, data[0], data[1]); if (err) break; @@ -153,9 +183,7 @@ static int msr_open(struct inode *inode, struct file *file) { unsigned int cpu = iminor(file_inode(file)); struct cpuinfo_x86 *c; - - if (!capable(CAP_SYS_RAWIO)) - return -EPERM; + struct msr_session_info *myinfo; if (cpu >= nr_cpu_ids || !cpu_online(cpu)) return -ENXIO; /* No such CPU */ @@ -164,6 +192,20 @@ static int msr_open(struct inode *inode, struct file *file) if (!cpu_has(c, X86_FEATURE_MSR)) return -EIO; /* MSR not supported */ + myinfo = kmalloc(sizeof(*myinfo), GFP_KERNEL); + if (!myinfo) + return -ENOMEM; + + myinfo->rawio_allowed = capable(CAP_SYS_RAWIO); + file->private_data = myinfo; + + return 0; +} + +static int msr_close(struct inode *inode, struct file *file) +{ + kfree(file->private_data); + file->private_data = 0; return 0; } @@ -178,6 +220,7 @@ static const struct file_operations msr_fops = { .open = msr_open, .unlocked_ioctl = msr_ioctl, .compat_ioctl = msr_ioctl, + .release = msr_close }; static int msr_device_create(int cpu) @@ -227,10 +270,15 @@ static int __init msr_init(void) int i, err = 0; i = 0; + err = msr_whitelist_init(); + if (err != 0) { + pr_err("failed to initialize whitelist for msr\n"); + goto out; + } if (__register_chrdev(MSR_MAJOR, 0, NR_CPUS, "cpu/msr", &msr_fops)) { pr_err("unable to get major %d for msr\n", MSR_MAJOR); err = -EBUSY; - goto out; + goto out_wlist; } msr_class = class_create(THIS_MODULE, "msr"); if (IS_ERR(msr_class)) { @@ -259,6 +307,8 @@ out_class: class_destroy(msr_class); out_chrdev: __unregister_chrdev(MSR_MAJOR, 0, NR_CPUS, "cpu/msr"); +out_wlist: + msr_whitelist_cleanup(); out: return err; } @@ -274,6 +324,7 @@ static void __exit msr_exit(void) __unregister_chrdev(MSR_MAJOR, 0, NR_CPUS, "cpu/msr"); __unregister_hotcpu_notifier(&msr_class_cpu_notifier); cpu_notifier_register_done(); + msr_whitelist_cleanup(); } module_init(msr_init); diff --git a/arch/x86/kernel/msr_whitelist.c b/arch/x86/kernel/msr_whitelist.c new file mode 100644 index 0000000..7d8affc --- /dev/null +++ b/arch/x86/kernel/msr_whitelist.c @@ -0,0 +1,344 @@ +/* + * MSR Whitelist implementation + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_WLIST_BSIZE ((128 * 1024) + 1) /* "+1" for null character */ + +struct whitelist_entry { + u64 wmask; /* Bits that may be written */ + u64 msr; /* Address of msr (used as hash key) */ + u64 *msrdata; /* ptr to original msr contents of writable bits */ + struct hlist_node hlist; +}; + +static void delete_whitelist(void); +static int create_whitelist(int nentries); +static struct whitelist_entry *find_in_whitelist(u64 msr); +static void add_to_whitelist(struct whitelist_entry *entry); +static int parse_next_whitelist_entry(char *inbuf, char **nextinbuf, + struct whitelist_entry *entry); +static ssize_t read_whitelist(struct file *file, char __user *buf, + size_t count, loff_t *ppos); +static int majordev; +static struct class *cdev_class; +static char cdev_created; +static char cdev_registered; +static char cdev_class_created; + +static DEFINE_HASHTABLE(whitelist_hash, 6); +static DEFINE_MUTEX(whitelist_mutex); +static struct whitelist_entry *whitelist; +static int whitelist_numentries; + +int msr_whitelist_maskexists(loff_t reg) +{ + struct whitelist_entry *entry; + + mutex_lock(&whitelist_mutex); + entry = find_in_whitelist((u64)reg); + mutex_unlock(&whitelist_mutex); + + return entry != NULL; +} + +u64 msr_whitelist_writemask(loff_t reg) +{ + struct whitelist_entry *entry; + + mutex_lock(&whitelist_mutex); + entry = find_in_whitelist((u64)reg); + mutex_unlock(&whitelist_mutex); + + return entry ? entry->wmask : 0; +} + +static int open_whitelist(struct inode *inode, struct file *file) +{ + return 0; +} + +/* + * After copying data from user space, we make two passes through it. + * The first pass is to ensure that the input file is valid. If the file is + * valid, we will then delete the current white list and then perform the + * second pass to actually create the new white list. + */ +static ssize_t write_whitelist(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + int err = 0; + const u32 __user *tmp = (const u32 __user *)buf; + char *s; + int res; + int num_entries; + struct whitelist_entry *entry; + char *kbuf; + + if (count <= 2) { + mutex_lock(&whitelist_mutex); + delete_whitelist(); + hash_init(whitelist_hash); + mutex_unlock(&whitelist_mutex); + return count; + } + + if (count+1 > MAX_WLIST_BSIZE) { + pr_err("write_whitelist: buffer of %zu bytes too large\n", + count); + return -EINVAL; + } + + kbuf = kzalloc(count+1, GFP_KERNEL); + if (!kbuf) + return -ENOMEM; + + if (copy_from_user(kbuf, tmp, count)) { + err = -EFAULT; + goto out_freebuffer; + } + + /* Pass 1: */ + for (num_entries = 0, s = kbuf, res = 1; res > 0; ) { + res = parse_next_whitelist_entry(s, &s, 0); + if (res < 0) { + err = res; + goto out_freebuffer; + } + + if (res) + num_entries++; + } + + /* Pass 2: */ + mutex_lock(&whitelist_mutex); + res = create_whitelist(num_entries); + if (res < 0) { + err = res; + goto out_releasemutex; + } + + for (entry = whitelist, s = kbuf, res = 1; res > 0; entry++) { + res = parse_next_whitelist_entry(s, &s, entry); + if (res < 0) { + pr_alert("write_whitelist: Table corrupted\n"); + delete_whitelist(); + err = res; /* This should not happen! */ + goto out_releasemutex; + } + + if (res) { + if (find_in_whitelist(entry->msr)) { + pr_err("write_whitelist: Duplicate: %llx\n", + entry->msr); + err = -EINVAL; + delete_whitelist(); + goto out_releasemutex; + } + add_to_whitelist(entry); + } + } + +out_releasemutex: + mutex_unlock(&whitelist_mutex); +out_freebuffer: + kfree(kbuf); + return err ? err : count; +} + +static ssize_t read_whitelist(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + loff_t idx = *ppos; + u32 __user *tmp = (u32 __user *) buf; + char kbuf[160]; + int len; + struct whitelist_entry e; + + mutex_lock(&whitelist_mutex); + *ppos = 0; + + if (idx >= whitelist_numentries || idx < 0) { + mutex_unlock(&whitelist_mutex); + return 0; + } + + e = whitelist[idx]; + mutex_unlock(&whitelist_mutex); + + len = sprintf(kbuf, + "MSR: %08llx Write Mask: %016llx\n", e.msr, e.wmask); + + if (len > count) + return -EFAULT; + + if (copy_to_user(tmp, kbuf, len)) + return -EFAULT; + + *ppos = idx+1; + return len; +} + +static const struct file_operations fops = { + .owner = THIS_MODULE, + .read = read_whitelist, + .write = write_whitelist, + .open = open_whitelist +}; + +static void delete_whitelist(void) +{ + if (whitelist == 0) + return; + + if (whitelist->msrdata != 0) + kfree(whitelist->msrdata); + + kfree(whitelist); + whitelist = 0; + whitelist_numentries = 0; +} + +static int create_whitelist(int nentries) +{ + hash_init(whitelist_hash); + delete_whitelist(); + whitelist_numentries = nentries; + whitelist = kcalloc(nentries, sizeof(*whitelist), GFP_KERNEL); + + if (!whitelist) + return -ENOMEM; + return 0; +} + +static struct whitelist_entry *find_in_whitelist(u64 msr) +{ + struct whitelist_entry *entry = 0; + + if (whitelist) { + hash_for_each_possible(whitelist_hash, entry, hlist, msr) + if (entry && entry->msr == msr) + return entry; + } + return 0; +} + +static void add_to_whitelist(struct whitelist_entry *entry) +{ + hash_add(whitelist_hash, &entry->hlist, entry->msr); +} + +static int parse_next_whitelist_entry(char *inbuf, char **nextinbuf, + struct whitelist_entry *entry) +{ + char *s = skip_spaces(inbuf); + int i; + u64 data[2]; + + while (*s == '#') { /* Skip remaining portion of line */ + for (s = s + 1; *s && *s != '\n'; s++) + ; + s = skip_spaces(s); + } + + if (*s == 0) + return 0; /* This means we are done with the input buffer */ + + for (i = 0; i < 2; i++) {/* we should have the first of 3 #s now */ + char *s2; + int err; + char tmp; + + s2 = s = skip_spaces(s); + while (!isspace(*s) && *s) + s++; + + if (*s == 0) { + pr_err("parse_next_whitelist_entry: Premature EOF"); + return -EINVAL; + } + + tmp = *s; + *s = 0; /* Null-terminate this portion of string */ + err = kstrtoull(s2, 0, &data[i]); + if (err) + return err; + *s++ = tmp; + } + + if (entry) { + entry->msr = data[0]; + entry->wmask = data[1]; + } + + *nextinbuf = s; /* Return where we left off to caller */ + return *nextinbuf - inbuf; +} + +static char *msr_whitelist_nodename(struct device *dev, umode_t *mode) +{ + return kasprintf(GFP_KERNEL, "cpu/msr_whitelist"); +} + +void msr_whitelist_cleanup(void) +{ + delete_whitelist(); + + if (cdev_created) { + cdev_created = 0; + device_destroy(cdev_class, MKDEV(majordev, 0)); + } + + if (cdev_class_created) { + cdev_class_created = 0; + class_destroy(cdev_class); + } + + if (cdev_registered) { + cdev_registered = 0; + unregister_chrdev(majordev, "cpu/msr_whitelist"); + } +} + +int msr_whitelist_init(void) +{ + int err; + struct device *dev; + + majordev = register_chrdev(0, "cpu/msr_whitelist", &fops); + if (majordev < 0) { + pr_err("msr_whitelist_init: unable to register chrdev\n"); + msr_whitelist_cleanup(); + return -EBUSY; + } + cdev_registered = 1; + + cdev_class = class_create(THIS_MODULE, "msr_whitelist"); + if (IS_ERR(cdev_class)) { + err = PTR_ERR(cdev_class); + msr_whitelist_cleanup(); + return err; + } + cdev_class_created = 1; + + cdev_class->devnode = msr_whitelist_nodename; + + dev = device_create(cdev_class, NULL, MKDEV(majordev, 0), + NULL, "msr_whitelist"); + if (IS_ERR(dev)) { + err = PTR_ERR(dev); + msr_whitelist_cleanup(); + return err; + } + cdev_created = 1; + return 0; +} diff --git a/arch/x86/kernel/msr_whitelist.h b/arch/x86/kernel/msr_whitelist.h new file mode 100644 index 0000000..529b4af --- /dev/null +++ b/arch/x86/kernel/msr_whitelist.h @@ -0,0 +1,38 @@ +/* + * Internal declarations for x86 MSR whitelist implementation functions. + * + * Copyright (c) 2015, Lawrence Livermore National Security, LLC. + * Produced at the Lawrence Livermore National Laboratory + * All rights reserved. + * + * This copyrighted material is made available to anyone wishing to use, + * modify, copy, or redistribute it subject to the terms and conditions + * of the GNU General Public License v.2. + * + * Thank you to everyone who has contributed and helped with this project: + * + * Kathleen Shoga + * Peter Bailey + * Trent D'Hooge + * Jim Foraker + * David Lowenthal + * Tapasya Patki + * Barry Rountree + * Marty McFadden + * + * Special thanks to Kendrick Shaw at Case Western Reserve University for + * his initial suggestion to explore MSRs. + * + * Latest Updates from: Marty McFadden, mcfadden8@llnl.gov + */ +#ifndef _ARCH_X68_KERNEL_MSR_WHITELIST_H +#define _ARCH_X68_KERNEL_MSR_WHITELIST_H 1 + +#include + +int msr_whitelist_init(void); +int msr_whitelist_cleanup(void); +int msr_whitelist_maskexists(loff_t reg); +u64 msr_whitelist_writemask(loff_t reg); + +#endif /* _ARCH_X68_KERNEL_MSR_WHITELIST_H */ -- 1.7.1