| // SPDX-License-Identifier: GPL-2.0 |
| #include <linux/stat.h> |
| #include <linux/sysctl.h> |
| #include "../fs/xfs/xfs_sysctl.h" |
| #include <linux/sunrpc/debug.h> |
| #include <linux/string.h> |
| #include <linux/syscalls.h> |
| #include <linux/namei.h> |
| #include <linux/mount.h> |
| #include <linux/fs.h> |
| #include <linux/nsproxy.h> |
| #include <linux/pid_namespace.h> |
| #include <linux/file.h> |
| #include <linux/ctype.h> |
| #include <linux/netdevice.h> |
| #include <linux/kernel.h> |
| #include <linux/uuid.h> |
| #include <linux/slab.h> |
| #include <linux/compat.h> |
| |
| static ssize_t binary_sysctl(const int *name, int nlen, |
| void __user *oldval, size_t oldlen, void __user *newval, size_t newlen) |
| { |
| return -ENOSYS; |
| } |
| |
| static void deprecated_sysctl_warning(const int *name, int nlen) |
| { |
| int i; |
| |
| /* |
| * CTL_KERN/KERN_VERSION is used by older glibc and cannot |
| * ever go away. |
| */ |
| if (nlen >= 2 && name[0] == CTL_KERN && name[1] == KERN_VERSION) |
| return; |
| |
| if (printk_ratelimit()) { |
| printk(KERN_INFO |
| "warning: process `%s' used the deprecated sysctl " |
| "system call with ", current->comm); |
| for (i = 0; i < nlen; i++) |
| printk(KERN_CONT "%d.", name[i]); |
| printk(KERN_CONT "\n"); |
| } |
| return; |
| } |
| |
| #define WARN_ONCE_HASH_BITS 8 |
| #define WARN_ONCE_HASH_SIZE (1<<WARN_ONCE_HASH_BITS) |
| |
| static DECLARE_BITMAP(warn_once_bitmap, WARN_ONCE_HASH_SIZE); |
| |
| #define FNV32_OFFSET 2166136261U |
| #define FNV32_PRIME 0x01000193 |
| |
| /* |
| * Print each legacy sysctl (approximately) only once. |
| * To avoid making the tables non-const use a external |
| * hash-table instead. |
| * Worst case hash collision: 6, but very rarely. |
| * NOTE! We don't use the SMP-safe bit tests. We simply |
| * don't care enough. |
| */ |
| static void warn_on_bintable(const int *name, int nlen) |
| { |
| int i; |
| u32 hash = FNV32_OFFSET; |
| |
| for (i = 0; i < nlen; i++) |
| hash = (hash ^ name[i]) * FNV32_PRIME; |
| hash %= WARN_ONCE_HASH_SIZE; |
| if (__test_and_set_bit(hash, warn_once_bitmap)) |
| return; |
| deprecated_sysctl_warning(name, nlen); |
| } |
| |
| static ssize_t do_sysctl(int __user *args_name, int nlen, |
| void __user *oldval, size_t oldlen, void __user *newval, size_t newlen) |
| { |
| int name[CTL_MAXNAME]; |
| int i; |
| |
| /* Check args->nlen. */ |
| if (nlen < 0 || nlen > CTL_MAXNAME) |
| return -ENOTDIR; |
| /* Read in the sysctl name for simplicity */ |
| for (i = 0; i < nlen; i++) |
| if (get_user(name[i], args_name + i)) |
| return -EFAULT; |
| |
| warn_on_bintable(name, nlen); |
| |
| return binary_sysctl(name, nlen, oldval, oldlen, newval, newlen); |
| } |
| |
| SYSCALL_DEFINE1(sysctl, struct __sysctl_args __user *, args) |
| { |
| struct __sysctl_args tmp; |
| size_t oldlen = 0; |
| ssize_t result; |
| |
| if (copy_from_user(&tmp, args, sizeof(tmp))) |
| return -EFAULT; |
| |
| if (tmp.oldval && !tmp.oldlenp) |
| return -EFAULT; |
| |
| if (tmp.oldlenp && get_user(oldlen, tmp.oldlenp)) |
| return -EFAULT; |
| |
| result = do_sysctl(tmp.name, tmp.nlen, tmp.oldval, oldlen, |
| tmp.newval, tmp.newlen); |
| |
| if (result >= 0) { |
| oldlen = result; |
| result = 0; |
| } |
| |
| if (tmp.oldlenp && put_user(oldlen, tmp.oldlenp)) |
| return -EFAULT; |
| |
| return result; |
| } |
| |
| |
| #ifdef CONFIG_COMPAT |
| |
| struct compat_sysctl_args { |
| compat_uptr_t name; |
| int nlen; |
| compat_uptr_t oldval; |
| compat_uptr_t oldlenp; |
| compat_uptr_t newval; |
| compat_size_t newlen; |
| compat_ulong_t __unused[4]; |
| }; |
| |
| COMPAT_SYSCALL_DEFINE1(sysctl, struct compat_sysctl_args __user *, args) |
| { |
| struct compat_sysctl_args tmp; |
| compat_size_t __user *compat_oldlenp; |
| size_t oldlen = 0; |
| ssize_t result; |
| |
| if (copy_from_user(&tmp, args, sizeof(tmp))) |
| return -EFAULT; |
| |
| if (tmp.oldval && !tmp.oldlenp) |
| return -EFAULT; |
| |
| compat_oldlenp = compat_ptr(tmp.oldlenp); |
| if (compat_oldlenp && get_user(oldlen, compat_oldlenp)) |
| return -EFAULT; |
| |
| result = do_sysctl(compat_ptr(tmp.name), tmp.nlen, |
| compat_ptr(tmp.oldval), oldlen, |
| compat_ptr(tmp.newval), tmp.newlen); |
| |
| if (result >= 0) { |
| oldlen = result; |
| result = 0; |
| } |
| |
| if (compat_oldlenp && put_user(oldlen, compat_oldlenp)) |
| return -EFAULT; |
| |
| return result; |
| } |
| |
| #endif /* CONFIG_COMPAT */ |