Home
Reading
Searching
Subscribe
Sponsors
Statistics
Posting
Contact
Spam
Lists
Links
About
Hosting
Filtering
Features Download
Marketing
Archives
FAQ
Blog
 
Gmane
From: Casey Schaufler <casey <at> schaufler-ca.com>
Subject: Re: [RFC] KPortReserve : kernel version of portreserve utility
Newsgroups: gmane.linux.kernel.lsm
Date: Saturday 3rd August 2013 18:20:16 UTC (over 4 years ago)
On 8/3/2013 1:15 AM, Tetsuo Handa wrote:
> Hello.
>
> There is a blog post regarding how to reliably reserve local port numbers
at
> http://cyberelk.net/tim/2012/02/15/portreserve-systemd-solution/
. Recently I
> heard a trouble in a RHEL5 system where portreserve utility is not
available.
>
> I wrote this trivial LSM module as a really race-proof solution. But I'm
> expecting that this functionality becomes available in a way that all
users can
> use regardless of their skill to use SELinux/SMACK/TOMOYO/AppArmor.
>
> (Question 1) Should this functionality implemented as LSM module?

It enforces additional restrictions on kernel operations.
It uses LSM hooks appropriately. OK, only one.
It looks like it could be useful in a number of scenarios.
I would say that it is a candidate for an LSM.

The only problem with it is that since there's only one LSM
allowed at a time you can't use it with most existing systems.

> (Question 2) If yes, should this functionality implemented as a part of
Yama?

I think not. It is unrelated to the other things that Yama does.
There has always been concern that Yama might become a dumping ground
for little features that can't get into the kernel any other way,
and this would be a prime example of that.

This is exactly the sort of thing that the multiple concurrent LSM
work is intended to address. Rather than adding this awkwardly to
another LSM, let it remain independent. It could grow into a more
general scheme for managing ports, or it could remain as a single,
small, easily understood facility.


>
> Regards.
> --------------------
> >From cbc76e3955e01dc6e590af860830b888ce7cbd0b Mon Sep 17 00:00:00 2001
> From: Tetsuo Handa 
> Date: Sat, 3 Aug 2013 16:58:05 +0900
> Subject: [PATCH] KPortReserve : kernel version of portreserve utility
>
> This module reserves local port like
/proc/sys/net/ipv4/ip_local_reserved_ports
> does, but this module is designed for stopping bind() requests with
non-zero
> local port numbers from unwanted programs.
>
> Signed-off-by: Tetsuo Handa 
> ---
>  security/Kconfig               |    6 +
>  security/Makefile              |    2 +
>  security/kportreserve/Kconfig  |   35 ++++
>  security/kportreserve/Makefile |    1 +
>  security/kportreserve/kpr.c    |  443
++++++++++++++++++++++++++++++++++++++++
>  5 files changed, 487 insertions(+), 0 deletions(-)
>  create mode 100644 security/kportreserve/Kconfig
>  create mode 100644 security/kportreserve/Makefile
>  create mode 100644 security/kportreserve/kpr.c
>
> diff --git a/security/Kconfig b/security/Kconfig
> index e9c6ac7..f4058ff 100644
> --- a/security/Kconfig
> +++ b/security/Kconfig
> @@ -122,6 +122,7 @@ source security/smack/Kconfig
>  source security/tomoyo/Kconfig
>  source security/apparmor/Kconfig
>  source security/yama/Kconfig
> +source security/kportreserve/Kconfig
>  
>  source security/integrity/Kconfig
>  
> @@ -132,6 +133,7 @@ choice
>  	default DEFAULT_SECURITY_TOMOYO if SECURITY_TOMOYO
>  	default DEFAULT_SECURITY_APPARMOR if SECURITY_APPARMOR
>  	default DEFAULT_SECURITY_YAMA if SECURITY_YAMA
> +	default DEFAULT_SECURITY_KPR if SECURITY_KPR
>  	default DEFAULT_SECURITY_DAC
>  
>  	help
> @@ -153,6 +155,9 @@ choice
>  	config DEFAULT_SECURITY_YAMA
>  		bool "Yama" if SECURITY_YAMA=y
>  
> +	config DEFAULT_SECURITY_KPR
> +		bool "KPortReserve" if SECURITY_KPR=y
> +
>  	config DEFAULT_SECURITY_DAC
>  		bool "Unix Discretionary Access Controls"
>  
> @@ -165,6 +170,7 @@ config DEFAULT_SECURITY
>  	default "tomoyo" if DEFAULT_SECURITY_TOMOYO
>  	default "apparmor" if DEFAULT_SECURITY_APPARMOR
>  	default "yama" if DEFAULT_SECURITY_YAMA
> +	default "kpr" if DEFAULT_SECURITY_KPR
>  	default "" if DEFAULT_SECURITY_DAC
>  
>  endmenu
> diff --git a/security/Makefile b/security/Makefile
> index c26c81e..87f95cc 100644
> --- a/security/Makefile
> +++ b/security/Makefile
> @@ -8,6 +8,7 @@ subdir-$(CONFIG_SECURITY_SMACK)		+= smack
>  subdir-$(CONFIG_SECURITY_TOMOYO)        += tomoyo
>  subdir-$(CONFIG_SECURITY_APPARMOR)	+= apparmor
>  subdir-$(CONFIG_SECURITY_YAMA)		+= yama
> +subdir-$(CONFIG_SECURITY_KPR)		+= kportreserve
>  
>  # always enable default capabilities
>  obj-y					+= commoncap.o
> @@ -23,6 +24,7 @@ obj-$(CONFIG_AUDIT)			+= lsm_audit.o
>  obj-$(CONFIG_SECURITY_TOMOYO)		+= tomoyo/built-in.o
>  obj-$(CONFIG_SECURITY_APPARMOR)		+= apparmor/built-in.o
>  obj-$(CONFIG_SECURITY_YAMA)		+= yama/built-in.o
> +obj-$(CONFIG_SECURITY_KPR)		+= kportreserve/built-in.o
>  obj-$(CONFIG_CGROUP_DEVICE)		+= device_cgroup.o
>  
>  # Object integrity file lists
> diff --git a/security/kportreserve/Kconfig
b/security/kportreserve/Kconfig
> new file mode 100644
> index 0000000..73ad5bc
> --- /dev/null
> +++ b/security/kportreserve/Kconfig
> @@ -0,0 +1,35 @@
> +config SECURITY_KPR
> +	bool "KPortReserve support"
> +	depends on SECURITY
> +	depends on PROC_FS
> +	select SECURITY_NETWORK
> +	default n
> +	help
> +	  This selects local port reserving module which is similar to
> +	  /proc/sys/net/ipv4/ip_local_reserved_ports . But this module is
> +	  designed for stopping bind() requests with non-zero local port
> +	  numbers from unwanted programs using white list reservations.
> +	  
> +	  If you are unsure how to answer this question, answer N.
> +	  
> +	  Usage:
> +	  
> +	  Use "add $port $program" format to add reservation.
> +	  The $port is a single port number between 0 and 65535.
> +	  The $program is the content of /proc/self/exe in TOMOYO's pathname
> +	  representation rule (i.e. consists with only ASCII printable
> +	  characters, and seen from the current thread's namespace's root (e.g.
> +	  /var/chroot/bin/bash for /bin/bash running inside /var/chroot/
> +	  chrooted environment)). The  means kernel threads).
> +	  For example,
> +	  
> +	  # echo "add 10000 /bin/bash" > /proc/reserved_local_port
> +	  # echo "add 20000 " > /proc/reserved_local_port
> +	  
> +	  allows bind() on port 10000 to /bin/bash and allows bind() on port
> +	  20000 to kernel threads.
> +	  
> +	  Use "del $port $program" format to remove reservation.
> +	  
> +	  Note that only port numbers which have at least one reservation are
> +	  checked by this module.
> diff --git a/security/kportreserve/Makefile
b/security/kportreserve/Makefile
> new file mode 100644
> index 0000000..6342521
> --- /dev/null
> +++ b/security/kportreserve/Makefile
> @@ -0,0 +1 @@
> +obj-$(CONFIG_SECURITY_KPR) := kpr.o
> diff --git a/security/kportreserve/kpr.c b/security/kportreserve/kpr.c
> new file mode 100644
> index 0000000..7b19d32
> --- /dev/null
> +++ b/security/kportreserve/kpr.c
> @@ -0,0 +1,443 @@
> +/*
> + * kpr.c - kernel version of portreserve.
> + */
> +#include 
> +#include 
> +#include 
> +#include 
> +#include 
> +#include 
> +#include 
> +#include 
> +#include 
> +
> +/* Max length of a line. */
> +#define MAX_LINE_LEN 16384
> +
> +/* Port numbers with at least one whitelist element exists. */
> +static unsigned long reserved_port_map[65536 / BITS_PER_LONG];
> +
> +/* Whitelist element. */
> +struct reserved_port_entry {
> +	struct list_head list;
> +	const char *exe;
> +	u16 port;
> +};
> +/* List of whitelist elements. */
> +static LIST_HEAD(reserved_port_list);
> +
> +/**
> + * kpr_encode - Encode binary string to ascii string.
> + *
> + * @str: String in binary format.
> + *
> + * Returns pointer to @str in ascii format on success, NULL otherwise.
> + *
> + * This function uses kzalloc(), so caller must kfree() if this function
> + * didn't return NULL.
> + */
> +static char *kpr_encode(const char *str)
> +{
> +	int i;
> +	int len = 0;
> +	const char *p = str;
> +	char *cp;
> +	char *cp0;
> +	const int str_len = strlen(str);
> +	for (i = 0; i < str_len; i++) {
> +		const unsigned char c = p[i];
> +		if (c == '\\')
> +			len += 2;
> +		else if (c > ' ' && c < 127)
> +			len++;
> +		else
> +			len += 4;
> +	}
> +	len++;
> +	cp = kzalloc(len, GFP_KERNEL);
> +	if (!cp)
> +		return NULL;
> +	cp0 = cp;
> +	p = str;
> +	for (i = 0; i < str_len; i++) {
> +		const unsigned char c = p[i];
> +		if (c == '\\') {
> +			*cp++ = '\\';
> +			*cp++ = '\\';
> +		} else if (c > ' ' && c < 127) {
> +			*cp++ = c;
> +		} else {
> +			*cp++ = '\\';
> +			*cp++ = (c >> 6) + '0';
> +			*cp++ = ((c >> 3) & 7) + '0';
> +			*cp++ = (c & 7) + '0';
> +		}
> +	}
> +	return cp0;
> +}
> +
> +/**
> + * kpr_realpath - Returns realpath(3) of the given pathname but ignores
chroot'ed root.
> + *
> + * @path: Pointer to "struct path".
> + *
> + * Returns the realpath of the given @path on success, NULL otherwise.
> + *
> + * This function uses kzalloc(), so caller must kfree() if this function
> + * didn't return NULL.
> + */
> +static char *kpr_realpath(struct path *path)
> +{
> +	char *buf = NULL;
> +	char *name = NULL;
> +	unsigned int buf_len = PAGE_SIZE / 2;
> +	while (1) {
> +		char *pos;
> +		buf_len <<= 1;
> +		kfree(buf);
> +		buf = kmalloc(buf_len, GFP_KERNEL);
> +		if (!buf)
> +			break;
> +		pos = d_absolute_path(path, buf, buf_len);
> +		if (IS_ERR(pos))
> +			continue;
> +		name = kpr_encode(pos);
> +		break;
> +	}
> +	kfree(buf);
> +	return name;
> +}
> +
> +/**
> + * kpr_get_exe - Get kpr_realpath() of current process.
> + *
> + * Returns the kpr_realpath() of current process on success, NULL
otherwise.
> + *
> + * This function uses kzalloc(), so the caller must kfree()
> + * if this function didn't return NULL.
> + */
> +static const char *kpr_get_exe(void)
> +{
> +	struct mm_struct *mm = current->mm;
> +	const char *cp = NULL;
> +	if (current->flags & PF_KTHREAD)
> +		return kstrdup("", GFP_KERNEL);
> +	if (mm) {
> +		down_read(&mm->mmap_sem);
> +		if (mm->exe_file)
> +			cp = kpr_realpath(&mm->exe_file->f_path);
> +		up_read(&mm->mmap_sem);
> +	}
> +	return cp;
> +}
> +
> +/**
> + * kpr_socket_bind - Check permission for bind().
> + *
> + * @sock:     Pointer to "struct socket".
> + * @addr:     Pointer to "struct sockaddr".
> + * @addr_len: Size of @addr.
> + *
> + * Returns 0 on success, negative value otherwise.
> + */
> +static int kpr_socket_bind(struct socket *sock, struct sockaddr *addr,
> +			   int addr_len)
> +{
> +	const char *exe;
> +	u16 port;
> +	switch (sock->sk->sk_family) {
> +	case PF_INET:
> +	case PF_INET6:
> +		break;
> +	default:
> +		return 0;
> +	}
> +	switch (sock->type) {
> +	case SOCK_STREAM:
> +	case SOCK_DGRAM:
> +		break;
> +	default:
> +		return 0;
> +	}
> +	switch (addr->sa_family) {
> +	case AF_INET:
> +		if (addr_len < sizeof(struct sockaddr_in))
> +			return 0;
> +		port = ((struct sockaddr_in *) addr)->sin_port;
> +		break;
> +	case AF_INET6:
> +		if (addr_len < SIN6_LEN_RFC2133)
> +			return 0;
> +		port = ((struct sockaddr_in6 *) addr)->sin6_port;
> +		break;
> +	default:
> +		return 0;
> +	}
> +	port = ntohs(port);
> +	if (!test_bit(port, reserved_port_map))
> +		return 0;
> +	exe = kpr_get_exe();
> +	if (!exe) {
> +		pr_warn("Unable to read /proc/self/exe . Rejecting bind(%u)
request.\n",
> +			port);
> +		return -ENOMEM;
> +	} else {
> +		struct reserved_port_entry *ptr;
> +		int ret = 0;
> +		rcu_read_lock();
> +		list_for_each_entry_rcu(ptr, &reserved_port_list, list) {
> +			if (port != ptr->port)
> +				continue;
> +			if (strcmp(exe, ptr->exe)) {
> +				ret = -EADDRINUSE;
> +				continue;
> +			}
> +			ret = 0;
> +			break;
> +		}
> +		rcu_read_unlock();
> +		kfree(exe);
> +		return ret;
> +	}
> +}
> +
> +/**
> + * kpr_read - read() for /proc/reserved_local_port interface.
> + *
> + * @file:  Pointer to "struct file".
> + * @buf:   Pointer to buffer.
> + * @count: Size of @buf.
> + * @ppos:  Offset of @file.
> + *
> + * Returns bytes read on success, negative value otherwise.
> + */
> +static ssize_t kpr_read(struct file *file, char __user *buf, size_t
count,
> +			loff_t *ppos)
> +{
> +	ssize_t copied = 0;
> +	int error = 0;
> +	int record = 0;
> +	loff_t offset = 0;
> +	char *data = vmalloc(MAX_LINE_LEN);
> +	if (!data)
> +		return -ENOMEM;
> +	while (1) {
> +		struct reserved_port_entry *ptr;
> +		int i = 0;
> +		data[0] = '\0';
> +		rcu_read_lock();
> +		list_for_each_entry_rcu(ptr, &reserved_port_list, list) {
> +			if (i++ < record)
> +				continue;
> +			snprintf(data, MAX_LINE_LEN - 1, "%u %s\n", ptr->port,
> +				 ptr->exe);
> +			break;
> +		}
> +		rcu_read_unlock();
> +		if (!data[0])
> +			break;
> +		for (i = 0; data[i]; i++) {
> +			if (offset++ < *ppos)
> +				continue;
> +			if (put_user(data[i], buf)) {
> +				error = -EFAULT;
> +				break;
> +			}
> +			buf++;
> +			copied++;
> +			(*ppos)++;
> +		}
> +		record++;
> +	}
> +	vfree(data);
> +	return copied ? copied : error;
> +}
> +
> +/**
> + * kpr_normalize_line - Format string.
> + *
> + * @buffer: The line to normalize.
> + *
> + * Returns nothing.
> + *
> + * Leading and trailing whitespaces are removed.
> + * Multiple whitespaces are packed into single space.
> + */
> +static void kpr_normalize_line(unsigned char *buffer)
> +{
> +	unsigned char *sp = buffer;
> +	unsigned char *dp = buffer;
> +	bool first = true;
> +	while (*sp && (*sp <= ' ' || *sp >= 127))
> +		sp++;
> +	while (*sp) {
> +		if (!first)
> +			*dp++ = ' ';
> +		first = false;
> +		while (*sp > ' ' && *sp < 127)
> +			*dp++ = *sp++;
> +		while (*sp && (*sp <= ' ' || *sp >= 127))
> +			sp++;
> +	}
> +	*dp = '\0';
> +}
> +
> +/**
> + * kpr_find_entry - Find an existing entry.
> + *
> + * @port: Port number.
> + * @exe:  Pathname. NULL for any.
> + *
> + * Returns pointer to existing entry if found, NULL otherwise.
> + */
> +static struct reserved_port_entry *kpr_find_entry(const u16 port,
> +						  const char *exe)
> +{
> +	struct reserved_port_entry *ptr;
> +	bool found = false;
> +	rcu_read_lock();
> +	list_for_each_entry_rcu(ptr, &reserved_port_list, list) {
> +		if (port != ptr->port)
> +			continue;
> +		if (exe && strcmp(exe, ptr->exe))
> +			continue;
> +		found = true;
> +		break;
> +	}
> +	rcu_read_unlock();
> +	return found ? ptr : NULL;
> +}
> +
> +/**
> + * kpr_update_entry - Update the list of whitelist elements.
> + *
> + * @data: Line of data to parse.
> + *
> + * Returns 0 on success, negative value otherwise.
> + *
> + * Caller holds a mutex to protect from concurrent updates.
> + */
> +static int kpr_update_entry(const char *data)
> +{
> +	struct reserved_port_entry *ptr;
> +	unsigned int port;
> +	if (sscanf(data, "add %u", &port) == 1 && port < 65536) {
> +		const char *cp = strchr(data + 4, ' ');
> +		if (!cp++ || strchr(cp, ' '))
> +			return -EINVAL;
> +		if (kpr_find_entry(port, cp))
> +			return 0;
> +		ptr = kzalloc(sizeof(*ptr), GFP_KERNEL);
> +		if (!ptr)
> +			return -ENOMEM;
> +		ptr->port = (u16) port;
> +		ptr->exe = kstrdup(cp, GFP_KERNEL);
> +		if (!ptr->exe) {
> +			kfree(ptr);
> +			return -ENOMEM;
> +		}
> +		list_add_tail_rcu(&ptr->list, &reserved_port_list);
> +		set_bit(ptr->port, reserved_port_map);
> +	} else if (sscanf(data, "del %u", &port) == 1 && port < 65536) {
> +		const char *cp = strchr(data + 4, ' ');
> +		if (!cp++ || strchr(cp, ' '))
> +			return -EINVAL;
> +		ptr = kpr_find_entry(port, cp);
> +		if (!ptr)
> +			return 0;
> +		list_del_rcu(&ptr->list);
> +		synchronize_rcu();
> +		kfree(ptr->exe);
> +		kfree(ptr);
> +		if (!kpr_find_entry(port, NULL))
> +			clear_bit(ptr->port, reserved_port_map);
> +	} else {
> +		return -EINVAL;
> +	}
> +	return 0;
> +}
> +
> +/**
> + * kpr_write - write() for /proc/reserved_local_port interface.
> + *
> + * @file:  Pointer to "struct file".
> + * @buf:   Domainname to transit to.
> + * @count: Size of @buf.
> + * @ppos:  Unused.
> + *
> + * Returns bytes parsed on success, negative value otherwise.
> + */
> +static ssize_t kpr_write(struct file *file, const char __user *buf,
> +			 size_t count, loff_t *ppos)
> +{
> +	char *data;
> +	ssize_t copied = 0;
> +	int error;
> +	if (!count)
> +		return 0;
> +	if (count > MAX_LINE_LEN - 1)
> +		count = MAX_LINE_LEN - 1;
> +	data = vmalloc(count + 1);
> +	if (!data)
> +		return -ENOMEM;
> +	if (copy_from_user(data, buf, count)) {
> +		error = -EFAULT;
> +		goto out;
> +	}
> +	data[count] = '\0';
> +	while (1) {
> +		static DEFINE_MUTEX(lock);
> +		char *cp = strchr(data, '\n');
> +		int len;
> +		if (!cp) {
> +			error = -EINVAL;
> +			break;
> +		}
> +		*cp = '\0';
> +		len = strlen(data) + 1;
> +		kpr_normalize_line(data);
> +		if (mutex_lock_interruptible(&lock)) {
> +			error = -EINTR;
> +			break;
> +		}
> +		error = kpr_update_entry(data);
> +		mutex_unlock(&lock);
> +		if (error < 0)
> +			break;
> +		copied += len;
> +		memmove(data, data + len, strlen(data + len) + 1);
> +	}
> +out:
> +	vfree(data);
> +	return copied ? copied : error;
> +}
> +
> +/* List of hooks. */
> +static struct security_operations kpr_ops = {
> +	.name        = "kpr",
> +	.socket_bind = kpr_socket_bind,
> +};
> +
> +/* Operations for /proc/reserved_local_port interface. */
> +static const struct file_operations kpr_operations = {
> +	.write = kpr_write,
> +	.read  = kpr_read,
> +};
> +
> +/**
> + * kpr_init - Initialize this module.
> + *
> + * Returns 0 on success, negative value otherwise.
> + */
> +static int __init kpr_init(void)
> +{
> +	if (!security_module_enable(&kpr_ops))
> +		return 0;
> +	if (!proc_create("reserved_local_port", 0644, NULL, &kpr_operations) ||
> +	    register_security(&kpr_ops))
> +		panic("Failure registering kportreserve");
> +	pr_info("KPortReserve initialized\n");
> +	return 0;
> +}
> +
> +late_initcall(kpr_init);

--
To unsubscribe from this list: send the line "unsubscribe
linux-security-module" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
 
CD: 3ms