Commit 8b5c6a3a authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'audit-pr-20180605' of git://git.kernel.org/pub/scm/linux/kernel/git/pcmoore/audit

Pull audit updates from Paul Moore:
 "Another reasonable chunk of audit changes for v4.18, thirteen patches
  in total.

  The thirteen patches can mostly be broken down into one of four
  categories: general bug fixes, accessor functions for audit state
  stored in the task_struct, negative filter matches on executable
  names, and extending the (relatively) new seccomp logging knobs to the
  audit subsystem.

  The main driver for the accessor functions from Richard are the
  changes we're working on to associate audit events with containers,
  but I think they have some standalone value too so I figured it would
  be good to get them in now.

  The seccomp/audit patches from Tyler apply the seccomp logging
  improvements from a few releases ago to audit's seccomp logging;
  starting with this patchset the changes in
  /proc/sys/kernel/seccomp/actions_logged should apply to both the
  standard kernel logging and audit.

  As usual, everything passes the audit-testsuite and it happens to
  merge cleanly with your tree"

[ Heh, except it had trivial merge conflicts with the SELinux tree that
  also came in from Paul   - Linus ]

* tag 'audit-pr-20180605' of git://git.kernel.org/pub/scm/linux/kernel/git/pcmoore/audit:
  audit: Fix wrong task in comparison of session ID
  audit: use existing session info function
  audit: normalize loginuid read access
  audit: use new audit_context access funciton for seccomp_actions_logged
  audit: use inline function to set audit context
  audit: use inline function to get audit context
  audit: convert sessionid unset to a macro
  seccomp: Don't special case audited processes when logging
  seccomp: Audit attempts to modify the actions_logged sysctl
  seccomp: Configurable separator for the actions_logged string
  seccomp: Separate read and write code for actions_logged sysctl
  audit: allow not equal op for audit by executable
  audit: add syscall information to FEATURE_CHANGE records
parents 8b70543e 5b713886
......@@ -207,13 +207,6 @@ directory. Here's a description of each file in that directory:
to the file do not need to be in ordered form but reads from the file
will be ordered in the same way as the actions_avail sysctl.
It is important to note that the value of ``actions_logged`` does not
prevent certain actions from being logged when the audit subsystem is
configured to audit a task. If the action is not found in
``actions_logged`` list, the final decision on whether to audit the
action for that task is ultimately left up to the audit subsystem to
decide for all seccomp return values other than ``SECCOMP_RET_ALLOW``.
The ``allow`` string is not accepted in the ``actions_logged`` sysctl
as it is not possible to log ``SECCOMP_RET_ALLOW`` actions. Attempting
to write ``allow`` to the sysctl will result in an EINVAL being
......
......@@ -232,12 +232,24 @@ extern void __audit_file(const struct file *);
extern void __audit_inode_child(struct inode *parent,
const struct dentry *dentry,
const unsigned char type);
extern void __audit_seccomp(unsigned long syscall, long signr, int code);
extern void audit_seccomp(unsigned long syscall, long signr, int code);
extern void audit_seccomp_actions_logged(const char *names,
const char *old_names, int res);
extern void __audit_ptrace(struct task_struct *t);
static inline void audit_set_context(struct task_struct *task, struct audit_context *ctx)
{
task->audit_context = ctx;
}
static inline struct audit_context *audit_context(void)
{
return current->audit_context;
}
static inline bool audit_dummy_context(void)
{
void *p = current->audit_context;
void *p = audit_context();
return !p || *(int *)p;
}
static inline void audit_free(struct task_struct *task)
......@@ -249,12 +261,12 @@ static inline void audit_syscall_entry(int major, unsigned long a0,
unsigned long a1, unsigned long a2,
unsigned long a3)
{
if (unlikely(current->audit_context))
if (unlikely(audit_context()))
__audit_syscall_entry(major, a0, a1, a2, a3);
}
static inline void audit_syscall_exit(void *pt_regs)
{
if (unlikely(current->audit_context)) {
if (unlikely(audit_context())) {
int success = is_syscall_success(pt_regs);
long return_code = regs_return_value(pt_regs);
......@@ -302,12 +314,6 @@ static inline void audit_inode_child(struct inode *parent,
}
void audit_core_dumps(long signr);
static inline void audit_seccomp(unsigned long syscall, long signr, int code)
{
if (audit_enabled && unlikely(!audit_dummy_context()))
__audit_seccomp(syscall, signr, code);
}
static inline void audit_ptrace(struct task_struct *t)
{
if (unlikely(!audit_dummy_context()))
......@@ -468,6 +474,12 @@ static inline bool audit_dummy_context(void)
{
return true;
}
static inline void audit_set_context(struct task_struct *task, struct audit_context *ctx)
{ }
static inline struct audit_context *audit_context(void)
{
return NULL;
}
static inline struct filename *audit_reusename(const __user char *name)
{
return NULL;
......@@ -498,10 +510,11 @@ static inline void audit_inode_child(struct inode *parent,
{ }
static inline void audit_core_dumps(long signr)
{ }
static inline void __audit_seccomp(unsigned long syscall, long signr, int code)
{ }
static inline void audit_seccomp(unsigned long syscall, long signr, int code)
{ }
static inline void audit_seccomp_actions_logged(const char *names,
const char *old_names, int res)
{ }
static inline int auditsc_get_stamp(struct audit_context *ctx,
struct timespec64 *t, unsigned int *serial)
{
......@@ -513,7 +526,7 @@ static inline kuid_t audit_get_loginuid(struct task_struct *tsk)
}
static inline unsigned int audit_get_sessionid(struct task_struct *tsk)
{
return -1;
return AUDIT_SID_UNSET;
}
static inline void audit_ipc_obj(struct kern_ipc_perm *ipcp)
{ }
......
......@@ -737,7 +737,7 @@ static inline struct audit_buffer *xfrm_audit_start(const char *op)
if (audit_enabled == 0)
return NULL;
audit_buf = audit_log_start(current->audit_context, GFP_ATOMIC,
audit_buf = audit_log_start(audit_context(), GFP_ATOMIC,
AUDIT_MAC_IPSEC_EVENT);
if (audit_buf == NULL)
return NULL;
......@@ -752,7 +752,7 @@ static inline void xfrm_audit_helper_usrinfo(bool task_valid,
audit_get_loginuid(current) :
INVALID_UID);
const unsigned int ses = task_valid ? audit_get_sessionid(current) :
(unsigned int) -1;
AUDIT_SID_UNSET;
audit_log_format(audit_buf, " auid=%u ses=%u", auid, ses);
audit_log_task_context(audit_buf);
......
......@@ -465,6 +465,7 @@ struct audit_tty_status {
};
#define AUDIT_UID_UNSET (unsigned int)-1
#define AUDIT_SID_UNSET ((unsigned int)-1)
/* audit_rule_data supports filter rules with both integer and string
* fields. It corresponds with AUDIT_ADD_RULE, AUDIT_DEL_RULE and
......
......@@ -9,6 +9,7 @@
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/audit.h>
#include <asm/pgtable.h>
#include <linux/uaccess.h>
......@@ -119,7 +120,7 @@ struct task_struct init_task
.thread_node = LIST_HEAD_INIT(init_signals.thread_head),
#ifdef CONFIG_AUDITSYSCALL
.loginuid = INVALID_UID,
.sessionid = (unsigned int)-1,
.sessionid = AUDIT_SID_UNSET,
#endif
#ifdef CONFIG_PERF_EVENTS
.perf_event_mutex = __MUTEX_INITIALIZER(init_task.perf_event_mutex),
......
......@@ -1099,8 +1099,7 @@ static void audit_log_feature_change(int which, u32 old_feature, u32 new_feature
if (audit_enabled == AUDIT_OFF)
return;
ab = audit_log_start(NULL, GFP_KERNEL, AUDIT_FEATURE_CHANGE);
ab = audit_log_start(audit_context(), GFP_KERNEL, AUDIT_FEATURE_CHANGE);
if (!ab)
return;
audit_log_task_info(ab, current);
......@@ -2317,8 +2316,7 @@ void audit_log_link_denied(const char *operation)
return;
/* Generate AUDIT_ANOM_LINK with subject, operation, outcome. */
ab = audit_log_start(current->audit_context, GFP_KERNEL,
AUDIT_ANOM_LINK);
ab = audit_log_start(audit_context(), GFP_KERNEL, AUDIT_ANOM_LINK);
if (!ab)
return;
audit_log_format(ab, "op=%s", operation);
......
......@@ -274,7 +274,7 @@ static void audit_update_watch(struct audit_parent *parent,
/* If the update involves invalidating rules, do the inode-based
* filtering now, so we don't omit records. */
if (invalidating && !audit_dummy_context())
audit_filter_inodes(current, current->audit_context);
audit_filter_inodes(current, audit_context());
/* updating ino will likely change which audit_hash_list we
* are on so we need a new watch for the new list */
......
......@@ -426,7 +426,7 @@ static int audit_field_valid(struct audit_entry *entry, struct audit_field *f)
return -EINVAL;
break;
case AUDIT_EXE:
if (f->op != Audit_equal)
if (f->op != Audit_not_equal && f->op != Audit_equal)
return -EINVAL;
if (entry->rule.listnr != AUDIT_FILTER_EXIT)
return -EINVAL;
......@@ -1089,8 +1089,6 @@ static void audit_list_rules(int seq, struct sk_buff_head *q)
static void audit_log_rule_change(char *action, struct audit_krule *rule, int res)
{
struct audit_buffer *ab;
uid_t loginuid = from_kuid(&init_user_ns, audit_get_loginuid(current));
unsigned int sessionid = audit_get_sessionid(current);
if (!audit_enabled)
return;
......@@ -1098,7 +1096,7 @@ static void audit_log_rule_change(char *action, struct audit_krule *rule, int re
ab = audit_log_start(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE);
if (!ab)
return;
audit_log_format(ab, "auid=%u ses=%u" ,loginuid, sessionid);
audit_log_session_info(ab);
audit_log_task_context(ab);
audit_log_format(ab, " op=%s", action);
audit_log_key(ab, rule->filterkey);
......
This diff is collapsed.
......@@ -1712,7 +1712,7 @@ static __latent_entropy struct task_struct *copy_process(
p->start_time = ktime_get_ns();
p->real_start_time = ktime_get_boot_ns();
p->io_context = NULL;
p->audit_context = NULL;
audit_set_context(p, NULL);
cgroup_fork(p);
#ifdef CONFIG_NUMA
p->mempolicy = mpol_dup(p->mempolicy);
......
......@@ -593,18 +593,15 @@ static inline void seccomp_log(unsigned long syscall, long signr, u32 action,
}
/*
* Force an audit message to be emitted when the action is RET_KILL_*,
* RET_LOG, or the FILTER_FLAG_LOG bit was set and the action is
* allowed to be logged by the admin.
* Emit an audit message when the action is RET_KILL_*, RET_LOG, or the
* FILTER_FLAG_LOG bit was set. The admin has the ability to silence
* any action from being logged by removing the action name from the
* seccomp_actions_logged sysctl.
*/
if (log)
return __audit_seccomp(syscall, signr, action);
if (!log)
return;
/*
* Let the audit subsystem decide if the action should be audited based
* on whether the current task itself is being audited.
*/
return audit_seccomp(syscall, signr, action);
audit_seccomp(syscall, signr, action);
}
/*
......@@ -1144,10 +1141,11 @@ static const struct seccomp_log_name seccomp_log_names[] = {
};
static bool seccomp_names_from_actions_logged(char *names, size_t size,
u32 actions_logged)
u32 actions_logged,
const char *sep)
{
const struct seccomp_log_name *cur;
bool append_space = false;
bool append_sep = false;
for (cur = seccomp_log_names; cur->name && size; cur++) {
ssize_t ret;
......@@ -1155,15 +1153,15 @@ static bool seccomp_names_from_actions_logged(char *names, size_t size,
if (!(actions_logged & cur->log))
continue;
if (append_space) {
ret = strscpy(names, " ", size);
if (append_sep) {
ret = strscpy(names, sep, size);
if (ret < 0)
return false;
names += ret;
size -= ret;
} else
append_space = true;
append_sep = true;
ret = strscpy(names, cur->name, size);
if (ret < 0)
......@@ -1208,46 +1206,102 @@ static bool seccomp_actions_logged_from_names(u32 *actions_logged, char *names)
return true;
}
static int seccomp_actions_logged_handler(struct ctl_table *ro_table, int write,
void __user *buffer, size_t *lenp,
loff_t *ppos)
static int read_actions_logged(struct ctl_table *ro_table, void __user *buffer,
size_t *lenp, loff_t *ppos)
{
char names[sizeof(seccomp_actions_avail)];
struct ctl_table table;
memset(names, 0, sizeof(names));
if (!seccomp_names_from_actions_logged(names, sizeof(names),
seccomp_actions_logged, " "))
return -EINVAL;
table = *ro_table;
table.data = names;
table.maxlen = sizeof(names);
return proc_dostring(&table, 0, buffer, lenp, ppos);
}
static int write_actions_logged(struct ctl_table *ro_table, void __user *buffer,
size_t *lenp, loff_t *ppos, u32 *actions_logged)
{
char names[sizeof(seccomp_actions_avail)];
struct ctl_table table;
int ret;
if (write && !capable(CAP_SYS_ADMIN))
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
memset(names, 0, sizeof(names));
if (!write) {
if (!seccomp_names_from_actions_logged(names, sizeof(names),
seccomp_actions_logged))
return -EINVAL;
}
table = *ro_table;
table.data = names;
table.maxlen = sizeof(names);
ret = proc_dostring(&table, write, buffer, lenp, ppos);
ret = proc_dostring(&table, 1, buffer, lenp, ppos);
if (ret)
return ret;
if (write) {
u32 actions_logged;
if (!seccomp_actions_logged_from_names(actions_logged, table.data))
return -EINVAL;
if (!seccomp_actions_logged_from_names(&actions_logged,
table.data))
return -EINVAL;
if (*actions_logged & SECCOMP_LOG_ALLOW)
return -EINVAL;
if (actions_logged & SECCOMP_LOG_ALLOW)
return -EINVAL;
seccomp_actions_logged = *actions_logged;
return 0;
}
seccomp_actions_logged = actions_logged;
}
static void audit_actions_logged(u32 actions_logged, u32 old_actions_logged,
int ret)
{
char names[sizeof(seccomp_actions_avail)];
char old_names[sizeof(seccomp_actions_avail)];
const char *new = names;
const char *old = old_names;
return 0;
if (!audit_enabled)
return;
memset(names, 0, sizeof(names));
memset(old_names, 0, sizeof(old_names));
if (ret)
new = "?";
else if (!actions_logged)
new = "(none)";
else if (!seccomp_names_from_actions_logged(names, sizeof(names),
actions_logged, ","))
new = "?";
if (!old_actions_logged)
old = "(none)";
else if (!seccomp_names_from_actions_logged(old_names,
sizeof(old_names),
old_actions_logged, ","))
old = "?";
return audit_seccomp_actions_logged(new, old, !ret);
}
static int seccomp_actions_logged_handler(struct ctl_table *ro_table, int write,
void __user *buffer, size_t *lenp,
loff_t *ppos)
{
int ret;
if (write) {
u32 actions_logged = 0;
u32 old_actions_logged = seccomp_actions_logged;
ret = write_actions_logged(ro_table, buffer, lenp, ppos,
&actions_logged);
audit_actions_logged(actions_logged, old_actions_logged, ret);
} else
ret = read_actions_logged(ro_table, buffer, lenp, ppos);
return ret;
}
static struct ctl_path seccomp_sysctl_path[] = {
......
......@@ -1062,7 +1062,7 @@ static int do_replace_finish(struct net *net, struct ebt_replace *repl,
#ifdef CONFIG_AUDIT
if (audit_enabled) {
audit_log(current->audit_context, GFP_KERNEL,
audit_log(audit_context(), GFP_KERNEL,
AUDIT_NETFILTER_CFG,
"table=%s family=%u entries=%u",
repl->name, AF_BRIDGE, repl->nentries);
......
......@@ -6731,15 +6731,15 @@ static int __dev_set_promiscuity(struct net_device *dev, int inc, bool notify)
dev->flags & IFF_PROMISC ? "entered" : "left");
if (audit_enabled) {
current_uid_gid(&uid, &gid);
audit_log(current->audit_context, GFP_ATOMIC,
AUDIT_ANOM_PROMISCUOUS,
"dev=%s prom=%d old_prom=%d auid=%u uid=%u gid=%u ses=%u",
dev->name, (dev->flags & IFF_PROMISC),
(old_flags & IFF_PROMISC),
from_kuid(&init_user_ns, audit_get_loginuid(current)),
from_kuid(&init_user_ns, uid),
from_kgid(&init_user_ns, gid),
audit_get_sessionid(current));
audit_log(audit_context(), GFP_ATOMIC,
AUDIT_ANOM_PROMISCUOUS,
"dev=%s prom=%d old_prom=%d auid=%u uid=%u gid=%u ses=%u",
dev->name, (dev->flags & IFF_PROMISC),
(old_flags & IFF_PROMISC),
from_kuid(&init_user_ns, audit_get_loginuid(current)),
from_kuid(&init_user_ns, uid),
from_kgid(&init_user_ns, gid),
audit_get_sessionid(current));
}
dev_change_rx_flags(dev, IFF_PROMISC);
......
......@@ -1420,7 +1420,7 @@ xt_replace_table(struct xt_table *table,
#ifdef CONFIG_AUDIT
if (audit_enabled) {
audit_log(current->audit_context, GFP_KERNEL,
audit_log(audit_context(), GFP_KERNEL,
AUDIT_NETFILTER_CFG,
"table=%s family=%u entries=%u",
table->name, table->af, private->number);
......
......@@ -104,7 +104,7 @@ struct audit_buffer *netlbl_audit_start_common(int type,
if (audit_enabled == 0)
return NULL;
audit_buf = audit_log_start(current->audit_context, GFP_ATOMIC, type);
audit_buf = audit_log_start(audit_context(), GFP_ATOMIC, type);
if (audit_buf == NULL)
return NULL;
......
......@@ -326,7 +326,7 @@ void ima_audit_measurement(struct integrity_iint_cache *iint,
hex_byte_pack(hash + (i * 2), iint->ima_hash->digest[i]);
hash[i * 2] = '\0';
ab = audit_log_start(current->audit_context, GFP_KERNEL,
ab = audit_log_start(audit_context(), GFP_KERNEL,
AUDIT_INTEGRITY_RULE);
if (!ab)
goto out;
......
......@@ -38,7 +38,7 @@ void integrity_audit_msg(int audit_msgno, struct inode *inode,
if (!integrity_audit_info && audit_info == 1) /* Skip info messages */
return;
ab = audit_log_start(current->audit_context, GFP_KERNEL, audit_msgno);
ab = audit_log_start(audit_context(), GFP_KERNEL, audit_msgno);
audit_log_format(ab, "pid=%d uid=%u auid=%u ses=%u",
task_pid_nr(current),
from_kuid(&init_user_ns, current_cred()->uid),
......
......@@ -447,7 +447,7 @@ void common_lsm_audit(struct common_audit_data *a,
if (a == NULL)
return;
/* we use GFP_ATOMIC so we won't sleep */
ab = audit_log_start(current->audit_context, GFP_ATOMIC | __GFP_NOWARN,
ab = audit_log_start(audit_context(), GFP_ATOMIC | __GFP_NOWARN,
AUDIT_AVC);
if (ab == NULL)
......
......@@ -3305,7 +3305,8 @@ static int selinux_inode_setxattr(struct dentry *dentry, const char *name,
} else {
audit_size = 0;
}
ab = audit_log_start(current->audit_context, GFP_ATOMIC, AUDIT_SELINUX_ERR);
ab = audit_log_start(audit_context(),
GFP_ATOMIC, AUDIT_SELINUX_ERR);
audit_log_format(ab, "op=setxattr invalid_context=");
audit_log_n_untrustedstring(ab, value, audit_size);
audit_log_end(ab);
......@@ -6460,7 +6461,9 @@ static int selinux_setprocattr(const char *name, void *value, size_t size)
audit_size = size - 1;
else
audit_size = size;
ab = audit_log_start(current->audit_context, GFP_ATOMIC, AUDIT_SELINUX_ERR);
ab = audit_log_start(audit_context(),
GFP_ATOMIC,
AUDIT_SELINUX_ERR);
audit_log_format(ab, "op=fscreate invalid_context=");
audit_log_n_untrustedstring(ab, value, audit_size);
audit_log_end(ab);
......
......@@ -167,7 +167,7 @@ static ssize_t sel_write_enforce(struct file *file, const char __user *buf,
NULL);
if (length)
goto out;
audit_log(current->audit_context, GFP_KERNEL, AUDIT_MAC_STATUS,
audit_log(audit_context(), GFP_KERNEL, AUDIT_MAC_STATUS,
"enforcing=%d old_enforcing=%d auid=%u ses=%u"
" enabled=%d old-enabled=%d lsm=selinux res=1",
new_value, old_value,
......@@ -303,7 +303,7 @@ static ssize_t sel_write_disable(struct file *file, const char __user *buf,
length = selinux_disable(fsi->state);
if (length)
goto out;
audit_log(current->audit_context, GFP_KERNEL, AUDIT_MAC_STATUS,
audit_log(audit_context(), GFP_KERNEL, AUDIT_MAC_STATUS,
"enforcing=%d old_enforcing=%d auid=%u ses=%u"
" enabled=%d old-enabled=%d lsm=selinux res=1",
enforcing, enforcing,
......@@ -581,7 +581,7 @@ static ssize_t sel_write_load(struct file *file, const char __user *buf,
length = count;
out1:
audit_log(current->audit_context, GFP_KERNEL, AUDIT_MAC_POLICY_LOAD,
audit_log(audit_context(), GFP_KERNEL, AUDIT_MAC_POLICY_LOAD,
"auid=%u ses=%u lsm=selinux res=1",
from_kuid(&init_user_ns, audit_get_loginuid(current)),
audit_get_sessionid(current));
......
......@@ -501,7 +501,7 @@ static void security_dump_masked_av(struct policydb *policydb,
goto out;
/* audit a message */
ab = audit_log_start(current->audit_context,
ab = audit_log_start(audit_context(),
GFP_ATOMIC, AUDIT_SELINUX_ERR);
if (!ab)
goto out;
......@@ -743,7 +743,7 @@ static int security_validtrans_handle_fail(struct selinux_state *state,
goto out;
if (context_struct_to_string(p, tcontext, &t, &tlen))
goto out;
audit_log(current->audit_context, GFP_ATOMIC, AUDIT_SELINUX_ERR,
audit_log(audit_context(), GFP_ATOMIC, AUDIT_SELINUX_ERR,
"op=security_validate_transition seresult=denied"
" oldcontext=%s newcontext=%s taskcontext=%s tclass=%s",
o, n, t, sym_name(p, SYM_CLASSES, tclass-1));
......@@ -929,7 +929,7 @@ int security_bounded_transition(struct selinux_state *state,
&old_name, &length) &&
!context_struct_to_string(policydb, new_context,
&new_name, &length)) {
audit_log(current->audit_context,
audit_log(audit_context(),
GFP_ATOMIC, AUDIT_SELINUX_ERR,
"op=security_bounded_transition "
"seresult=denied "
......@@ -1586,7 +1586,7 @@ static int compute_sid_handle_invalid_context(
goto out;
if (context_struct_to_string(policydb, newcontext, &n, &nlen))
goto out;
audit_log(current->audit_context, GFP_ATOMIC, AUDIT_SELINUX_ERR,
audit_log(audit_context(), GFP_ATOMIC, AUDIT_SELINUX_ERR,
"op=security_compute_sid invalid_context=%s"
" scontext=%s"
" tcontext=%s"
......@@ -2882,7 +2882,7 @@ int security_set_bools(struct selinux_state *state, int len, int *values)
for (i = 0; i < len; i++) {
if (!!values[i] != policydb->bool_val_to_struct[i]->state) {
audit_log(current->audit_context, GFP_ATOMIC,
audit_log(audit_context(), GFP_ATOMIC,
AUDIT_MAC_CONFIG_CHANGE,
"bool=%s val=%d old_val=%d auid=%u ses=%u",
sym_name(policydb, SYM_BOOLS, i),
......@@ -3025,7 +3025,7 @@ int security_sid_mls_copy(struct selinux_state *state,
if (rc) {
if (!context_struct_to_string(policydb, &newcon, &s,
&len)) {
audit_log(current->audit_context,
audit_log(audit_context(),
GFP_ATOMIC, AUDIT_SELINUX_ERR,
"op=security_sid_mls_copy "
"invalid_context=%s", s);
......
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