Commit 9ac684fc authored by James Morris's avatar James Morris

Merge branch 'next' into for-linus

parents 3fa8749e 81990fbd
......@@ -283,6 +283,7 @@ X!Earch/x86/kernel/mca_32.c
<chapter id="security">
<title>Security Framework</title>
!Isecurity/security.c
!Esecurity/inode.c
</chapter>
<chapter id="audit">
......
If you want to use SELinux, chances are you will want
to use the distro-provided policies, or install the
latest reference policy release from
http://oss.tresys.com/projects/refpolicy
However, if you want to install a dummy policy for
testing, you can do using 'mdp' provided under
scripts/selinux. Note that this requires the selinux
userspace to be installed - in particular you will
need checkpolicy to compile a kernel, and setfiles and
fixfiles to label the filesystem.
1. Compile the kernel with selinux enabled.
2. Type 'make' to compile mdp.
3. Make sure that you are not running with
SELinux enabled and a real policy. If
you are, reboot with selinux disabled
before continuing.
4. Run install_policy.sh:
cd scripts/selinux
sh install_policy.sh
Step 4 will create a new dummy policy valid for your
kernel, with a single selinux user, role, and type.
It will compile the policy, will set your SELINUXTYPE to
dummy in /etc/selinux/config, install the compiled policy
as 'dummy', and relabel your filesystem.
......@@ -3650,7 +3650,8 @@ P: Eric Paris
M: eparis@parisplace.org
L: linux-kernel@vger.kernel.org (kernel issues)
L: selinux@tycho.nsa.gov (subscribers-only, general discussion)
W: http://www.nsa.gov/selinux
W: http://selinuxproject.org
T: git kernel.org:pub/scm/linux/kernel/git/jmorris/security-testing-2.6.git
S: Supported
SENSABLE PHANTOM
......
......@@ -6,6 +6,7 @@ menuconfig TCG_TPM
tristate "TPM Hardware Support"
depends on HAS_IOMEM
depends on EXPERIMENTAL
select SECURITYFS
---help---
If you have a TPM security chip in your system, which
implements the Trusted Computing Group's specification,
......
......@@ -1560,11 +1560,6 @@ struct security_operations {
extern int security_init(void);
extern int security_module_enable(struct security_operations *ops);
extern int register_security(struct security_operations *ops);
extern struct dentry *securityfs_create_file(const char *name, mode_t mode,
struct dentry *parent, void *data,
const struct file_operations *fops);
extern struct dentry *securityfs_create_dir(const char *name, struct dentry *parent);
extern void securityfs_remove(struct dentry *dentry);
/* Security operations */
int security_ptrace_may_access(struct task_struct *child, unsigned int mode);
......@@ -2424,25 +2419,6 @@ static inline int security_netlink_recv(struct sk_buff *skb, int cap)
return cap_netlink_recv(skb, cap);
}
static inline struct dentry *securityfs_create_dir(const char *name,
struct dentry *parent)
{
return ERR_PTR(-ENODEV);
}
static inline struct dentry *securityfs_create_file(const char *name,
mode_t mode,
struct dentry *parent,
void *data,
const struct file_operations *fops)
{
return ERR_PTR(-ENODEV);
}
static inline void securityfs_remove(struct dentry *dentry)
{
}
static inline int security_secid_to_secctx(u32 secid, char **secdata, u32 *seclen)
{
return -EOPNOTSUPP;
......@@ -2806,5 +2782,35 @@ static inline void security_audit_rule_free(void *lsmrule)
#endif /* CONFIG_SECURITY */
#endif /* CONFIG_AUDIT */
#ifdef CONFIG_SECURITYFS
extern struct dentry *securityfs_create_file(const char *name, mode_t mode,
struct dentry *parent, void *data,
const struct file_operations *fops);
extern struct dentry *securityfs_create_dir(const char *name, struct dentry *parent);
extern void securityfs_remove(struct dentry *dentry);
#else /* CONFIG_SECURITYFS */
static inline struct dentry *securityfs_create_dir(const char *name,
struct dentry *parent)
{
return ERR_PTR(-ENODEV);
}
static inline struct dentry *securityfs_create_file(const char *name,
mode_t mode,
struct dentry *parent,
void *data,
const struct file_operations *fops)
{
return ERR_PTR(-ENODEV);
}
static inline void securityfs_remove(struct dentry *dentry)
{}
#endif
#endif /* ! __LINUX_SECURITY_H */
......@@ -20,6 +20,7 @@ hostprogs-y += unifdef
subdir-$(CONFIG_MODVERSIONS) += genksyms
subdir-y += mod
subdir-$(CONFIG_SECURITY_SELINUX) += selinux
# Let clean descend into subdirs
subdir- += basic kconfig package
subdir- += basic kconfig package selinux
subdir-y := mdp
subdir- += mdp
Please see Documentation/SELinux.txt for information on
installing a dummy SELinux policy.
#!/bin/sh
if [ `id -u` -ne 0 ]; then
echo "$0: must be root to install the selinux policy"
exit 1
fi
SF=`which setfiles`
if [ $? -eq 1 ]; then
if [ -f /sbin/setfiles ]; then
SF="/usr/setfiles"
else
echo "no selinux tools installed: setfiles"
exit 1
fi
fi
cd mdp
CP=`which checkpolicy`
VERS=`$CP -V | awk '{print $1}'`
./mdp policy.conf file_contexts
$CP -o policy.$VERS policy.conf
mkdir -p /etc/selinux/dummy/policy
mkdir -p /etc/selinux/dummy/contexts/files
cp file_contexts /etc/selinux/dummy/contexts/files
cp dbus_contexts /etc/selinux/dummy/contexts
cp policy.$VERS /etc/selinux/dummy/policy
FC_FILE=/etc/selinux/dummy/contexts/files/file_contexts
if [ ! -d /etc/selinux ]; then
mkdir -p /etc/selinux
fi
if [ ! -f /etc/selinux/config ]; then
cat > /etc/selinux/config << EOF
SELINUX=enforcing
SELINUXTYPE=dummy
EOF
else
TYPE=`cat /etc/selinux/config | grep "^SELINUXTYPE" | tail -1 | awk -F= '{ print $2 '}`
if [ "eq$TYPE" != "eqdummy" ]; then
selinuxenabled
if [ $? -eq 0 ]; then
echo "SELinux already enabled with a non-dummy policy."
echo "Exiting. Please install policy by hand if that"
echo "is what you REALLY want."
exit 1
fi
mv /etc/selinux/config /etc/selinux/config.mdpbak
grep -v "^SELINUXTYPE" /etc/selinux/config.mdpbak >> /etc/selinux/config
echo "SELINUXTYPE=dummy" >> /etc/selinux/config
fi
fi
cd /etc/selinux/dummy/contexts/files
$SF file_contexts /
mounts=`cat /proc/$$/mounts | egrep "ext2|ext3|xfs|jfs|ext4|ext4dev|gfs2" | awk '{ print $2 '}`
$SF file_contexts $mounts
dodev=`cat /proc/$$/mounts | grep "/dev "`
if [ "eq$dodev" != "eq" ]; then
mount --move /dev /mnt
$SF file_contexts /dev
mount --move /mnt /dev
fi
hostprogs-y := mdp
HOST_EXTRACFLAGS += -Isecurity/selinux/include
always := $(hostprogs-y)
clean-files := $(hostprogs-y) policy.* file_contexts
<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
<selinux>
</selinux>
</busconfig>
/*
*
* mdp - make dummy policy
*
* When pointed at a kernel tree, builds a dummy policy for that kernel
* with exactly one type with full rights to itself.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* Copyright (C) IBM Corporation, 2006
*
* Authors: Serge E. Hallyn <serue@us.ibm.com>
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include "flask.h"
void usage(char *name)
{
printf("usage: %s [-m] policy_file context_file\n", name);
exit(1);
}
void find_common_name(char *cname, char *dest, int len)
{
char *start, *end;
start = strchr(cname, '_')+1;
end = strchr(start, '_');
if (!start || !end || start-cname > len || end-start > len) {
printf("Error with commons defines\n");
exit(1);
}
strncpy(dest, start, end-start);
dest[end-start] = '\0';
}
#define S_(x) x,
static char *classlist[] = {
#include "class_to_string.h"
NULL
};
#undef S_
#include "initial_sid_to_string.h"
#define TB_(x) char *x[] = {
#define TE_(x) NULL };
#define S_(x) x,
#include "common_perm_to_string.h"
#undef TB_
#undef TE_
#undef S_
struct common {
char *cname;
char **perms;
};
struct common common[] = {
#define TB_(x) { #x, x },
#define S_(x)
#define TE_(x)
#include "common_perm_to_string.h"
#undef TB_
#undef TE_
#undef S_
};
#define S_(x, y, z) {x, #y},
struct av_inherit {
int class;
char *common;
};
struct av_inherit av_inherit[] = {
#include "av_inherit.h"
};
#undef S_
#include "av_permissions.h"
#define S_(x, y, z) {x, y, z},
struct av_perms {
int class;
int perm_i;
char *perm_s;
};
struct av_perms av_perms[] = {
#include "av_perm_to_string.h"
};
#undef S_
int main(int argc, char *argv[])
{
int i, j, mls = 0;
char **arg, *polout, *ctxout;
int classlist_len, initial_sid_to_string_len;
FILE *fout;
if (argc < 3)
usage(argv[0]);
arg = argv+1;
if (argc==4 && strcmp(argv[1], "-m") == 0) {
mls = 1;
arg++;
}
polout = *arg++;
ctxout = *arg;
fout = fopen(polout, "w");
if (!fout) {
printf("Could not open %s for writing\n", polout);
usage(argv[0]);
}
classlist_len = sizeof(classlist) / sizeof(char *);
/* print out the classes */
for (i=1; i < classlist_len; i++) {
if(classlist[i])
fprintf(fout, "class %s\n", classlist[i]);
else
fprintf(fout, "class user%d\n", i);
}
fprintf(fout, "\n");
initial_sid_to_string_len = sizeof(initial_sid_to_string) / sizeof (char *);
/* print out the sids */
for (i=1; i < initial_sid_to_string_len; i++)
fprintf(fout, "sid %s\n", initial_sid_to_string[i]);
fprintf(fout, "\n");
/* print out the commons */
for (i=0; i< sizeof(common)/sizeof(struct common); i++) {
char cname[101];
find_common_name(common[i].cname, cname, 100);
cname[100] = '\0';
fprintf(fout, "common %s\n{\n", cname);
for (j=0; common[i].perms[j]; j++)
fprintf(fout, "\t%s\n", common[i].perms[j]);
fprintf(fout, "}\n\n");
}
fprintf(fout, "\n");
/* print out the class permissions */
for (i=1; i < classlist_len; i++) {
if (classlist[i]) {
int firstperm = -1, numperms = 0;
fprintf(fout, "class %s\n", classlist[i]);
/* does it inherit from a common? */
for (j=0; j < sizeof(av_inherit)/sizeof(struct av_inherit); j++)
if (av_inherit[j].class == i)
fprintf(fout, "inherits %s\n", av_inherit[j].common);
for (j=0; j < sizeof(av_perms)/sizeof(struct av_perms); j++) {
if (av_perms[j].class == i) {
if (firstperm == -1)
firstperm = j;
numperms++;
}
}
if (!numperms) {
fprintf(fout, "\n");
continue;
}
fprintf(fout, "{\n");
/* print out the av_perms */
for (j=0; j < numperms; j++) {
fprintf(fout, "\t%s\n", av_perms[firstperm+j].perm_s);
}
fprintf(fout, "}\n\n");
}
}
fprintf(fout, "\n");
/* NOW PRINT OUT MLS STUFF */
if (mls) {
printf("MLS not yet implemented\n");
exit(1);
}
/* types, roles, and allows */
fprintf(fout, "type base_t;\n");
fprintf(fout, "role base_r types { base_t };\n");
for (i=1; i < classlist_len; i++) {
if (classlist[i])
fprintf(fout, "allow base_t base_t:%s *;\n", classlist[i]);
else
fprintf(fout, "allow base_t base_t:user%d *;\n", i);
}
fprintf(fout, "user user_u roles { base_r };\n");
fprintf(fout, "\n");
/* default sids */
for (i=1; i < initial_sid_to_string_len; i++)
fprintf(fout, "sid %s user_u:base_r:base_t\n", initial_sid_to_string[i]);
fprintf(fout, "\n");
fprintf(fout, "fs_use_xattr ext2 user_u:base_r:base_t;\n");
fprintf(fout, "fs_use_xattr ext3 user_u:base_r:base_t;\n");
fprintf(fout, "fs_use_xattr jfs user_u:base_r:base_t;\n");
fprintf(fout, "fs_use_xattr xfs user_u:base_r:base_t;\n");
fprintf(fout, "fs_use_xattr reiserfs user_u:base_r:base_t;\n");
fprintf(fout, "fs_use_task pipefs user_u:base_r:base_t;\n");
fprintf(fout, "fs_use_task sockfs user_u:base_r:base_t;\n");
fprintf(fout, "fs_use_trans devpts user_u:base_r:base_t;\n");
fprintf(fout, "fs_use_trans tmpfs user_u:base_r:base_t;\n");
fprintf(fout, "fs_use_trans shm user_u:base_r:base_t;\n");
fprintf(fout, "genfscon proc / user_u:base_r:base_t\n");
fclose(fout);
fout = fopen(ctxout, "w");
if (!fout) {
printf("Wrote policy, but cannot open %s for writing\n", ctxout);
usage(argv[0]);
}
fprintf(fout, "/ user_u:base_r:base_t\n");
fprintf(fout, "/.* user_u:base_r:base_t\n");
fclose(fout);
return 0;
}
......@@ -51,6 +51,14 @@ config SECURITY
If you are unsure how to answer this question, answer N.
config SECURITYFS
bool "Enable the securityfs filesystem"
help
This will build the securityfs filesystem. It is currently used by
the TPM bios character driver. It is not used by SELinux or SMACK.
If you are unsure how to answer this question, answer N.
config SECURITY_NETWORK
bool "Socket and Networking Security Hooks"
depends on SECURITY
......
......@@ -10,7 +10,8 @@ subdir-$(CONFIG_SECURITY_SMACK) += smack
obj-y += commoncap.o
# Object file lists
obj-$(CONFIG_SECURITY) += security.o capability.o inode.o
obj-$(CONFIG_SECURITY) += security.o capability.o
obj-$(CONFIG_SECURITYFS) += inode.o
# Must precede capability.o in order to stack properly.
obj-$(CONFIG_SECURITY_SELINUX) += selinux/built-in.o
obj-$(CONFIG_SECURITY_SMACK) += smack/built-in.o
......
......@@ -541,7 +541,7 @@ int cap_task_post_setuid (uid_t old_ruid, uid_t old_euid, uid_t old_suid,
* yet with increased caps.
* So we check for increased caps on the target process.
*/
static inline int cap_safe_nice(struct task_struct *p)
static int cap_safe_nice(struct task_struct *p)
{
if (!cap_issubset(p->cap_permitted, current->cap_permitted) &&
!capable(CAP_SYS_NICE))
......
......@@ -190,7 +190,7 @@ static int create_by_name(const char *name, mode_t mode,
* @name: a pointer to a string containing the name of the file to create.
* @mode: the permission that the file should have
* @parent: a pointer to the parent dentry for this file. This should be a
* directory dentry if set. If this paramater is NULL, then the
* directory dentry if set. If this parameter is %NULL, then the
* file will be created in the root of the securityfs filesystem.
* @data: a pointer to something that the caller will want to get to later
* on. The inode.i_private pointer will point to this value on
......@@ -199,18 +199,18 @@ static int create_by_name(const char *name, mode_t mode,
* this file.
*
* This is the basic "create a file" function for securityfs. It allows for a
* wide range of flexibility in createing a file, or a directory (if you
* wide range of flexibility in creating a file, or a directory (if you
* want to create a directory, the securityfs_create_dir() function is
* recommended to be used instead.)
* recommended to be used instead).
*
* This function will return a pointer to a dentry if it succeeds. This
* This function returns a pointer to a dentry if it succeeds. This
* pointer must be passed to the securityfs_remove() function when the file is
* to be removed (no automatic cleanup happens if your module is unloaded,
* you are responsible here.) If an error occurs, NULL will be returned.
* you are responsible here). If an error occurs, %NULL is returned.
*
* If securityfs is not enabled in the kernel, the value -ENODEV will be
* If securityfs is not enabled in the kernel, the value %-ENODEV is
* returned. It is not wise to check for this value, but rather, check for
* NULL or !NULL instead as to eliminate the need for #ifdef in the calling
* %NULL or !%NULL instead as to eliminate the need for #ifdef in the calling
* code.
*/
struct dentry *securityfs_create_file(const char *name, mode_t mode,
......@@ -252,19 +252,19 @@ EXPORT_SYMBOL_GPL(securityfs_create_file);
* @name: a pointer to a string containing the name of the directory to
* create.
* @parent: a pointer to the parent dentry for this file. This should be a
* directory dentry if set. If this paramater is NULL, then the
* directory dentry if set. If this parameter is %NULL, then the
* directory will be created in the root of the securityfs filesystem.
*
* This function creates a directory in securityfs with the given name.
* This function creates a directory in securityfs with the given @name.
*
* This function will return a pointer to a dentry if it succeeds. This
* This function returns a pointer to a dentry if it succeeds. This
* pointer must be passed to the securityfs_remove() function when the file is
* to be removed (no automatic cleanup happens if your module is unloaded,
* you are responsible here.) If an error occurs, NULL will be returned.
* you are responsible here). If an error occurs, %NULL will be returned.
*
* If securityfs is not enabled in the kernel, the value -ENODEV will be
* If securityfs is not enabled in the kernel, the value %-ENODEV is
* returned. It is not wise to check for this value, but rather, check for
* NULL or !NULL instead as to eliminate the need for #ifdef in the calling
* %NULL or !%NULL instead as to eliminate the need for #ifdef in the calling
* code.
*/
struct dentry *securityfs_create_dir(const char *name, struct dentry *parent)
......@@ -278,16 +278,15 @@ EXPORT_SYMBOL_GPL(securityfs_create_dir);
/**
* securityfs_remove - removes a file or directory from the securityfs filesystem
*
* @dentry: a pointer to a the dentry of the file or directory to be
* removed.
* @dentry: a pointer to a the dentry of the file or directory to be removed.
*
* This function removes a file or directory in securityfs that was previously
* created with a call to another securityfs function (like
* securityfs_create_file() or variants thereof.)
*
* This function is required to be called in order for the file to be
* removed, no automatic cleanup of files will happen when a module is
* removed, you are responsible here.
* removed. No automatic cleanup of files will happen when a module is
* removed; you are responsible here.
*/
void securityfs_remove(struct dentry *dentry)
{
......
......@@ -82,8 +82,8 @@ __setup("security=", choose_lsm);
*
* Return true if:
* -The passed LSM is the one chosen by user at boot time,
* -or user didsn't specify a specific LSM and we're the first to ask
* for registeration permissoin,
* -or user didn't specify a specific LSM and we're the first to ask
* for registration permission,
* -or the passed LSM is currently loaded.
* Otherwise, return false.
*/
......@@ -101,13 +101,13 @@ int __init security_module_enable(struct security_operations *ops)
* register_security - registers a security framework with the kernel
* @ops: a pointer to the struct security_options that is to be registered
*
* This function is to allow a security module to register itself with the
* This function allows a security module to register itself with the
* kernel security subsystem. Some rudimentary checking is done on the @ops
* value passed to this function. You'll need to check first if your LSM
* is allowed to register its @ops by calling security_module_enable(@ops).
*
* If there is already a security module registered with the kernel,
* an error will be returned. Otherwise 0 is returned on success.
* an error will be returned. Otherwise %0 is returned on success.
*/
int register_security(struct security_operations *ops)
{
......
......@@ -6,9 +6,6 @@ config SECURITY_SELINUX
help
This selects NSA Security-Enhanced Linux (SELinux).
You will also need a policy configuration and a labeled filesystem.
You can obtain the policy compiler (checkpolicy), the utility for
labeling filesystems (setfiles), and an example policy configuration
from <http://www.nsa.gov/selinux/>.
If you are unsure how to answer this question, answer N.
config SECURITY_SELINUX_BOOTPARAM
......
......@@ -136,7 +136,7 @@ static inline int avc_hash(u32 ssid, u32 tsid, u16 tclass)
* @tclass: target security class
* @av: access vector
*/
static void avc_dump_av(struct audit_buffer *ab, u16 tclass, u32 av)
void avc_dump_av(struct audit_buffer *ab, u16 tclass, u32 av)
{
const char **common_pts = NULL;
u32 common_base = 0;
......
......@@ -957,7 +957,8 @@ out_err:
return rc;
}
void selinux_write_opts(struct seq_file *m, struct security_mnt_opts *opts)
static void selinux_write_opts(struct seq_file *m,
struct security_mnt_opts *opts)
{
int i;
char *prefix;
......@@ -1290,7 +1291,7 @@ static int inode_doinit_with_dentry(struct inode *inode, struct dentry *opt_dent
/* Default to the fs superblock SID. */
isec->sid = sbsec->sid;
if (sbsec->proc) {
if (sbsec->proc && !S_ISLNK(inode->i_mode)) {
struct proc_inode *proci = PROC_I(inode);
if (proci->pde) {
isec->sclass = inode_mode_to_security_class(inode->i_mode);
......@@ -3548,38 +3549,44 @@ out:
#endif /* IPV6 */
static int selinux_parse_skb(struct sk_buff *skb, struct avc_audit_data *ad,
char **addrp, int src, u8 *proto)
char **_addrp, int src, u8 *proto)
{
int ret = 0;
char *addrp;
int ret;
switch (ad->u.net.family) {
case PF_INET:
ret = selinux_parse_skb_ipv4(skb, ad, proto);
if (ret || !addrp)
break;
*addrp = (char *)(src ? &ad->u.net.v4info.saddr :
if (ret)
goto parse_error;
addrp = (char *)(src ? &ad->u.net.v4info.saddr :
&ad->u.net.v4info.daddr);
break;
goto okay;
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
case PF_INET6:
ret = selinux_parse_skb_ipv6(skb, ad, proto);
if (ret || !addrp)
break;
*addrp = (char *)(src ? &ad->u.net.v6info.saddr :
if (ret)
goto parse_error;
addrp = (char *)(src ? &ad->u.net.v6info.saddr :
&ad->u.net.v6info.daddr);
break;
goto okay;
#endif /* IPV6 */
default:
break;
addrp = NULL;
goto okay;
}
if (unlikely(ret))
parse_error:
printk(KERN_WARNING
"SELinux: failure in selinux_parse_skb(),"
" unable to parse packet\n");
return ret;
okay:
if (_addrp)
*_addrp = addrp;
return 0;
}
/**
......@@ -5219,8 +5226,12 @@ static int selinux_setprocattr(struct task_struct *p,
if (sid == 0)
return -EINVAL;
/* Only allow single threaded processes to change context */
/*
* SELinux allows to change context in the following case only.
* - Single threaded processes.
* - Multi threaded processes intend to change its context into
* more restricted domain (defined by TYPEBOUNDS statement).
*/
if (atomic_read(&p->mm->mm_users) != 1) {
struct task_struct *g, *t;
struct mm_struct *mm = p->mm;
......@@ -5228,11 +5239,16 @@ static int selinux_setprocattr(struct task_struct *p,
do_each_thread(g, t) {
if (t->mm == mm && t != p) {
read_unlock(&tasklist_lock);
return -EPERM;
error = security_bounded_transition(tsec->sid, sid);
if (!error)
goto boundary_ok;
return error;
}
} while_each_thread(g, t);
read_unlock(&tasklist_lock);
}
boundary_ok:
/* Check permissions for the transition. */
error = avc_has_perm(tsec->sid, sid, SECCLASS_PROCESS,
......
......@@ -12,6 +12,7 @@
#include <linux/kdev_t.h>
#include <linux/spinlock.h>
#include <linux/init.h>
#include <linux/audit.h>
#include <linux/in6.h>
#include <linux/path.h>
#include <asm/system.h>
......@@ -126,6 +127,9 @@ int avc_add_callback(int (*callback)(u32 event, u32 ssid, u32 tsid,
u32 events, u32 ssid, u32 tsid,
u16 tclass, u32 perms);
/* Shows permission in human readable form */
void avc_dump_av(struct audit_buffer *ab, u16 tclass, u32 av);
/* Exported to selinuxfs */
int avc_get_hash_stats(char *page);
extern unsigned int avc_cache_threshold;
......
......@@ -27,13 +27,14 @@
#define POLICYDB_VERSION_RANGETRANS 21
#define POLICYDB_VERSION_POLCAP 22
#define POLICYDB_VERSION_PERMISSIVE 23
#define POLICYDB_VERSION_BOUNDARY 24
/* Range of policy versions we understand*/
#define POLICYDB_VERSION_MIN POLICYDB_VERSION_BASE
#ifdef CONFIG_SECURITY_SELINUX_POLICYDB_VERSION_MAX
#define POLICYDB_VERSION_MAX CONFIG_SECURITY_SELINUX_POLICYDB_VERSION_MAX_VALUE
#else
#define POLICYDB_VERSION_MAX POLICYDB_VERSION_PERMISSIVE
#define POLICYDB_VERSION_MAX POLICYDB_VERSION_BOUNDARY
#endif
#define CONTEXT_MNT 0x01
......@@ -62,6 +63,16 @@ enum {
extern int selinux_policycap_netpeer;
extern int selinux_policycap_openperm;
/*
* type_datum properties
* available at the kernel policy version >= POLICYDB_VERSION_BOUNDARY
*/
#define TYPEDATUM_PROPERTY_PRIMARY 0x0001
#define TYPEDATUM_PROPERTY_ATTRIBUTE 0x0002
/* limitation of boundary depth */
#define POLICYDB_BOUNDS_MAXDEPTH 4
int security_load_policy(void *data, size_t len);
int security_policycap_supported(unsigned int req_cap);
......@@ -117,6 +128,8 @@ int security_node_sid(u16 domain, void *addr, u32 addrlen,
int security_validate_transition(u32 oldsid, u32 newsid, u32 tasksid,
u16 tclass);
int security_bounded_transition(u32 oldsid, u32 newsid);
int security_sid_mls_copy(u32 sid, u32 mls_sid, u32 *new_sid);
int security_net_peersid_resolve(u32 nlbl_sid, u32 nlbl_type,
......
......@@ -98,7 +98,7 @@ struct avtab_node *
avtab_insert_nonunique(struct avtab *h, struct avtab_key *key, struct avtab_datum *datum)
{
int hvalue;
struct avtab_node *prev, *cur, *newnode;
struct avtab_node *prev, *cur;
u16 specified = key->specified & ~(AVTAB_ENABLED|AVTAB_ENABLED_OLD);
if (!h || !h->htable)
......@@ -122,9 +122,7 @@ avtab_insert_nonunique(struct avtab *h, struct avtab_key *key, struct avtab_datu
key->target_class < cur->key.target_class)
break;
}
newnode = avtab_insert_node(h, hvalue, prev, cur, key, datum);
return newnode;
return avtab_insert_node(h, hvalue, prev, cur, key, datum);
}
struct avtab_datum *avtab_search(struct avtab *h, struct avtab_key *key)
......@@ -231,7 +229,7 @@ void avtab_destroy(struct avtab *h)
for (i = 0; i < h->nslot; i++) {
cur = h->htable[i];
while (cur != NULL) {
while (cur) {
temp = cur;
cur = cur->next;
kmem_cache_free(avtab_node_cachep, temp);
......
......@@ -29,7 +29,7 @@ static int cond_evaluate_expr(struct policydb *p, struct cond_expr *expr)
int s[COND_EXPR_MAXDEPTH];
int sp = -1;
for (cur = expr; cur != NULL; cur = cur->next) {
for (cur = expr; cur; cur = cur->next) {
switch (cur->expr_type) {
case COND_BOOL:
if (sp == (COND_EXPR_MAXDEPTH - 1))
......@@ -97,14 +97,14 @@ int evaluate_cond_node(struct policydb *p, struct cond_node *node)
if (new_state == -1)
printk(KERN_ERR "SELinux: expression result was undefined - disabling all rules.\n");
/* turn the rules on or off */
for (cur = node->true_list; cur != NULL; cur = cur->next) {
for (cur = node->true_list; cur; cur = cur->next) {
if (new_state <= 0)
cur->node->key.specified &= ~AVTAB_ENABLED;
else
cur->node->key.specified |= AVTAB_ENABLED;
}
for (cur = node->false_list; cur != NULL; cur = cur->next) {
for (cur = node->false_list; cur; cur = cur->next) {
/* -1 or 1 */
if (new_state)
cur->node->key.specified &= ~AVTAB_ENABLED;
......@@ -128,7 +128,7 @@ int cond_policydb_init(struct policydb *p)
static void cond_av_list_destroy(struct cond_av_list *list)
{
struct cond_av_list *cur, *next;
for (cur = list; cur != NULL; cur = next) {
for (cur = list; cur; cur = next) {
next = cur->next;
/* the avtab_ptr_t node is destroy by the avtab */
kfree(cur);
......@@ -139,7 +139,7 @@ static void cond_node_destroy(struct cond_node *node)
{
struct cond_expr *cur_expr, *next_expr;
for (cur_expr = node->expr; cur_expr != NULL; cur_expr = next_expr) {
for (cur_expr = node->expr; cur_expr; cur_expr = next_expr) {
next_expr = cur_expr->next;
kfree(cur_expr);
}
......@@ -155,7 +155,7 @@ static void cond_list_destroy(struct cond_node *list)
if (list == NULL)
return;
for (cur = list; cur != NULL; cur = next) {
for (cur = list; cur; cur = next) {
next = cur->next;
cond_node_destroy(cur);
}
......@@ -239,7 +239,7 @@ int cond_read_bool(struct policydb *p, struct hashtab *h, void *fp)
rc = next_entry(key, fp, len);
if (rc < 0)
goto err;
key[len] = 0;
key[len] = '\0';
if (hashtab_insert(h, key, booldatum))
goto err;
......@@ -291,7 +291,7 @@ static int cond_insertf(struct avtab *a, struct avtab_key *k, struct avtab_datum
goto err;
}
found = 0;
for (cur = other; cur != NULL; cur = cur->next) {
for (cur = other; cur; cur = cur->next) {
if (cur->node == node_ptr) {
found = 1;
break;
......@@ -485,7 +485,7 @@ void cond_compute_av(struct avtab *ctab, struct avtab_key *key, struct av_decisi
if (!ctab || !key || !avd)
return;
for (node = avtab_search_node(ctab, key); node != NULL;
for (node = avtab_search_node(ctab, key); node;
node = avtab_search_node_next(node, key->specified)) {
if ((u16)(AVTAB_ALLOWED|AVTAB_ENABLED) ==
(node->key.specified & (AVTAB_ALLOWED|AVTAB_ENABLED)))
......
......@@ -28,7 +28,7 @@ struct cond_expr {
#define COND_XOR 5 /* bool ^ bool */
#define COND_EQ 6 /* bool == bool */
#define COND_NEQ 7 /* bool != bool */
#define COND_LAST 8
#define COND_LAST COND_NEQ
__u32 expr_type;
__u32 bool;
struct cond_expr *next;
......
......@@ -109,7 +109,7 @@ int ebitmap_netlbl_export(struct ebitmap *ebmap,
*catmap = c_iter;
c_iter->startbit = e_iter->startbit & ~(NETLBL_CATMAP_SIZE - 1);
while (e_iter != NULL) {
while (e_iter) {
for (i = 0; i < EBITMAP_UNIT_NUMS; i++) {
unsigned int delta, e_startbit, c_endbit;
......@@ -197,7 +197,7 @@ int ebitmap_netlbl_import(struct ebitmap *ebmap,
}
}
c_iter = c_iter->next;
} while (c_iter != NULL);
} while (c_iter);
if (e_iter != NULL)
ebmap->highbit = e_iter->startbit + EBITMAP_SIZE;
else
......
......@@ -81,7 +81,7 @@ void *hashtab_search(struct hashtab *h, const void *key)
hvalue = h->hash_value(h, key);
cur = h->htable[hvalue];
while (cur != NULL && h->keycmp(h, key, cur->key) > 0)
while (cur && h->keycmp(h, key, cur->key) > 0)
cur = cur->next;
if (cur == NULL || (h->keycmp(h, key, cur->key) != 0))
......@@ -100,7 +100,7 @@ void hashtab_destroy(struct hashtab *h)
for (i = 0; i < h->size; i++) {
cur = h->htable[i];
while (cur != NULL) {
while (cur) {
temp = cur;
cur = cur->next;
kfree(temp);
......@@ -127,7 +127,7 @@ int hashtab_map(struct hashtab *h,
for (i = 0; i < h->size; i++) {
cur = h->htable[i];
while (cur != NULL) {
while (cur) {
ret = apply(cur->key, cur->datum, args);
if (ret)
return ret;
......
......@@ -283,8 +283,8 @@ int mls_context_to_sid(struct policydb *pol,
p++;
delim = *p;
if (delim != 0)
*p++ = 0;
if (delim != '\0')
*p++ = '\0';
for (l = 0; l < 2; l++) {
levdatum = hashtab_search(pol->p_levels.table, scontextp);
......@@ -302,14 +302,14 @@ int mls_context_to_sid(struct policydb *pol,
while (*p && *p != ',' && *p != '-')
p++;
delim = *p;
if (delim != 0)
*p++ = 0;
if (delim != '\0')
*p++ = '\0';
/* Separate into range if exists */
rngptr = strchr(scontextp, '.');
if (rngptr != NULL) {
/* Remove '.' */
*rngptr++ = 0;
*rngptr++ = '\0';
}
catdatum = hashtab_search(pol->p_cats.table,
......@@ -357,8 +357,8 @@ int mls_context_to_sid(struct policydb *pol,
p++;
delim = *p;
if (delim != 0)
*p++ = 0;
if (delim != '\0')
*p++ = '\0';
} else
break;
}
......
This diff is collapsed.
......@@ -61,6 +61,7 @@ struct class_datum {
/* Role attributes */
struct role_datum {
u32 value; /* internal role value */
u32 bounds; /* boundary of role */
struct ebitmap dominates; /* set of roles dominated by this role */
struct ebitmap types; /* set of authorized types for role */
};
......@@ -81,12 +82,15 @@ struct role_allow {
/* Type attributes */
struct type_datum {
u32 value; /* internal type value */
u32 bounds; /* boundary of type */
unsigned char primary; /* primary name? */
unsigned char attribute;/* attribute ?*/
};
/* User attributes */
struct user_datum {
u32 value; /* internal user value */
u32 bounds; /* bounds of user */
struct ebitmap roles; /* set of authorized roles for user */
struct mls_range range; /* MLS range (min - max) for user */
struct mls_level dfltlevel; /* default login MLS level for user */
......@@ -209,6 +213,7 @@ struct policydb {
struct class_datum **class_val_to_struct;
struct role_datum **role_val_to_struct;
struct user_datum **user_val_to_struct;
struct type_datum **type_val_to_struct;
/* type enforcement access vectors and transitions */
struct avtab te_avtab;
......
......@@ -88,6 +88,11 @@ static u32 latest_granting;
static int context_struct_to_string(struct context *context, char **scontext,
u32 *scontext_len);
static int context_struct_compute_av(struct context *scontext,
struct context *tcontext,
u16 tclass,
u32 requested,
struct av_decision *avd);
/*
* Return the boolean value of a constraint expression
* when it is applied to the specified source and target
......@@ -273,6 +278,100 @@ mls_ops:
return s[0];
}
/*
* security_boundary_permission - drops violated permissions
* on boundary constraint.
*/
static void type_attribute_bounds_av(struct context *scontext,
struct context *tcontext,
u16 tclass,
u32 requested,
struct av_decision *avd)
{
struct context lo_scontext;
struct context lo_tcontext;
struct av_decision lo_avd;
struct type_datum *source
= policydb.type_val_to_struct[scontext->type - 1];
struct type_datum *target
= policydb.type_val_to_struct[tcontext->type - 1];
u32 masked = 0;
if (source->bounds) {
memset(&lo_avd, 0, sizeof(lo_avd));
memcpy(&lo_scontext, scontext, sizeof(lo_scontext));
lo_scontext.type = source->bounds;
context_struct_compute_av(&lo_scontext,
tcontext,
tclass,
requested,
&lo_avd);
if ((lo_avd.allowed & avd->allowed) == avd->allowed)
return; /* no masked permission */
masked = ~lo_avd.allowed & avd->allowed;
}
if (target->bounds) {
memset(&lo_avd, 0, sizeof(lo_avd));
memcpy(&lo_tcontext, tcontext, sizeof(lo_tcontext));
lo_tcontext.type = target->bounds;
context_struct_compute_av(scontext,
&lo_tcontext,
tclass,
requested,
&lo_avd);
if ((lo_avd.allowed & avd->allowed) == avd->allowed)
return; /* no masked permission */
masked = ~lo_avd.allowed & avd->allowed;
}
if (source->bounds && target->bounds) {
memset(&lo_avd, 0, sizeof(lo_avd));
/*
* lo_scontext and lo_tcontext are already
* set up.
*/
context_struct_compute_av(&lo_scontext,
&lo_tcontext,
tclass,
requested,
&lo_avd);
if ((lo_avd.allowed & avd->allowed) == avd->allowed)
return; /* no masked permission */
masked = ~lo_avd.allowed & avd->allowed;
}
if (masked) {
struct audit_buffer *ab;
char *stype_name
= policydb.p_type_val_to_name[source->value - 1];
char *ttype_name
= policydb.p_type_val_to_name[target->value - 1];
char *tclass_name
= policydb.p_class_val_to_name[tclass - 1];
/* mask violated permissions */
avd->allowed &= ~masked;
/* notice to userspace via audit message */
ab = audit_log_start(current->audit_context,
GFP_ATOMIC, AUDIT_SELINUX_ERR);
if (!ab)
return;
audit_log_format(ab, "av boundary violation: "
"source=%s target=%s tclass=%s",
stype_name, ttype_name, tclass_name);
avc_dump_av(ab, tclass, masked);
audit_log_end(ab);
}
}
/*
* Compute access vectors based on a context structure pair for
* the permissions in a particular class.
......@@ -356,7 +455,7 @@ static int context_struct_compute_av(struct context *scontext,
avkey.source_type = i + 1;
avkey.target_type = j + 1;
for (node = avtab_search_node(&policydb.te_avtab, &avkey);
node != NULL;
node;
node = avtab_search_node_next(node, avkey.specified)) {
if (node->key.specified == AVTAB_ALLOWED)
avd->allowed |= node->datum.data;
......@@ -404,6 +503,14 @@ static int context_struct_compute_av(struct context *scontext,
PROCESS__DYNTRANSITION);
}
/*
* If the given source and target types have boundary
* constraint, lazy checks have to mask any violated
* permission and notice it to userspace via audit.
*/
type_attribute_bounds_av(scontext, tcontext,
tclass, requested, avd);
return 0;
inval_class:
......@@ -549,6 +656,69 @@ out:
return rc;
}
/*
* security_bounded_transition - check whether the given
* transition is directed to bounded, or not.
* It returns 0, if @newsid is bounded by @oldsid.
* Otherwise, it returns error code.
*
* @oldsid : current security identifier
* @newsid : destinated security identifier
*/
int security_bounded_transition(u32 old_sid, u32 new_sid)
{
struct context *old_context, *new_context;
struct type_datum *type;
int index;
int rc = -EINVAL;
read_lock(&policy_rwlock);
old_context = sidtab_search(&sidtab, old_sid);
if (!old_context) {
printk(KERN_ERR "SELinux: %s: unrecognized SID %u\n",
__func__, old_sid);
goto out;
}
new_context = sidtab_search(&sidtab, new_sid);
if (!new_context) {
printk(KERN_ERR "SELinux: %s: unrecognized SID %u\n",
__func__, new_sid);
goto out;
}
/* type/domain unchaned */
if (old_context->type == new_context->type) {
rc = 0;
goto out;
}
index = new_context->type;
while (true) {
type = policydb.type_val_to_struct[index - 1];
BUG_ON(!type);
/* not bounded anymore */
if (!type->bounds) {
rc = -EPERM;
break;
}
/* @newsid is bounded by @oldsid */
if (type->bounds == old_context->type) {
rc = 0;
break;
}
index = type->bounds;
}
out:
read_unlock(&policy_rwlock);
return rc;
}
/**
* security_compute_av - Compute access vector decisions.
* @ssid: source security identifier
......@@ -794,7 +964,7 @@ static int string_to_context_struct(struct policydb *pol,
*p++ = 0;
typdatum = hashtab_search(pol->p_types.table, scontextp);
if (!typdatum)
if (!typdatum || typdatum->attribute)
goto out;
ctx->type = typdatum->value;
......@@ -1037,7 +1207,7 @@ static int security_compute_sid(u32 ssid,
/* If no permanent rule, also check for enabled conditional rules */
if (!avdatum) {
node = avtab_search_node(&policydb.te_cond_avtab, &avkey);
for (; node != NULL; node = avtab_search_node_next(node, specified)) {
for (; node; node = avtab_search_node_next(node, specified)) {
if (node->key.specified & AVTAB_ENABLED) {
avdatum = &node->datum;
break;
......@@ -2050,7 +2220,7 @@ int security_set_bools(int len, int *values)
policydb.bool_val_to_struct[i]->state = 0;
}
for (cur = policydb.cond_list; cur != NULL; cur = cur->next) {
for (cur = policydb.cond_list; cur; cur = cur->next) {
rc = evaluate_cond_node(&policydb, cur);
if (rc)
goto out;
......@@ -2102,7 +2272,7 @@ static int security_preserve_bools(struct policydb *p)
if (booldatum)
booldatum->state = bvalues[i];
}
for (cur = p->cond_list; cur != NULL; cur = cur->next) {
for (cur = p->cond_list; cur; cur = cur->next) {
rc = evaluate_cond_node(p, cur);
if (rc)
goto out;
......
......@@ -43,7 +43,7 @@ int sidtab_insert(struct sidtab *s, u32 sid, struct context *context)
hvalue = SIDTAB_HASH(sid);
prev = NULL;
cur = s->htable[hvalue];
while (cur != NULL && sid > cur->sid) {
while (cur && sid > cur->sid) {
prev = cur;
cur = cur->next;
}
......@@ -92,7 +92,7 @@ static struct context *sidtab_search_core(struct sidtab *s, u32 sid, int force)
hvalue = SIDTAB_HASH(sid);
cur = s->htable[hvalue];
while (cur != NULL && sid > cur->sid)
while (cur && sid > cur->sid)
cur = cur->next;
if (force && cur && sid == cur->sid && cur->context.len)
......@@ -103,7 +103,7 @@ static struct context *sidtab_search_core(struct sidtab *s, u32 sid, int force)
sid = SECINITSID_UNLABELED;
hvalue = SIDTAB_HASH(sid);
cur = s->htable[hvalue];
while (cur != NULL && sid > cur->sid)
while (cur && sid > cur->sid)
cur = cur->next;
if (!cur || sid != cur->sid)
return NULL;
......@@ -136,7 +136,7 @@ int sidtab_map(struct sidtab *s,
for (i = 0; i < SIDTAB_SIZE; i++) {
cur = s->htable[i];
while (cur != NULL) {
while (cur) {
rc = apply(cur->sid, &cur->context, args);
if (rc)
goto out;
......@@ -155,7 +155,7 @@ static inline u32 sidtab_search_context(struct sidtab *s,
for (i = 0; i < SIDTAB_SIZE; i++) {
cur = s->htable[i];
while (cur != NULL) {
while (cur) {
if (context_cmp(&cur->context, context))
return cur->sid;
cur = cur->next;
......@@ -242,7 +242,7 @@ void sidtab_destroy(struct sidtab *s)
for (i = 0; i < SIDTAB_SIZE; i++) {
cur = s->htable[i];
while (cur != NULL) {
while (cur) {
temp = cur;
cur = cur->next;
context_destroy(&temp->context);
......
......@@ -178,6 +178,7 @@ u32 smack_to_secid(const char *);
extern int smack_cipso_direct;
extern int smack_net_nltype;
extern char *smack_net_ambient;
extern char *smack_onlycap;
extern struct smack_known *smack_known;
extern struct smack_known smack_known_floor;
......
......@@ -157,7 +157,7 @@ int smk_access(char *subject_label, char *object_label, int request)
*
* This function checks the current subject label/object label pair
* in the access rule list and returns 0 if the access is permitted,
* non zero otherwise. It allows that current my have the capability
* non zero otherwise. It allows that current may have the capability
* to override the rules.
*/
int smk_curacc(char *obj_label, u32 mode)
......@@ -168,6 +168,14 @@ int smk_curacc(char *obj_label, u32 mode)
if (rc == 0)
return 0;
/*
* Return if a specific label has been designated as the
* only one that gets privilege and current does not
* have that label.
*/
if (smack_onlycap != NULL && smack_onlycap != current->security)
return rc;
if (capable(CAP_MAC_OVERRIDE))
return 0;
......
......@@ -39,6 +39,7 @@ enum smk_inos {
SMK_DIRECT = 6, /* CIPSO level indicating direct label */
SMK_AMBIENT = 7, /* internet ambient label */
SMK_NLTYPE = 8, /* label scheme to use by default */
SMK_ONLYCAP = 9, /* the only "capable" label */
};
/*
......@@ -68,6 +69,16 @@ int smack_net_nltype = NETLBL_NLTYPE_CIPSOV4;
*/
int smack_cipso_direct = SMACK_CIPSO_DIRECT_DEFAULT;
/*
* Unless a process is running with this label even
* having CAP_MAC_OVERRIDE isn't enough to grant
* privilege to violate MAC policy. If no label is
* designated (the NULL case) capabilities apply to
* everyone. It is expected that the hat (^) label
* will be used if any label is used.
*/
char *smack_onlycap;
static int smk_cipso_doi_value = SMACK_CIPSO_DOI_DEFAULT;
struct smk_list_entry *smack_list;
......@@ -787,6 +798,85 @@ static const struct file_operations smk_ambient_ops = {
.write = smk_write_ambient,
};
/**
* smk_read_onlycap - read() for /smack/onlycap
* @filp: file pointer, not actually used
* @buf: where to put the result
* @cn: maximum to send along
* @ppos: where to start
*
* Returns number of bytes read or error code, as appropriate
*/
static ssize_t smk_read_onlycap(struct file *filp, char __user *buf,
size_t cn, loff_t *ppos)
{
char *smack = "";
ssize_t rc = -EINVAL;
int asize;
if (*ppos != 0)
return 0;
if (smack_onlycap != NULL)
smack = smack_onlycap;
asize = strlen(smack) + 1;
if (cn >= asize)
rc = simple_read_from_buffer(buf, cn, ppos, smack, asize);
return rc;
}
/**
* smk_write_onlycap - write() for /smack/onlycap
* @filp: file pointer, not actually used
* @buf: where to get the data from
* @count: bytes sent
* @ppos: where to start
*
* Returns number of bytes written or error code, as appropriate
*/
static ssize_t smk_write_onlycap(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
char in[SMK_LABELLEN];
char *sp = current->security;
if (!capable(CAP_MAC_ADMIN))
return -EPERM;
/*
* This can be done using smk_access() but is done
* explicitly for clarity. The smk_access() implementation
* would use smk_access(smack_onlycap, MAY_WRITE)
*/
if (smack_onlycap != NULL && smack_onlycap != sp)
return -EPERM;
if (count >= SMK_LABELLEN)
return -EINVAL;
if (copy_from_user(in, buf, count) != 0)
return -EFAULT;
/*
* Should the null string be passed in unset the onlycap value.
* This seems like something to be careful with as usually
* smk_import only expects to return NULL for errors. It
* is usually the case that a nullstring or "\n" would be
* bad to pass to smk_import but in fact this is useful here.
*/
smack_onlycap = smk_import(in, count);
return count;
}
static const struct file_operations smk_onlycap_ops = {
.read = smk_read_onlycap,
.write = smk_write_onlycap,
};
struct option_names {
int o_number;
char *o_name;
......@@ -919,6 +1009,8 @@ static int smk_fill_super(struct super_block *sb, void *data, int silent)
{"ambient", &smk_ambient_ops, S_IRUGO|S_IWUSR},
[SMK_NLTYPE] =
{"nltype", &smk_nltype_ops, S_IRUGO|S_IWUSR},
[SMK_ONLYCAP] =
{"onlycap", &smk_onlycap_ops, S_IRUGO|S_IWUSR},
/* last one */ {""}
};
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment