mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-10-28 23:24:50 +00:00
bef3efbeb8
Each read from a file in efivarfs results in two calls to EFI (one to get the file size, another to get the actual data). On X86 these EFI calls result in broadcast system management interrupts (SMI) which affect performance of the whole system. A malicious user can loop performing reads from efivarfs bringing the system to its knees. Linus suggested per-user rate limit to solve this. So we add a ratelimit structure to "user_struct" and initialize it for the root user for no limit. When allocating user_struct for other users we set the limit to 100 per second. This could be used for other places that want to limit the rate of some detrimental user action. In efivarfs if the limit is exceeded when reading, we take an interruptible nap for 50ms and check the rate limit again. Signed-off-by: Tony Luck <tony.luck@intel.com> Acked-by: Ard Biesheuvel <ard.biesheuvel@linaro.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
187 lines
3.9 KiB
C
187 lines
3.9 KiB
C
/*
|
|
* Copyright (C) 2012 Red Hat, Inc.
|
|
* Copyright (C) 2012 Jeremy Kerr <jeremy.kerr@canonical.com>
|
|
*
|
|
* 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/efi.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/mount.h>
|
|
|
|
#include "internal.h"
|
|
|
|
static ssize_t efivarfs_file_write(struct file *file,
|
|
const char __user *userbuf, size_t count, loff_t *ppos)
|
|
{
|
|
struct efivar_entry *var = file->private_data;
|
|
void *data;
|
|
u32 attributes;
|
|
struct inode *inode = file->f_mapping->host;
|
|
unsigned long datasize = count - sizeof(attributes);
|
|
ssize_t bytes;
|
|
bool set = false;
|
|
|
|
if (count < sizeof(attributes))
|
|
return -EINVAL;
|
|
|
|
if (copy_from_user(&attributes, userbuf, sizeof(attributes)))
|
|
return -EFAULT;
|
|
|
|
if (attributes & ~(EFI_VARIABLE_MASK))
|
|
return -EINVAL;
|
|
|
|
data = memdup_user(userbuf + sizeof(attributes), datasize);
|
|
if (IS_ERR(data))
|
|
return PTR_ERR(data);
|
|
|
|
bytes = efivar_entry_set_get_size(var, attributes, &datasize,
|
|
data, &set);
|
|
if (!set && bytes) {
|
|
if (bytes == -ENOENT)
|
|
bytes = -EIO;
|
|
goto out;
|
|
}
|
|
|
|
if (bytes == -ENOENT) {
|
|
drop_nlink(inode);
|
|
d_delete(file->f_path.dentry);
|
|
dput(file->f_path.dentry);
|
|
} else {
|
|
inode_lock(inode);
|
|
i_size_write(inode, datasize + sizeof(attributes));
|
|
inode_unlock(inode);
|
|
}
|
|
|
|
bytes = count;
|
|
|
|
out:
|
|
kfree(data);
|
|
|
|
return bytes;
|
|
}
|
|
|
|
static ssize_t efivarfs_file_read(struct file *file, char __user *userbuf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
struct efivar_entry *var = file->private_data;
|
|
unsigned long datasize = 0;
|
|
u32 attributes;
|
|
void *data;
|
|
ssize_t size = 0;
|
|
int err;
|
|
|
|
while (!__ratelimit(&file->f_cred->user->ratelimit)) {
|
|
if (!msleep_interruptible(50))
|
|
return -EINTR;
|
|
}
|
|
|
|
err = efivar_entry_size(var, &datasize);
|
|
|
|
/*
|
|
* efivarfs represents uncommitted variables with
|
|
* zero-length files. Reading them should return EOF.
|
|
*/
|
|
if (err == -ENOENT)
|
|
return 0;
|
|
else if (err)
|
|
return err;
|
|
|
|
data = kmalloc(datasize + sizeof(attributes), GFP_KERNEL);
|
|
|
|
if (!data)
|
|
return -ENOMEM;
|
|
|
|
size = efivar_entry_get(var, &attributes, &datasize,
|
|
data + sizeof(attributes));
|
|
if (size)
|
|
goto out_free;
|
|
|
|
memcpy(data, &attributes, sizeof(attributes));
|
|
size = simple_read_from_buffer(userbuf, count, ppos,
|
|
data, datasize + sizeof(attributes));
|
|
out_free:
|
|
kfree(data);
|
|
|
|
return size;
|
|
}
|
|
|
|
static int
|
|
efivarfs_ioc_getxflags(struct file *file, void __user *arg)
|
|
{
|
|
struct inode *inode = file->f_mapping->host;
|
|
unsigned int i_flags;
|
|
unsigned int flags = 0;
|
|
|
|
i_flags = inode->i_flags;
|
|
if (i_flags & S_IMMUTABLE)
|
|
flags |= FS_IMMUTABLE_FL;
|
|
|
|
if (copy_to_user(arg, &flags, sizeof(flags)))
|
|
return -EFAULT;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
efivarfs_ioc_setxflags(struct file *file, void __user *arg)
|
|
{
|
|
struct inode *inode = file->f_mapping->host;
|
|
unsigned int flags;
|
|
unsigned int i_flags = 0;
|
|
int error;
|
|
|
|
if (!inode_owner_or_capable(inode))
|
|
return -EACCES;
|
|
|
|
if (copy_from_user(&flags, arg, sizeof(flags)))
|
|
return -EFAULT;
|
|
|
|
if (flags & ~FS_IMMUTABLE_FL)
|
|
return -EOPNOTSUPP;
|
|
|
|
if (!capable(CAP_LINUX_IMMUTABLE))
|
|
return -EPERM;
|
|
|
|
if (flags & FS_IMMUTABLE_FL)
|
|
i_flags |= S_IMMUTABLE;
|
|
|
|
|
|
error = mnt_want_write_file(file);
|
|
if (error)
|
|
return error;
|
|
|
|
inode_lock(inode);
|
|
inode_set_flags(inode, i_flags, S_IMMUTABLE);
|
|
inode_unlock(inode);
|
|
|
|
mnt_drop_write_file(file);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static long
|
|
efivarfs_file_ioctl(struct file *file, unsigned int cmd, unsigned long p)
|
|
{
|
|
void __user *arg = (void __user *)p;
|
|
|
|
switch (cmd) {
|
|
case FS_IOC_GETFLAGS:
|
|
return efivarfs_ioc_getxflags(file, arg);
|
|
case FS_IOC_SETFLAGS:
|
|
return efivarfs_ioc_setxflags(file, arg);
|
|
}
|
|
|
|
return -ENOTTY;
|
|
}
|
|
|
|
const struct file_operations efivarfs_file_operations = {
|
|
.open = simple_open,
|
|
.read = efivarfs_file_read,
|
|
.write = efivarfs_file_write,
|
|
.llseek = no_llseek,
|
|
.unlocked_ioctl = efivarfs_file_ioctl,
|
|
};
|