All of lore.kernel.org
 help / color / mirror / Atom feed
* [kernel-hardening] Re: [RFC] KPortReserve : kernel version of portreserve utility
       [not found] <201308031715.FCH73469.OVOHQStOFMLJFF@I-love.SAKURA.ne.jp>
@ 2013-08-07 14:31 ` Vasily Kulikov
  2013-08-07 16:00   ` Casey Schaufler
  2013-08-08 10:50   ` Tetsuo Handa
  0 siblings, 2 replies; 5+ messages in thread
From: Vasily Kulikov @ 2013-08-07 14:31 UTC (permalink / raw)
  To: Tetsuo Handa; +Cc: twaugh, amwang, linux-security-module, kernel-hardening

(CC'ed kernel-hardening)

Hi Tetsuo,

See my comments inlined,

On Sat, Aug 03, 2013 at 17:15 +0900, 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?
> (Question 2) If yes, should this functionality implemented as a part of Yama?
> 
> Regards.
> --------------------
> >From cbc76e3955e01dc6e590af860830b888ce7cbd0b Mon Sep 17 00:00:00 2001
> From: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
> 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 <penguin-kernel@I-love.SAKURA.ne.jp>
> ---
>  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 <kernel> means kernel threads).
> +	  For example,
> +	  
> +	  # echo "add 10000 /bin/bash" > /proc/reserved_local_port
> +	  # echo "add 20000 <kernel>" > /proc/reserved_local_port
> +	  
> +	  allows bind() on port 10000 to /bin/bash and allows bind() on port
> +	  20000 to kernel threads.

Wait, it restrict the port to a *program*, not a user/group/security
domain?  It means *any* user may run this program and obtain the port.
Is it intentional behaviour?  I guess it would be MUCH more useful to
allow some port to this specific user, NOT a program.  For most daemons
we have separate user accounts (SELinux contexts in some distros,
whatever), so it makes sense to limit the port to a UID/GID (or LSM's
security context).

> +	  
> +	  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 <linux/security.h>
> +#include <linux/proc_fs.h>
> +#include <linux/vmalloc.h>
> +#include <linux/net.h>
> +#include <linux/inet.h>
> +#include <linux/uaccess.h>
> +#include <net/sock.h>
> +#include <net/ip.h>
> +#include <net/ipv6.h>
> +
> +/* 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;

d_absolute_path() may fail in case of not only ENOMEM, but also EINVAL.
In this case you'll OOM the kernel with too big kmalloc().  buf_len
should be limited with some (not too big) number.

> +		name = kpr_encode(pos);
> +		break;
> +	}
> +	kfree(buf);
> +	return name;
> +}
> +
> +/**
> + * kpr_get_exe - Get kpr_realpath() of current process.

Probably it should be called kpr_realpath_current() or
current_kpr_realpath() then?

> + *
> + * 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("<kernel>", 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);
> -- 
> 1.7.1
> --
> 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

Thanks,

-- 
Vasily Kulikov
http://www.openwall.com - bringing security into open computing environments

^ permalink raw reply	[flat|nested] 5+ messages in thread

* [kernel-hardening] Re: [RFC] KPortReserve : kernel version of portreserve utility
  2013-08-07 14:31 ` [kernel-hardening] Re: [RFC] KPortReserve : kernel version of portreserve utility Vasily Kulikov
@ 2013-08-07 16:00   ` Casey Schaufler
  2013-08-08 10:50   ` Tetsuo Handa
  1 sibling, 0 replies; 5+ messages in thread
From: Casey Schaufler @ 2013-08-07 16:00 UTC (permalink / raw)
  To: Vasily Kulikov
  Cc: Tetsuo Handa, twaugh, amwang, linux-security-module,
	kernel-hardening

On 8/7/2013 7:31 AM, Vasily Kulikov wrote:
> (CC'ed kernel-hardening)
>
> Hi Tetsuo,
>
> See my comments inlined,
>
> On Sat, Aug 03, 2013 at 17:15 +0900, 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?
>> (Question 2) If yes, should this functionality implemented as a part of Yama?
>>
>> Regards.
>> --------------------
>> >From cbc76e3955e01dc6e590af860830b888ce7cbd0b Mon Sep 17 00:00:00 2001
>> From: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
>> 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 <penguin-kernel@I-love.SAKURA.ne.jp>
>> ---
>>  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 <kernel> means kernel threads).
>> +	  For example,
>> +	  
>> +	  # echo "add 10000 /bin/bash" > /proc/reserved_local_port
>> +	  # echo "add 20000 <kernel>" > /proc/reserved_local_port
>> +	  
>> +	  allows bind() on port 10000 to /bin/bash and allows bind() on port
>> +	  20000 to kernel threads.
> Wait, it restrict the port to a *program*, not a user/group/security
> domain?  It means *any* user may run this program and obtain the port.
> Is it intentional behaviour?  I guess it would be MUCH more useful to
> allow some port to this specific user, NOT a program.  For most daemons
> we have separate user accounts (SELinux contexts in some distros,
> whatever), so it makes sense to limit the port to a UID/GID (or LSM's
> security context).

Very 20th century thinking.

While the security community has been toying with program based
access controls for the past four decades (UNICOS had program access
lists in the 1990s) it wasn't until "Apps" hit phones that they
began to be taken seriously. Android used (co-opted, hijacked) the
UID to accomplish this. Some (but not all) aspects of SELinux policy
in Fedora identify the program and its standing within the system.
Both of these systems abuse security attributes that are not intended
to identify programs to do just that. This limits the legitimate use
of those attributes for their original purpose.

What Tetsuo is proposing is using the information he really cares
about (the program) rather than an attribute (UID, SELinux context,
Smack label) that can be associated with the program. Further, he
is using it in a way that does not interfere with the intended use
of UIDs, labels or any other existing security attribute.

Very 21st century thinking.

>> +	  
>> +	  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 <linux/security.h>
>> +#include <linux/proc_fs.h>
>> +#include <linux/vmalloc.h>
>> +#include <linux/net.h>
>> +#include <linux/inet.h>
>> +#include <linux/uaccess.h>
>> +#include <net/sock.h>
>> +#include <net/ip.h>
>> +#include <net/ipv6.h>
>> +
>> +/* 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;
> d_absolute_path() may fail in case of not only ENOMEM, but also EINVAL.
> In this case you'll OOM the kernel with too big kmalloc().  buf_len
> should be limited with some (not too big) number.
>
>> +		name = kpr_encode(pos);
>> +		break;
>> +	}
>> +	kfree(buf);
>> +	return name;
>> +}
>> +
>> +/**
>> + * kpr_get_exe - Get kpr_realpath() of current process.
> Probably it should be called kpr_realpath_current() or
> current_kpr_realpath() then?
>
>> + *
>> + * 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("<kernel>", 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);
>> -- 
>> 1.7.1
>> --
>> 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
> Thanks,
>

^ permalink raw reply	[flat|nested] 5+ messages in thread

* [kernel-hardening] Re: [RFC] KPortReserve : kernel version of portreserve utility
  2013-08-07 14:31 ` [kernel-hardening] Re: [RFC] KPortReserve : kernel version of portreserve utility Vasily Kulikov
  2013-08-07 16:00   ` Casey Schaufler
@ 2013-08-08 10:50   ` Tetsuo Handa
  2013-08-11  2:50     ` Tetsuo Handa
  1 sibling, 1 reply; 5+ messages in thread
From: Tetsuo Handa @ 2013-08-08 10:50 UTC (permalink / raw)
  To: segoon; +Cc: twaugh, amwang, linux-security-module, kernel-hardening

Vasily Kulikov wrote:
> Wait, it restrict the port to a *program*, not a user/group/security
> domain?  It means *any* user may run this program and obtain the port.
> Is it intentional behaviour?

Yes, it is intentional behaviour. Below is the background of proposing this
functionality.

I sometimes hear of troubles caused by local port reservation conflicts.
For example, active/standby high-availability services failed to failover
because the migrating application failed to bind() to port number which the
application uses was already used by other applications (e.g. portmap ,
vsftpd ) when failover occurred.
For another example, some enterprise application failed to start because
the application failed to bind() to port numbers which the application uses
was already used by other applications when the application starts.

Regarding autobind cases, /proc/sys/net/ipv4/ip_local_reserved_ports can
prevent troubles shown above. But regarding non-autobind cases,
/proc/sys/net/ipv4/ip_local_reserved_ports cannot.

While portreserve utility is provided for managing non-autobind cases, it is
not available in RHEL4/5. While it is available in RHEL6, it is not race-free.

Thus, I decided to write a trivial, race-free kernel module which restricts
local ports to the programs.

The applications which I care are likely running as root user. Thus, I'm not
feeling that this module needs to restrict the port to a user/group/security
domain.

> I guess it would be MUCH more useful to
> allow some port to this specific user, NOT a program.  For most daemons
> we have separate user accounts (SELinux contexts in some distros,
> whatever), so it makes sense to limit the port to a UID/GID (or LSM's
> security context).

I know. Skilled users can use more complicated implementations which restrict
the port to a user/group/security domain. The goal of this module is to allow
unskilled users to use this module as simple/easy as
/proc/sys/net/ipv4/ip_local_reserved_ports .



> d_absolute_path() may fail in case of not only ENOMEM, but also EINVAL.

Indeed. I truncated too much.

Well, if the system entered into a situation where d_absolute_path() returns
EINVAL, program's pathname can never be calculated and we would need to choose
one from

  any program which got EINVAL cannot bind() the reserved port

and

  any program which got EINVAL can bind() the reserved port

if answer to Question 5 is "no".



(Question 5) Do we want to distinguish interpreter/busybox programs?

I worry that the use of /proc/self/exe is too coarse in some systems.
Say, there are four programs.

  (1) /sbin/prog1 starts with "#! /usr/bin/perl" and calls bind() with port
      50000 and the EADDRINUSE error is fatal.

  (2) /bin/prog2 starts with "#! /usr/bin/perl" and calls bind() with randomly
      chosen non-0 port numbers but the EADDRINUSE error is not fatal.

  (3) /bin/prog3 is a symlink to a multicall-binary and calls bind() with port
      60000 and the EADDRINUSE error is fatal.

  (4) /bin/prog4 is a symlink to a multicall-binary and calls bind() with
      randomly chosen non-0 port numbers but the EADDRINUSE error is not fatal.

The content of /proc/self/exe cannot distinguish (1) and (2) because they are
both /usr/bin/perl . But users might expect this module to return 0 for (1)
and return EADDRINUSE for (2).

The content of /proc/self/exe cannot distinguish (3) and (4) if they are
symlinks to the same multicall-binary. But users might expect this module
to return 0 for (3) and return EADDRINUSE for (4).

To distinguish such cases, I need to use the pathname passed to do_execve()
rather than the content of /proc/self/exe .

^ permalink raw reply	[flat|nested] 5+ messages in thread

* [kernel-hardening] Re: [RFC] KPortReserve : kernel version of portreserve utility
  2013-08-08 10:50   ` Tetsuo Handa
@ 2013-08-11  2:50     ` Tetsuo Handa
  2013-08-21 12:39       ` [PATCH v3] " Tetsuo Handa
  0 siblings, 1 reply; 5+ messages in thread
From: Tetsuo Handa @ 2013-08-11  2:50 UTC (permalink / raw)
  To: dhowells
  Cc: segoon, twaugh, amwang, linux-security-module, kernel-hardening,
	penguin-kernel

Hello.

Changes from version 1:

  (1) Changed interface file from /proc/reserved_local_port to
      /sys/kernel/security/kportreserve/entry .

  (2) Changed interface file to use "$port $program" rather than
      "add $port $program".

  (3) Changed to use the content of pathname passed to previous execve()
      request rather than the content of /proc/self/exe .

  (4) Added check whether the pathname passed to interface file is correct.

(1) is Casey's suggestion that all LSM controlling filesystems should use
securityfs.

(2) is Casey's comment that symmetric on read and write would be better.

(3) is Vasily's comment that the system may enter into condition where
d_absolute_path() returns -EINVAL. If the system enters into such condition,
this module cannot read /proc/self/exe at bind() time. Also, we might want to
distinguish interpreter-based programs and symlinked-multicall-binary-based
programs where the content of /proc/self/exe becomes the name of interpreter
program and the name of the entity of the multicall-binary program.



David, I have a question. To implement (3), I associated pathname passed to
previous execve() with "struct cred". But I'd like to check that this approach
works OK.

According to commit ee18d64c "KEYS: Add a keyctl to install a process's session
keyring on its parent [try #6]", it sounds to me that security_transfer_creds()
in key_change_session_keyring() replaces parent process's security domain with
child process's security domain.

If the child process called execve() after the parent process called fork(),
is the pathname associated with the parent process's "struct cred" replaced
with the pathname associated with the child process's "struct cred" by
security_transfer_creds() ?

Regards.
--------------------
>>From a5cdb8611328dc7877ff9c7b0e7419e77992572a Mon Sep 17 00:00:00 2001
From: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Date: Sun, 11 Aug 2013 11:41:22 +0900
Subject: [PATCHv2] 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 <penguin-kernel@I-love.SAKURA.ne.jp>
---
 security/Kconfig               |    6 +
 security/Makefile              |    2 +
 security/kportreserve/Kconfig  |   35 +++
 security/kportreserve/Makefile |    1 +
 security/kportreserve/kpr.c    |  561 ++++++++++++++++++++++++++++++++++++++++
 5 files changed, 605 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 <kernel> means kernel threads).
+	  For example,
+	  
+	  # echo "add 10000 /bin/bash" > /proc/reserved_local_port
+	  # echo "add 20000 <kernel>" > /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..7971f0b
--- /dev/null
+++ b/security/kportreserve/kpr.c
@@ -0,0 +1,561 @@
+/*
+ * kpr.c - kernel version of portreserve.
+ */
+#include <linux/security.h>
+#include <linux/vmalloc.h>
+#include <linux/net.h>
+#include <linux/inet.h>
+#include <linux/uaccess.h>
+#include <linux/binfmts.h>
+#include <net/sock.h>
+#include <net/ip.h>
+#include <net/ipv6.h>
+
+/* 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);
+
+/* Per a "struct cred" info. */
+struct task_name_info {
+	atomic_t users;
+	char exe[0]; /* Content of current "struct linux_binprm"->filename . */
+};
+
+/**
+ * kpr_cred_alloc_blank - Target for security_cred_alloc_blank().
+ *
+ * @new: Pointer to "struct cred".
+ * @gfp: Memory allocation flags.
+ *
+ * Returns 0.
+ */
+static int kpr_cred_alloc_blank(struct cred *new, gfp_t gfp)
+{
+	new->security = NULL;
+	return 0;
+}
+
+/**
+ * kpr_cred_prepare - Target for security_prepare_creds().
+ *
+ * @new: Pointer to "struct cred".
+ * @old: Pointer to "struct cred".
+ * @gfp: Memory allocation flags.
+ *
+ * Returns 0.
+ */
+static int kpr_cred_prepare(struct cred *new, const struct cred *old,
+			       gfp_t gfp)
+{
+	struct task_name_info *info = old->security;
+	new->security = info;
+	if (info)
+		atomic_inc(&info->users);
+	return 0;
+}
+
+/**
+ * kpr_cred_transfer - Target for security_transfer_creds().
+ *
+ * @new: Pointer to "struct cred".
+ * @old: Pointer to "struct cred".
+ */
+static void kpr_cred_transfer(struct cred *new, const struct cred *old)
+{
+	kpr_cred_prepare(new, old, 0);
+}
+
+/**
+ * kpr_cred_free - Target for security_cred_free().
+ *
+ * @cred: Pointer to "struct cred".
+ */
+static void kpr_cred_free(struct cred *cred)
+{
+	struct task_name_info *info = cred->security;
+	if (info && atomic_dec_and_test(&info->users))
+		kfree(info);
+}
+
+/**
+ * kpr_make_info - Encode binary string to ascii string.
+ *
+ * @str: String in binary format.
+ *
+ * Returns pointer to "struct task_info_name" with @str in ascii format on
+ * success, NULL otherwise.
+ *
+ * This function uses kmalloc(), so caller must kfree() if this function
+ * didn't return NULL.
+ */
+static struct task_name_info *kpr_make_info(const char *str)
+{
+	int i;
+	int len = 0;
+	struct task_name_info *info;
+	const char *p = str;
+	char *cp;
+	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++;
+	info = kmalloc(sizeof(*info) + len, GFP_KERNEL);
+	if (!info)
+		return NULL;
+	atomic_set(&info->users, 1);
+	cp = info->exe;
+	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';
+		}
+	}
+	*cp = '\0';
+	return info;
+}
+
+/**
+ * kpr_correct_word - Validate a string.
+ *
+ * @string: The string to check.
+ *
+ * Check whether the given string follows the naming rules.
+ * Returns true if @string follows the naming rules, false otherwise.
+ */
+static bool kpr_correct_word(const char *string)
+{
+	if (!*string)
+		return false;
+	while (1) {
+		unsigned char c = *string++;
+		if (!c)
+			return true;
+		if (c == '\\') {
+			c = *string++;
+			switch (c) {
+			case '\\':  /* "\\" */
+				continue;
+			case '0':   /* "\ooo" */
+			case '1':
+			case '2':
+			case '3':
+				{
+					unsigned char d;
+					unsigned char e;
+					c -= '0';
+					d = *string++ - '0';
+					if (d > 7)
+						break;
+					e = *string++ - '0';
+					if (e > 7)
+						break;
+					c = (c << 6) + (d << 3) + (e);
+					if (c <= ' ' || c >= 127)
+						continue;
+				}
+			}
+			return false;
+		} else if (c <= ' ' || c >= 127) {
+			return false;
+		}
+	}
+	return true;
+}
+
+/**
+ * kpr_bprm_set_creds - Target for security_bprm_set_creds().
+ *
+ * @bprm: Pointer to "struct linux_binprm".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int kpr_bprm_set_creds(struct linux_binprm *bprm)
+{
+	const int rc = cap_bprm_set_creds(bprm);
+
+	if (rc)
+		return rc;
+	if (!bprm->cred_prepared) {
+		struct task_name_info *info = kpr_make_info(bprm->filename);
+
+		if (!info)
+			return -ENOMEM;
+		kpr_cred_free(bprm->cred);
+		bprm->cred->security = info;
+	}
+	return 0;
+}
+
+
+/**
+ * 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)
+{
+	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;
+	{
+		struct reserved_port_entry *ptr;
+		int ret = 0;
+		const char *exe = ((struct task_name_info *)
+				   current_security())->exe;
+		if (!exe)
+			exe = "<unknown>";
+		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();
+		return ret;
+	}
+}
+
+/**
+ * kpr_read - read() for /sys/kernel/security/kportreserve/entry 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, "%u", &port) == 1 && port < 65536) {
+		const char *cp = strchr(data, ' ');
+		if (!cp++ || !kpr_correct_word(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++ || !kpr_correct_word(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 /sys/kernel/security/kportreserve/entry 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",
+	.cred_prepare     = kpr_cred_prepare,
+	.cred_alloc_blank = kpr_cred_alloc_blank,
+	.cred_transfer    = kpr_cred_transfer,
+	.cred_free        = kpr_cred_free,
+	.bprm_set_creds   = kpr_bprm_set_creds,
+	.socket_bind      = kpr_socket_bind,
+};
+
+static bool kpr_registered;
+
+/**
+ * kpr_register - Register this module.
+ *
+ * Returns 0.
+ */
+static int __init kpr_register(void)
+{
+	struct cred *cred = (struct cred *) current_cred();
+	struct task_name_info *info;
+	const char kernel_name[] = "<kernel>";
+
+	if (!security_module_enable(&kpr_ops))
+		return 0;
+	info = kmalloc(sizeof(*info) + sizeof(kernel_name), GFP_KERNEL);
+	if (!info)
+		goto out;
+	atomic_set(&info->users, 1);
+	memcpy(info->exe, kernel_name, sizeof(kernel_name));
+	cred->security = info;
+	if (register_security(&kpr_ops))
+		goto out;
+	kpr_registered = true;
+	pr_info("KPortReserve initialized\n");
+	return 0;
+out:
+	panic("Failure registering kportreserve");
+}
+security_initcall(kpr_register);
+
+/* Operations for /sys/kernel/security/kportreserve/entry interface. */
+static const struct file_operations kpr_operations = {
+	.write = kpr_write,
+	.read  = kpr_read,
+};
+
+/**
+ * kpr_init - Initialize this module.
+ *
+ * Returns 0.
+ */
+static int __init kpr_init(void)
+{
+	if (kpr_registered) {
+		struct dentry *kpr_dir = securityfs_create_dir("kportreserve",
+							       NULL);
+		if (!kpr_dir ||
+		    !securityfs_create_file("entry", 0644, kpr_dir, NULL,
+					    &kpr_operations))
+			panic("Failure registering kportreserve");
+	}
+	return 0;
+}
+fs_initcall(kpr_init);
-- 
1.7.1

^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH v3] KPortReserve : kernel version of portreserve utility
  2013-08-11  2:50     ` Tetsuo Handa
@ 2013-08-21 12:39       ` Tetsuo Handa
  0 siblings, 0 replies; 5+ messages in thread
From: Tetsuo Handa @ 2013-08-21 12:39 UTC (permalink / raw)
  To: linux-kernel, linux-security-module, netdev

Hello.

A good summary on this proposal written by Jake Edge is available at
http://lwn.net/SubscriberLink/563178/c8a2e2fd4a794a9e/ .

Changes from version 2:

(1) Report number of rejections, the name of process and its pid, up to once
    per a minute, in order to be able to figure out unexpected rejection
    which could be caused by misconfiguration / misunderstanding.

    Aug 21 21:28:38 localhost kernel: [  139.438347] KPortReserve:(#1): Rejected bind(22) by /root/testapp1 (pid=4636)
    Aug 21 21:31:25 localhost kernel: [  306.755200] KPortReserve:(#3): Rejected bind(80) by /root/testapp2 (pid=4688)

(2) Updated Kconfig help.

Regards.
--------------------
>From efc84232e6df17ad0a7359fb9f4b72b4f4a02ed6 Mon Sep 17 00:00:00 2001
From: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Date: Wed, 21 Aug 2013 21:19:28 +0900
Subject: [PATCH v3] 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 <penguin-kernel@I-love.SAKURA.ne.jp>
---
 security/Kconfig               |    6 +
 security/Makefile              |    2 +
 security/kportreserve/Kconfig  |   43 +++
 security/kportreserve/Makefile |    1 +
 security/kportreserve/kpr.c    |  573 ++++++++++++++++++++++++++++++++++++++++
 5 files changed, 625 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..41049ae
--- /dev/null
+++ b/security/kportreserve/Kconfig
@@ -0,0 +1,43 @@
+config SECURITY_KPR
+	bool "KPortReserve support"
+	depends on SECURITY
+	select SECURITY_NETWORK
+	select SECURITY_FS
+	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.
+
+	  Specifications:
+
+	  Use "$port $identifier" format to add reservation.
+	  Use "del $port $identifier" format to remove reservation.
+
+	  The $port is a single port number between 0 and 65535.
+	  The $identifier is an identifier word in TOMOYO's string
+	  representation rule (i.e. consists with only ASCII printable
+	  characters). Upon successful execve() operation, $identifier is
+	  automatically replaced with the filename passed to execve()
+	  operation succeeds. For example, $identifier of current thread will
+	  be changed to /usr/sbin/httpd if execve("/usr/sbin/httpd") succeeds.
+	  The kernel threads get <kernel> as the identifier, with an exception
+	  that the userspace processes will also get <kernel> as the identifier
+	  if execve("<kernel>") (i.e. executing a program named <kernel>
+	  located in the current directory) succeeds.
+
+	  Example:
+
+	  Configuring
+
+	  # echo "10000 /bin/bash" > /sys/kernel/security/kportreserve/entry
+	  # echo "20000 <kernel>" > /sys/kernel/security/kportreserve/entry
+
+	  will allow /bin/bash to bind() on port 10000 and allow <kernel> to
+	  bind() on port 20000.
+
+	  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..67bfdcb
--- /dev/null
+++ b/security/kportreserve/kpr.c
@@ -0,0 +1,573 @@
+/*
+ * kpr.c - kernel version of portreserve.
+ */
+#include <linux/security.h>
+#include <linux/vmalloc.h>
+#include <linux/net.h>
+#include <linux/inet.h>
+#include <linux/uaccess.h>
+#include <linux/binfmts.h>
+#include <net/sock.h>
+#include <net/ip.h>
+#include <net/ipv6.h>
+
+/* 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;
+	u16 port;
+	char id[0];
+};
+/* List of whitelist elements. */
+static LIST_HEAD(reserved_port_list);
+
+/* Per a "struct cred" info. */
+struct task_name_info {
+	atomic_t users;
+	char id[0];
+};
+
+/**
+ * kpr_cred_alloc_blank - Target for security_cred_alloc_blank().
+ *
+ * @new: Pointer to "struct cred".
+ * @gfp: Memory allocation flags.
+ *
+ * Returns 0.
+ */
+static int kpr_cred_alloc_blank(struct cred *new, gfp_t gfp)
+{
+	new->security = NULL;
+	return 0;
+}
+
+/**
+ * kpr_cred_prepare - Target for security_prepare_creds().
+ *
+ * @new: Pointer to "struct cred".
+ * @old: Pointer to "struct cred".
+ * @gfp: Memory allocation flags.
+ *
+ * Returns 0.
+ */
+static int kpr_cred_prepare(struct cred *new, const struct cred *old,
+			       gfp_t gfp)
+{
+	struct task_name_info *info = old->security;
+	new->security = info;
+	if (info)
+		atomic_inc(&info->users);
+	return 0;
+}
+
+/**
+ * kpr_cred_transfer - Target for security_transfer_creds().
+ *
+ * @new: Pointer to "struct cred".
+ * @old: Pointer to "struct cred".
+ */
+static void kpr_cred_transfer(struct cred *new, const struct cred *old)
+{
+	kpr_cred_prepare(new, old, 0);
+}
+
+/**
+ * kpr_cred_free - Target for security_cred_free().
+ *
+ * @cred: Pointer to "struct cred".
+ */
+static void kpr_cred_free(struct cred *cred)
+{
+	struct task_name_info *info = cred->security;
+	if (info && atomic_dec_and_test(&info->users))
+		kfree(info);
+}
+
+/**
+ * kpr_make_info - Encode binary string to ascii string.
+ *
+ * @str: String in binary format.
+ *
+ * Returns pointer to "struct task_info_name" with @str in ascii format on
+ * success, NULL otherwise.
+ *
+ * This function uses kmalloc(), so caller must kfree() if this function
+ * didn't return NULL.
+ */
+static struct task_name_info *kpr_make_info(const char *str)
+{
+	int i;
+	int len = 0;
+	struct task_name_info *info;
+	const char *p = str;
+	char *cp;
+	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++;
+	info = kmalloc(sizeof(*info) + len, GFP_KERNEL);
+	if (!info)
+		return NULL;
+	atomic_set(&info->users, 1);
+	cp = info->id;
+	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';
+		}
+	}
+	*cp = '\0';
+	return info;
+}
+
+/**
+ * kpr_correct_word - Validate a string.
+ *
+ * @string: The string to check.
+ *
+ * Check whether the given string follows the naming rules.
+ * Returns true if @string follows the naming rules, false otherwise.
+ */
+static bool kpr_correct_word(const char *string)
+{
+	if (!*string)
+		return false;
+	while (1) {
+		unsigned char c = *string++;
+		if (!c)
+			return true;
+		if (c == '\\') {
+			c = *string++;
+			switch (c) {
+			case '\\':  /* "\\" */
+				continue;
+			case '0':   /* "\ooo" */
+			case '1':
+			case '2':
+			case '3':
+				{
+					unsigned char d;
+					unsigned char e;
+					c -= '0';
+					d = *string++ - '0';
+					if (d > 7)
+						break;
+					e = *string++ - '0';
+					if (e > 7)
+						break;
+					c = (c << 6) + (d << 3) + (e);
+					if (c <= ' ' || c >= 127)
+						continue;
+				}
+			}
+			return false;
+		} else if (c <= ' ' || c >= 127) {
+			return false;
+		}
+	}
+	return true;
+}
+
+/**
+ * kpr_bprm_set_creds - Target for security_bprm_set_creds().
+ *
+ * @bprm: Pointer to "struct linux_binprm".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int kpr_bprm_set_creds(struct linux_binprm *bprm)
+{
+	const int rc = cap_bprm_set_creds(bprm);
+
+	if (rc)
+		return rc;
+	if (!bprm->cred_prepared) {
+		struct task_name_info *info = kpr_make_info(bprm->filename);
+
+		if (!info)
+			return -ENOMEM;
+		kpr_cred_free(bprm->cred);
+		bprm->cred->security = info;
+	}
+	return 0;
+}
+
+
+/**
+ * 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)
+{
+	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;
+	{
+		static atomic_t counter = ATOMIC_INIT(0);
+		static u64 last_time;
+		u64 now_time;
+		struct reserved_port_entry *ptr;
+		bool reserved = false;
+		const char *id = ((struct task_name_info *)
+				  current_security())->id;
+
+		rcu_read_lock();
+		list_for_each_entry_rcu(ptr, &reserved_port_list, list) {
+			if (port != ptr->port)
+				continue;
+			if (strcmp(id, ptr->id)) {
+				reserved = true;
+				continue;
+			}
+			reserved = false;
+			break;
+		}
+		rcu_read_unlock();
+		if (!reserved)
+			return 0;
+		/*
+		 * Notify up to once per a minute, in case of rejection by
+		 * inappropriate configuration.
+		 */
+		now_time = jiffies_64;
+		atomic_inc(&counter);
+		if (!last_time || now_time > last_time + 60 * HZ) {
+			last_time = now_time;
+			pr_info("KPortReserve:(#%u): Rejected bind(%d) by %s (pid=%d)\n",
+				atomic_read(&counter), port, id, current->pid);
+		}
+		return -EADDRINUSE;
+	}
+}
+
+/**
+ * kpr_entry_read - Read current configuration.
+ *
+ * @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_entry_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->id);
+			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.
+ * @id:   Identifier. 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 *id)
+{
+	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 (id && strcmp(id, ptr->id))
+			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;
+	int len;
+	if (sscanf(data, "%u", &port) == 1 && port < 65536) {
+		const char *cp = strchr(data, ' ');
+		if (!cp++ || !kpr_correct_word(cp))
+			return -EINVAL;
+		if (kpr_find_entry(port, cp))
+			return 0;
+		len = strlen(cp) + 1;
+		ptr = kmalloc(sizeof(*ptr) + len, GFP_KERNEL);
+		if (!ptr)
+			return -ENOMEM;
+		ptr->port = (u16) port;
+		strcpy(ptr->id, cp);
+		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++ || !kpr_correct_word(cp))
+			return -EINVAL;
+		ptr = kpr_find_entry(port, cp);
+		if (!ptr)
+			return 0;
+		list_del_rcu(&ptr->list);
+		synchronize_rcu();
+		kfree(ptr);
+		if (!kpr_find_entry(port, NULL))
+			clear_bit(ptr->port, reserved_port_map);
+	} else {
+		return -EINVAL;
+	}
+	return 0;
+}
+
+/**
+ * kpr_entry_write - Update current configuration.
+ *
+ * @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_entry_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",
+	.cred_prepare     = kpr_cred_prepare,
+	.cred_alloc_blank = kpr_cred_alloc_blank,
+	.cred_transfer    = kpr_cred_transfer,
+	.cred_free        = kpr_cred_free,
+	.bprm_set_creds   = kpr_bprm_set_creds,
+	.socket_bind      = kpr_socket_bind,
+};
+
+static bool kpr_registered;
+
+/**
+ * kpr_register - Register this module.
+ *
+ * Returns 0.
+ */
+static int __init kpr_register(void)
+{
+	struct cred *cred = (struct cred *) current_cred();
+	struct task_name_info *info;
+	const char kernel_name[] = "<kernel>";
+
+	if (!security_module_enable(&kpr_ops))
+		return 0;
+	info = kmalloc(sizeof(*info) + sizeof(kernel_name), GFP_KERNEL);
+	if (!info)
+		goto out;
+	atomic_set(&info->users, 1);
+	strcpy(info->id, kernel_name);
+	cred->security = info;
+	if (register_security(&kpr_ops))
+		goto out;
+	kpr_registered = true;
+	pr_info("KPortReserve initialized\n");
+	return 0;
+out:
+	panic("Failure registering kportreserve");
+}
+security_initcall(kpr_register);
+
+/* Operations for /sys/kernel/security/kportreserve/entry interface. */
+static const struct file_operations kpr_entry_operations = {
+	.write = kpr_entry_write,
+	.read  = kpr_entry_read,
+};
+
+/**
+ * kpr_init - Initialize this module.
+ *
+ * Returns 0.
+ */
+static int __init kpr_init(void)
+{
+	if (kpr_registered) {
+		struct dentry *kpr_dir = securityfs_create_dir("kportreserve",
+							       NULL);
+		if (!kpr_dir ||
+		    !securityfs_create_file("entry", 0644, kpr_dir, NULL,
+					    &kpr_entry_operations))
+			panic("Failure registering kportreserve");
+	}
+	return 0;
+}
+fs_initcall(kpr_init);
-- 
1.7.1

^ permalink raw reply related	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2013-08-21 12:39 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
     [not found] <201308031715.FCH73469.OVOHQStOFMLJFF@I-love.SAKURA.ne.jp>
2013-08-07 14:31 ` [kernel-hardening] Re: [RFC] KPortReserve : kernel version of portreserve utility Vasily Kulikov
2013-08-07 16:00   ` Casey Schaufler
2013-08-08 10:50   ` Tetsuo Handa
2013-08-11  2:50     ` Tetsuo Handa
2013-08-21 12:39       ` [PATCH v3] " Tetsuo Handa

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.