Re: [PATCH] x86/msr: Restrict /dev/cpu/*/msr read and write to a single MSR per call
From: H. Peter Anvin
Date: Fri Jun 26 2026 - 14:11:19 EST
On June 26, 2026 10:40:36 AM PDT, Tim Wiederhake <twiederh@xxxxxxxxxx> wrote:
>Reading or writing /dev/cpu/*/msr with a buffer larger than 8 bytes
>reads or writes not successive MSRs, but the same one over and over.
>While documented, this behavior is unintuitive and not helpful.
>
>Remove the loops and process a single 8-byte MSR value per call.
>
>Signed-off-by: Tim Wiederhake <twiederh@xxxxxxxxxx>
>---
>This is a resend of a patch originally submitted in May 2023 [1],
>updated to also cover msr_write and rebased to v7.1.
>
>[1] https://lkml.org/lkml/2023/5/23/1230
>
> arch/x86/kernel/msr.c | 48 +++++++++++++++----------------------------
> 1 file changed, 16 insertions(+), 32 deletions(-)
>
>diff --git a/arch/x86/kernel/msr.c b/arch/x86/kernel/msr.c
>index 4469c784eaa0..528a8e9b2319 100644
>--- a/arch/x86/kernel/msr.c
>+++ b/arch/x86/kernel/msr.c
>@@ -10,8 +10,7 @@
> * x86 MSR access device
> *
> * This device is accessed by lseek() to the appropriate register number
>- * and then read/write in chunks of 8 bytes. A larger size means multiple
>- * reads or writes of the same register.
>+ * and then read/write in chunks of 8 bytes.
> *
> * This driver uses /dev/cpu/%d/msr where %d is the minor number, and on
> * an SMP box will direct the access to CPU %d.
>@@ -57,24 +56,17 @@ static ssize_t msr_read(struct file *file, char __user *buf,
> u32 reg = *ppos;
> int cpu = iminor(file_inode(file));
> int err = 0;
>- ssize_t bytes = 0;
>
>- if (count % 8)
>+ if (count < 8)
> return -EINVAL; /* Invalid chunk size */
>
>- for (; count; count -= 8) {
>- err = rdmsr_safe_on_cpu(cpu, reg, &data[0], &data[1]);
>- if (err)
>- break;
>- if (copy_to_user(tmp, &data, 8)) {
>- err = -EFAULT;
>- break;
>- }
>- tmp += 2;
>- bytes += 8;
>- }
>+ err = rdmsr_safe_on_cpu(cpu, reg, &data[0], &data[1]);
>+ if (err)
>+ return err;
>+ if (copy_to_user(tmp, &data, 8))
>+ return -EFAULT;
>
>- return bytes ? bytes : err;
>+ return 8;
> }
>
> static int filter_write(u32 reg)
>@@ -113,7 +105,6 @@ static ssize_t msr_write(struct file *file, const char __user *buf,
> u32 reg = *ppos;
> int cpu = iminor(file_inode(file));
> int err = 0;
>- ssize_t bytes = 0;
>
> err = security_locked_down(LOCKDOWN_MSR);
> if (err)
>@@ -123,26 +114,19 @@ static ssize_t msr_write(struct file *file, const char __user *buf,
> if (err)
> return err;
>
>- if (count % 8)
>+ if (count < 8)
> return -EINVAL; /* Invalid chunk size */
>
>- for (; count; count -= 8) {
>- if (copy_from_user(&data, tmp, 8)) {
>- err = -EFAULT;
>- break;
>- }
>-
>- add_taint(TAINT_CPU_OUT_OF_SPEC, LOCKDEP_STILL_OK);
>+ if (copy_from_user(&data, tmp, 8))
>+ return -EFAULT;
>
>- err = wrmsr_safe_on_cpu(cpu, reg, data[0], data[1]);
>- if (err)
>- break;
>+ add_taint(TAINT_CPU_OUT_OF_SPEC, LOCKDEP_STILL_OK);
>
>- tmp += 2;
>- bytes += 8;
>- }
>+ err = wrmsr_safe_on_cpu(cpu, reg, data[0], data[1]);
>+ if (err)
>+ return err;
>
>- return bytes ? bytes : err;
>+ return 8;
> }
>
> static long msr_ioctl(struct file *file, unsigned int ioc, unsigned long arg)
As you say, it is documented and intentional. It isn't necessarily useful for MSRs that behave like registers, but an MSR with I/O-like characteristics (say, a virtual MSR that accepts logging information) it can be useful.