Skip to content
Snippets Groups Projects
sysctl_binary.c 51 KiB
Newer Older
  • Learn to ignore specific revisions
  • 				str++;
    			
    			result = -EFAULT;
    			if (put_user(value, vec + i))
    				goto out_kfree;
    
    			copied += sizeof(*vec);
    			if (!isdigit(*str))
    				break;
    		}
    	}
    
    	if (newval && newlen) {
    		unsigned __user *vec = newval;
    		size_t length = newlen / sizeof(*vec);
    		char *str, *end;
    		int i;
    
    		str = buffer;
    		end = str + BUFSZ;
    		for (i = 0; i < length; i++) {
    			unsigned long value;
    
    			result = -EFAULT;
    			if (get_user(value, vec + i))
    				goto out_kfree;
    
    			str += snprintf(str, end - str, "%lu\t", value);
    		}
    
    
    		result = kernel_write(file, buffer, str - buffer, 0);
    
    		if (result < 0)
    			goto out_kfree;
    	}
    	result = copied;
    out_kfree:
    	kfree(buffer);
    out:
    	return result;
    }
    
    static ssize_t bin_ulongvec(struct file *file,
    	void __user *oldval, size_t oldlen, void __user *newval, size_t newlen)
    
    	ssize_t copied = 0;
    	char *buffer;
    	ssize_t result;
    
    	result = -ENOMEM;
    	buffer = kmalloc(BUFSZ, GFP_KERNEL);
    	if (!buffer)
    		goto out;
    
    	if (oldval && oldlen) {
    		unsigned long __user *vec = oldval;
    		size_t length = oldlen / sizeof(*vec);
    		char *str, *end;
    		int i;
    
    
    		result = kernel_read(file, 0, buffer, BUFSZ - 1);
    
    		if (result < 0)
    			goto out_kfree;
    
    		str = buffer;
    		end = str + result;
    		*end++ = '\0';
    		for (i = 0; i < length; i++) {
    			unsigned long value;
    
    			value = simple_strtoul(str, &str, 10);
    			while (isspace(*str))
    				str++;
    			
    			result = -EFAULT;
    			if (put_user(value, vec + i))
    				goto out_kfree;
    
    			copied += sizeof(*vec);
    			if (!isdigit(*str))
    				break;
    		}
    
    	if (newval && newlen) {
    		unsigned long __user *vec = newval;
    		size_t length = newlen / sizeof(*vec);
    		char *str, *end;
    		int i;
    
    		str = buffer;
    		end = str + BUFSZ;
    		for (i = 0; i < length; i++) {
    			unsigned long value;
    
    			result = -EFAULT;
    			if (get_user(value, vec + i))
    				goto out_kfree;
    
    			str += snprintf(str, end - str, "%lu\t", value);
    		}
    
    
    		result = kernel_write(file, buffer, str - buffer, 0);
    
    	result = copied;
    out_kfree:
    	kfree(buffer);
    out:
    	return result;
    }
    
    static ssize_t bin_uuid(struct file *file,
    	void __user *oldval, size_t oldlen, void __user *newval, size_t newlen)
    
    	ssize_t result, copied = 0;
    
    	/* Only supports reads */
    	if (oldval && oldlen) {
    		char buf[40], *str = buf;
    		unsigned char uuid[16];
    		int i;
    
    
    		result = kernel_read(file, 0, buf, sizeof(buf) - 1);
    
    		if (result < 0)
    			goto out;
    
    		buf[result] = '\0';
    
    		/* Convert the uuid to from a string to binary */
    		for (i = 0; i < 16; i++) {
    			result = -EIO;
    			if (!isxdigit(str[0]) || !isxdigit(str[1]))
    				goto out;
    
    
    			uuid[i] = (hex_to_bin(str[0]) << 4) |
    					hex_to_bin(str[1]);
    
    			str += 2;
    			if (*str == '-')
    				str++;
    		}
    
    		if (oldlen > 16)
    			oldlen = 16;
    
    		result = -EFAULT;
    		if (copy_to_user(oldval, uuid, oldlen))
    			goto out;
    
    		copied = oldlen;
    	}
    	result = copied;
    out:
    	return result;
    }
    
    static ssize_t bin_dn_node_address(struct file *file,
    	void __user *oldval, size_t oldlen, void __user *newval, size_t newlen)
    {
    	ssize_t result, copied = 0;
    
    	if (oldval && oldlen) {
    		char buf[15], *nodep;
    		unsigned long area, node;
    		__le16 dnaddr;
    
    
    		result = kernel_read(file, 0, buf, sizeof(buf) - 1);
    
    		/* Convert the decnet address to binary */
    
    		nodep = strchr(buf, '.');
    
    
    		area = simple_strtoul(buf, NULL, 10);
    		node = simple_strtoul(nodep, NULL, 10);
    
    		result = -EIO;
    		if ((area > 63)||(node > 1023))
    			goto out;
    
    		dnaddr = cpu_to_le16((area << 10) | node);
    
    		result = -EFAULT;
    		if (put_user(dnaddr, (__le16 __user *)oldval))
    			goto out;
    
    		copied = sizeof(dnaddr);
    	}
    
    	if (newval && newlen) {
    		__le16 dnaddr;
    		char buf[15];
    		int len;
    
    		result = -EINVAL;
    		if (newlen != sizeof(dnaddr))
    			goto out;
    
    		result = -EFAULT;
    		if (get_user(dnaddr, (__le16 __user *)newval))
    			goto out;
    
    		len = snprintf(buf, sizeof(buf), "%hu.%hu",
    				le16_to_cpu(dnaddr) >> 10,
    				le16_to_cpu(dnaddr) & 0x3ff);
    
    
    		result = kernel_write(file, buf, len, 0);
    
    		if (result < 0)
    			goto out;
    	}
    
    	result = copied;
    out:
    	return result;
    }
    
    static const struct bin_table *get_sysctl(const int *name, int nlen, char *path)
    {
    	const struct bin_table *table = &bin_root_table[0];
    	int ctl_name;
    
    
    	/* The binary sysctl tables have a small maximum depth so
    	 * there is no danger of overflowing our path as it PATH_MAX
    	 * bytes long.
    	 */
    
    		return ERR_PTR(-ENOTDIR);
    	ctl_name = *name;
    	name++;
    	nlen--;
    	for ( ; table->convert; table++) {
    
    
    		/*
    		 * For a wild card entry map from ifindex to network
    		 * device name.
    		 */
    
    		if (!table->ctl_name) {
    #ifdef CONFIG_NET
    
    			struct net *net = current->nsproxy->net_ns;
    			struct net_device *dev;
    
    			dev = dev_get_by_index(net, ctl_name);
    
    			if (dev) {
    				len = strlen(dev->name);
    				memcpy(path, dev->name, len);
    				dev_put(dev);
    			}
    #endif
    
    		/* Use the well known sysctl number to proc name mapping */
    		} else if (ctl_name == table->ctl_name) {
    			len = strlen(table->procname);
    			memcpy(path, table->procname, len);
    		}
    
    static char *sysctl_getname(const int *name, int nlen, const struct bin_table **tablep)
    
    	char *tmp, *result;
    
    	result = ERR_PTR(-ENOMEM);
    	tmp = __getname();
    	if (tmp) {
    		const struct bin_table *table = get_sysctl(name, nlen, tmp);
    		result = tmp;
    		*tablep = table;
    		if (IS_ERR(table)) {
    			__putname(tmp);
    			result = ERR_CAST(table);
    
    static ssize_t binary_sysctl(const int *name, int nlen,
    	void __user *oldval, size_t oldlen, void __user *newval, size_t newlen)
    {
    	const struct bin_table *table = NULL;
    	struct vfsmount *mnt;
    	struct file *file;
    	ssize_t result;
    	char *pathname;
    	int flags;
    
    	pathname = sysctl_getname(name, nlen, &table);
    	result = PTR_ERR(pathname);
    	if (IS_ERR(pathname))
    		goto out;
    
    	/* How should the sysctl be accessed? */
    	if (oldval && oldlen && newval && newlen) {
    		flags = O_RDWR;
    	} else if (newval && newlen) {
    		flags = O_WRONLY;
    	} else if (oldval && oldlen) {
    		flags = O_RDONLY;
    	} else {
    		result = 0;
    		goto out_putname;
    	}
    
    
    	mnt = task_active_pid_ns(current)->proc_mnt;
    
    	file = file_open_root(mnt->mnt_root, mnt, pathname, flags);
    
    	result = PTR_ERR(file);
    	if (IS_ERR(file))
    		goto out_putname;
    
    	result = table->convert(file, oldval, oldlen, newval, newlen);
    
    	fput(file);
    out_putname:
    
    	__putname(pathname);
    
    static ssize_t binary_sysctl(const int *name, int nlen,
    	void __user *oldval, size_t oldlen, void __user *newval, size_t newlen)
    
    static void deprecated_sysctl_warning(const int *name, int nlen)
    
    	/*
    	 * CTL_KERN/KERN_VERSION is used by older glibc and cannot
    	 * ever go away.
    	 */
    	if (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);
    
    #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)
    
    	/* 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;
    
    
    	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.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 */