mirror of
https://github.com/torvalds/linux.git
synced 2024-11-18 18:11:56 +00:00
26c5d78c97
After request_module(), nothing is stopping the module from being
unloaded until someone takes a reference to it via try_get_module().
The WARN_ONCE() in get_fs_type() is thus user-reachable, via userspace
running 'rmmod' concurrently.
Since WARN_ONCE() is for kernel bugs only, not for user-reachable
situations, downgrade this warning to pr_warn_once().
Keep it printed once only, since the intent of this warning is to detect
a bug in modprobe at boot time. Printing the warning more than once
wouldn't really provide any useful extra information.
Fixes: 41124db869
("fs: warn in case userspace lied about modprobe return")
Signed-off-by: Eric Biggers <ebiggers@google.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Reviewed-by: Jessica Yu <jeyu@kernel.org>
Cc: Alexei Starovoitov <ast@kernel.org>
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Jeff Vander Stoep <jeffv@google.com>
Cc: Jessica Yu <jeyu@kernel.org>
Cc: Kees Cook <keescook@chromium.org>
Cc: Luis Chamberlain <mcgrof@kernel.org>
Cc: NeilBrown <neilb@suse.com>
Cc: <stable@vger.kernel.org> [4.13+]
Link: http://lkml.kernel.org/r/20200312202552.241885-3-ebiggers@kernel.org
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
288 lines
6.4 KiB
C
288 lines
6.4 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* linux/fs/filesystems.c
|
|
*
|
|
* Copyright (C) 1991, 1992 Linus Torvalds
|
|
*
|
|
* table of configured filesystems
|
|
*/
|
|
|
|
#include <linux/syscalls.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/proc_fs.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/kmod.h>
|
|
#include <linux/init.h>
|
|
#include <linux/module.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/fs_parser.h>
|
|
|
|
/*
|
|
* Handling of filesystem drivers list.
|
|
* Rules:
|
|
* Inclusion to/removals from/scanning of list are protected by spinlock.
|
|
* During the unload module must call unregister_filesystem().
|
|
* We can access the fields of list element if:
|
|
* 1) spinlock is held or
|
|
* 2) we hold the reference to the module.
|
|
* The latter can be guaranteed by call of try_module_get(); if it
|
|
* returned 0 we must skip the element, otherwise we got the reference.
|
|
* Once the reference is obtained we can drop the spinlock.
|
|
*/
|
|
|
|
static struct file_system_type *file_systems;
|
|
static DEFINE_RWLOCK(file_systems_lock);
|
|
|
|
/* WARNING: This can be used only if we _already_ own a reference */
|
|
struct file_system_type *get_filesystem(struct file_system_type *fs)
|
|
{
|
|
__module_get(fs->owner);
|
|
return fs;
|
|
}
|
|
|
|
void put_filesystem(struct file_system_type *fs)
|
|
{
|
|
module_put(fs->owner);
|
|
}
|
|
|
|
static struct file_system_type **find_filesystem(const char *name, unsigned len)
|
|
{
|
|
struct file_system_type **p;
|
|
for (p = &file_systems; *p; p = &(*p)->next)
|
|
if (strncmp((*p)->name, name, len) == 0 &&
|
|
!(*p)->name[len])
|
|
break;
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* register_filesystem - register a new filesystem
|
|
* @fs: the file system structure
|
|
*
|
|
* Adds the file system passed to the list of file systems the kernel
|
|
* is aware of for mount and other syscalls. Returns 0 on success,
|
|
* or a negative errno code on an error.
|
|
*
|
|
* The &struct file_system_type that is passed is linked into the kernel
|
|
* structures and must not be freed until the file system has been
|
|
* unregistered.
|
|
*/
|
|
|
|
int register_filesystem(struct file_system_type * fs)
|
|
{
|
|
int res = 0;
|
|
struct file_system_type ** p;
|
|
|
|
if (fs->parameters &&
|
|
!fs_validate_description(fs->name, fs->parameters))
|
|
return -EINVAL;
|
|
|
|
BUG_ON(strchr(fs->name, '.'));
|
|
if (fs->next)
|
|
return -EBUSY;
|
|
write_lock(&file_systems_lock);
|
|
p = find_filesystem(fs->name, strlen(fs->name));
|
|
if (*p)
|
|
res = -EBUSY;
|
|
else
|
|
*p = fs;
|
|
write_unlock(&file_systems_lock);
|
|
return res;
|
|
}
|
|
|
|
EXPORT_SYMBOL(register_filesystem);
|
|
|
|
/**
|
|
* unregister_filesystem - unregister a file system
|
|
* @fs: filesystem to unregister
|
|
*
|
|
* Remove a file system that was previously successfully registered
|
|
* with the kernel. An error is returned if the file system is not found.
|
|
* Zero is returned on a success.
|
|
*
|
|
* Once this function has returned the &struct file_system_type structure
|
|
* may be freed or reused.
|
|
*/
|
|
|
|
int unregister_filesystem(struct file_system_type * fs)
|
|
{
|
|
struct file_system_type ** tmp;
|
|
|
|
write_lock(&file_systems_lock);
|
|
tmp = &file_systems;
|
|
while (*tmp) {
|
|
if (fs == *tmp) {
|
|
*tmp = fs->next;
|
|
fs->next = NULL;
|
|
write_unlock(&file_systems_lock);
|
|
synchronize_rcu();
|
|
return 0;
|
|
}
|
|
tmp = &(*tmp)->next;
|
|
}
|
|
write_unlock(&file_systems_lock);
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
EXPORT_SYMBOL(unregister_filesystem);
|
|
|
|
#ifdef CONFIG_SYSFS_SYSCALL
|
|
static int fs_index(const char __user * __name)
|
|
{
|
|
struct file_system_type * tmp;
|
|
struct filename *name;
|
|
int err, index;
|
|
|
|
name = getname(__name);
|
|
err = PTR_ERR(name);
|
|
if (IS_ERR(name))
|
|
return err;
|
|
|
|
err = -EINVAL;
|
|
read_lock(&file_systems_lock);
|
|
for (tmp=file_systems, index=0 ; tmp ; tmp=tmp->next, index++) {
|
|
if (strcmp(tmp->name, name->name) == 0) {
|
|
err = index;
|
|
break;
|
|
}
|
|
}
|
|
read_unlock(&file_systems_lock);
|
|
putname(name);
|
|
return err;
|
|
}
|
|
|
|
static int fs_name(unsigned int index, char __user * buf)
|
|
{
|
|
struct file_system_type * tmp;
|
|
int len, res;
|
|
|
|
read_lock(&file_systems_lock);
|
|
for (tmp = file_systems; tmp; tmp = tmp->next, index--)
|
|
if (index <= 0 && try_module_get(tmp->owner))
|
|
break;
|
|
read_unlock(&file_systems_lock);
|
|
if (!tmp)
|
|
return -EINVAL;
|
|
|
|
/* OK, we got the reference, so we can safely block */
|
|
len = strlen(tmp->name) + 1;
|
|
res = copy_to_user(buf, tmp->name, len) ? -EFAULT : 0;
|
|
put_filesystem(tmp);
|
|
return res;
|
|
}
|
|
|
|
static int fs_maxindex(void)
|
|
{
|
|
struct file_system_type * tmp;
|
|
int index;
|
|
|
|
read_lock(&file_systems_lock);
|
|
for (tmp = file_systems, index = 0 ; tmp ; tmp = tmp->next, index++)
|
|
;
|
|
read_unlock(&file_systems_lock);
|
|
return index;
|
|
}
|
|
|
|
/*
|
|
* Whee.. Weird sysv syscall.
|
|
*/
|
|
SYSCALL_DEFINE3(sysfs, int, option, unsigned long, arg1, unsigned long, arg2)
|
|
{
|
|
int retval = -EINVAL;
|
|
|
|
switch (option) {
|
|
case 1:
|
|
retval = fs_index((const char __user *) arg1);
|
|
break;
|
|
|
|
case 2:
|
|
retval = fs_name(arg1, (char __user *) arg2);
|
|
break;
|
|
|
|
case 3:
|
|
retval = fs_maxindex();
|
|
break;
|
|
}
|
|
return retval;
|
|
}
|
|
#endif
|
|
|
|
int __init get_filesystem_list(char *buf)
|
|
{
|
|
int len = 0;
|
|
struct file_system_type * tmp;
|
|
|
|
read_lock(&file_systems_lock);
|
|
tmp = file_systems;
|
|
while (tmp && len < PAGE_SIZE - 80) {
|
|
len += sprintf(buf+len, "%s\t%s\n",
|
|
(tmp->fs_flags & FS_REQUIRES_DEV) ? "" : "nodev",
|
|
tmp->name);
|
|
tmp = tmp->next;
|
|
}
|
|
read_unlock(&file_systems_lock);
|
|
return len;
|
|
}
|
|
|
|
#ifdef CONFIG_PROC_FS
|
|
static int filesystems_proc_show(struct seq_file *m, void *v)
|
|
{
|
|
struct file_system_type * tmp;
|
|
|
|
read_lock(&file_systems_lock);
|
|
tmp = file_systems;
|
|
while (tmp) {
|
|
seq_printf(m, "%s\t%s\n",
|
|
(tmp->fs_flags & FS_REQUIRES_DEV) ? "" : "nodev",
|
|
tmp->name);
|
|
tmp = tmp->next;
|
|
}
|
|
read_unlock(&file_systems_lock);
|
|
return 0;
|
|
}
|
|
|
|
static int __init proc_filesystems_init(void)
|
|
{
|
|
proc_create_single("filesystems", 0, NULL, filesystems_proc_show);
|
|
return 0;
|
|
}
|
|
module_init(proc_filesystems_init);
|
|
#endif
|
|
|
|
static struct file_system_type *__get_fs_type(const char *name, int len)
|
|
{
|
|
struct file_system_type *fs;
|
|
|
|
read_lock(&file_systems_lock);
|
|
fs = *(find_filesystem(name, len));
|
|
if (fs && !try_module_get(fs->owner))
|
|
fs = NULL;
|
|
read_unlock(&file_systems_lock);
|
|
return fs;
|
|
}
|
|
|
|
struct file_system_type *get_fs_type(const char *name)
|
|
{
|
|
struct file_system_type *fs;
|
|
const char *dot = strchr(name, '.');
|
|
int len = dot ? dot - name : strlen(name);
|
|
|
|
fs = __get_fs_type(name, len);
|
|
if (!fs && (request_module("fs-%.*s", len, name) == 0)) {
|
|
fs = __get_fs_type(name, len);
|
|
if (!fs)
|
|
pr_warn_once("request_module fs-%.*s succeeded, but still no fs?\n",
|
|
len, name);
|
|
}
|
|
|
|
if (dot && fs && !(fs->fs_flags & FS_HAS_SUBTYPE)) {
|
|
put_filesystem(fs);
|
|
fs = NULL;
|
|
}
|
|
return fs;
|
|
}
|
|
|
|
EXPORT_SYMBOL(get_fs_type);
|