2005-04-16 22:20:36 +00:00
|
|
|
/*
|
|
|
|
* Netlink message type permission tables, for user generated messages.
|
|
|
|
*
|
|
|
|
* Author: James Morris <jmorris@redhat.com>
|
|
|
|
*
|
|
|
|
* Copyright (C) 2004 Red Hat, Inc., James Morris <jmorris@redhat.com>
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License version 2,
|
|
|
|
* as published by the Free Software Foundation.
|
|
|
|
*/
|
|
|
|
#include <linux/types.h>
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/netlink.h>
|
|
|
|
#include <linux/rtnetlink.h>
|
|
|
|
#include <linux/if.h>
|
2005-08-16 03:34:48 +00:00
|
|
|
#include <linux/inet_diag.h>
|
2005-04-16 22:20:36 +00:00
|
|
|
#include <linux/xfrm.h>
|
|
|
|
#include <linux/audit.h>
|
2014-01-28 19:45:41 +00:00
|
|
|
#include <linux/sock_diag.h>
|
2005-04-16 22:20:36 +00:00
|
|
|
|
|
|
|
#include "flask.h"
|
|
|
|
#include "av_permissions.h"
|
2011-08-30 02:09:15 +00:00
|
|
|
#include "security.h"
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2008-04-18 21:38:26 +00:00
|
|
|
struct nlmsg_perm {
|
2005-04-16 22:20:36 +00:00
|
|
|
u16 nlmsg_type;
|
|
|
|
u32 perm;
|
|
|
|
};
|
|
|
|
|
2017-03-06 16:58:08 +00:00
|
|
|
static const struct nlmsg_perm nlmsg_route_perms[] =
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
|
|
|
{ RTM_NEWLINK, NETLINK_ROUTE_SOCKET__NLMSG_WRITE },
|
|
|
|
{ RTM_DELLINK, NETLINK_ROUTE_SOCKET__NLMSG_WRITE },
|
|
|
|
{ RTM_GETLINK, NETLINK_ROUTE_SOCKET__NLMSG_READ },
|
|
|
|
{ RTM_SETLINK, NETLINK_ROUTE_SOCKET__NLMSG_WRITE },
|
|
|
|
{ RTM_NEWADDR, NETLINK_ROUTE_SOCKET__NLMSG_WRITE },
|
|
|
|
{ RTM_DELADDR, NETLINK_ROUTE_SOCKET__NLMSG_WRITE },
|
|
|
|
{ RTM_GETADDR, NETLINK_ROUTE_SOCKET__NLMSG_READ },
|
|
|
|
{ RTM_NEWROUTE, NETLINK_ROUTE_SOCKET__NLMSG_WRITE },
|
|
|
|
{ RTM_DELROUTE, NETLINK_ROUTE_SOCKET__NLMSG_WRITE },
|
|
|
|
{ RTM_GETROUTE, NETLINK_ROUTE_SOCKET__NLMSG_READ },
|
|
|
|
{ RTM_NEWNEIGH, NETLINK_ROUTE_SOCKET__NLMSG_WRITE },
|
|
|
|
{ RTM_DELNEIGH, NETLINK_ROUTE_SOCKET__NLMSG_WRITE },
|
|
|
|
{ RTM_GETNEIGH, NETLINK_ROUTE_SOCKET__NLMSG_READ },
|
|
|
|
{ RTM_NEWRULE, NETLINK_ROUTE_SOCKET__NLMSG_WRITE },
|
|
|
|
{ RTM_DELRULE, NETLINK_ROUTE_SOCKET__NLMSG_WRITE },
|
|
|
|
{ RTM_GETRULE, NETLINK_ROUTE_SOCKET__NLMSG_READ },
|
|
|
|
{ RTM_NEWQDISC, NETLINK_ROUTE_SOCKET__NLMSG_WRITE },
|
|
|
|
{ RTM_DELQDISC, NETLINK_ROUTE_SOCKET__NLMSG_WRITE },
|
|
|
|
{ RTM_GETQDISC, NETLINK_ROUTE_SOCKET__NLMSG_READ },
|
|
|
|
{ RTM_NEWTCLASS, NETLINK_ROUTE_SOCKET__NLMSG_WRITE },
|
|
|
|
{ RTM_DELTCLASS, NETLINK_ROUTE_SOCKET__NLMSG_WRITE },
|
|
|
|
{ RTM_GETTCLASS, NETLINK_ROUTE_SOCKET__NLMSG_READ },
|
|
|
|
{ RTM_NEWTFILTER, NETLINK_ROUTE_SOCKET__NLMSG_WRITE },
|
|
|
|
{ RTM_DELTFILTER, NETLINK_ROUTE_SOCKET__NLMSG_WRITE },
|
|
|
|
{ RTM_GETTFILTER, NETLINK_ROUTE_SOCKET__NLMSG_READ },
|
|
|
|
{ RTM_NEWACTION, NETLINK_ROUTE_SOCKET__NLMSG_WRITE },
|
|
|
|
{ RTM_DELACTION, NETLINK_ROUTE_SOCKET__NLMSG_WRITE },
|
|
|
|
{ RTM_GETACTION, NETLINK_ROUTE_SOCKET__NLMSG_READ },
|
|
|
|
{ RTM_NEWPREFIX, NETLINK_ROUTE_SOCKET__NLMSG_WRITE },
|
|
|
|
{ RTM_GETMULTICAST, NETLINK_ROUTE_SOCKET__NLMSG_READ },
|
|
|
|
{ RTM_GETANYCAST, NETLINK_ROUTE_SOCKET__NLMSG_READ },
|
2005-06-19 05:50:55 +00:00
|
|
|
{ RTM_GETNEIGHTBL, NETLINK_ROUTE_SOCKET__NLMSG_READ },
|
|
|
|
{ RTM_SETNEIGHTBL, NETLINK_ROUTE_SOCKET__NLMSG_WRITE },
|
2008-11-05 12:35:06 +00:00
|
|
|
{ RTM_NEWADDRLABEL, NETLINK_ROUTE_SOCKET__NLMSG_WRITE },
|
|
|
|
{ RTM_DELADDRLABEL, NETLINK_ROUTE_SOCKET__NLMSG_WRITE },
|
|
|
|
{ RTM_GETADDRLABEL, NETLINK_ROUTE_SOCKET__NLMSG_READ },
|
2010-12-16 16:46:51 +00:00
|
|
|
{ RTM_GETDCB, NETLINK_ROUTE_SOCKET__NLMSG_READ },
|
|
|
|
{ RTM_SETDCB, NETLINK_ROUTE_SOCKET__NLMSG_WRITE },
|
2012-12-07 18:59:48 +00:00
|
|
|
{ RTM_NEWNETCONF, NETLINK_ROUTE_SOCKET__NLMSG_WRITE },
|
2017-03-28 21:28:01 +00:00
|
|
|
{ RTM_DELNETCONF, NETLINK_ROUTE_SOCKET__NLMSG_WRITE },
|
2012-12-07 18:59:48 +00:00
|
|
|
{ RTM_GETNETCONF, NETLINK_ROUTE_SOCKET__NLMSG_READ },
|
2012-12-14 22:09:50 +00:00
|
|
|
{ RTM_NEWMDB, NETLINK_ROUTE_SOCKET__NLMSG_WRITE },
|
|
|
|
{ RTM_DELMDB, NETLINK_ROUTE_SOCKET__NLMSG_WRITE },
|
2012-12-07 00:04:48 +00:00
|
|
|
{ RTM_GETMDB, NETLINK_ROUTE_SOCKET__NLMSG_READ },
|
2015-04-08 16:36:38 +00:00
|
|
|
{ RTM_NEWNSID, NETLINK_ROUTE_SOCKET__NLMSG_WRITE },
|
2015-04-08 16:36:39 +00:00
|
|
|
{ RTM_DELNSID, NETLINK_ROUTE_SOCKET__NLMSG_READ },
|
2015-04-08 16:36:38 +00:00
|
|
|
{ RTM_GETNSID, NETLINK_ROUTE_SOCKET__NLMSG_READ },
|
2016-04-20 15:43:43 +00:00
|
|
|
{ RTM_NEWSTATS, NETLINK_ROUTE_SOCKET__NLMSG_READ },
|
|
|
|
{ RTM_GETSTATS, NETLINK_ROUTE_SOCKET__NLMSG_READ },
|
2005-04-16 22:20:36 +00:00
|
|
|
};
|
|
|
|
|
2017-03-06 16:58:08 +00:00
|
|
|
static const struct nlmsg_perm nlmsg_tcpdiag_perms[] =
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
|
|
|
{ TCPDIAG_GETSOCK, NETLINK_TCPDIAG_SOCKET__NLMSG_READ },
|
2005-08-16 03:34:48 +00:00
|
|
|
{ DCCPDIAG_GETSOCK, NETLINK_TCPDIAG_SOCKET__NLMSG_READ },
|
2014-01-28 19:45:41 +00:00
|
|
|
{ SOCK_DIAG_BY_FAMILY, NETLINK_TCPDIAG_SOCKET__NLMSG_READ },
|
2016-02-03 16:17:12 +00:00
|
|
|
{ SOCK_DESTROY, NETLINK_TCPDIAG_SOCKET__NLMSG_WRITE },
|
2005-04-16 22:20:36 +00:00
|
|
|
};
|
|
|
|
|
2017-03-06 16:58:08 +00:00
|
|
|
static const struct nlmsg_perm nlmsg_xfrm_perms[] =
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
|
|
|
{ XFRM_MSG_NEWSA, NETLINK_XFRM_SOCKET__NLMSG_WRITE },
|
|
|
|
{ XFRM_MSG_DELSA, NETLINK_XFRM_SOCKET__NLMSG_WRITE },
|
|
|
|
{ XFRM_MSG_GETSA, NETLINK_XFRM_SOCKET__NLMSG_READ },
|
|
|
|
{ XFRM_MSG_NEWPOLICY, NETLINK_XFRM_SOCKET__NLMSG_WRITE },
|
|
|
|
{ XFRM_MSG_DELPOLICY, NETLINK_XFRM_SOCKET__NLMSG_WRITE },
|
|
|
|
{ XFRM_MSG_GETPOLICY, NETLINK_XFRM_SOCKET__NLMSG_READ },
|
|
|
|
{ XFRM_MSG_ALLOCSPI, NETLINK_XFRM_SOCKET__NLMSG_WRITE },
|
2006-03-21 03:17:39 +00:00
|
|
|
{ XFRM_MSG_ACQUIRE, NETLINK_XFRM_SOCKET__NLMSG_WRITE },
|
|
|
|
{ XFRM_MSG_EXPIRE, NETLINK_XFRM_SOCKET__NLMSG_WRITE },
|
2005-04-16 22:20:36 +00:00
|
|
|
{ XFRM_MSG_UPDPOLICY, NETLINK_XFRM_SOCKET__NLMSG_WRITE },
|
|
|
|
{ XFRM_MSG_UPDSA, NETLINK_XFRM_SOCKET__NLMSG_WRITE },
|
2006-03-21 03:17:39 +00:00
|
|
|
{ XFRM_MSG_POLEXPIRE, NETLINK_XFRM_SOCKET__NLMSG_WRITE },
|
|
|
|
{ XFRM_MSG_FLUSHSA, NETLINK_XFRM_SOCKET__NLMSG_WRITE },
|
|
|
|
{ XFRM_MSG_FLUSHPOLICY, NETLINK_XFRM_SOCKET__NLMSG_WRITE },
|
|
|
|
{ XFRM_MSG_NEWAE, NETLINK_XFRM_SOCKET__NLMSG_WRITE },
|
|
|
|
{ XFRM_MSG_GETAE, NETLINK_XFRM_SOCKET__NLMSG_READ },
|
2015-04-10 14:24:26 +00:00
|
|
|
{ XFRM_MSG_REPORT, NETLINK_XFRM_SOCKET__NLMSG_READ },
|
2015-04-10 14:24:27 +00:00
|
|
|
{ XFRM_MSG_MIGRATE, NETLINK_XFRM_SOCKET__NLMSG_WRITE },
|
2015-04-08 16:36:42 +00:00
|
|
|
{ XFRM_MSG_NEWSADINFO, NETLINK_XFRM_SOCKET__NLMSG_READ },
|
|
|
|
{ XFRM_MSG_GETSADINFO, NETLINK_XFRM_SOCKET__NLMSG_READ },
|
2015-04-08 16:36:40 +00:00
|
|
|
{ XFRM_MSG_NEWSPDINFO, NETLINK_XFRM_SOCKET__NLMSG_WRITE },
|
2015-04-08 16:36:41 +00:00
|
|
|
{ XFRM_MSG_GETSPDINFO, NETLINK_XFRM_SOCKET__NLMSG_READ },
|
2015-04-10 14:24:28 +00:00
|
|
|
{ XFRM_MSG_MAPPING, NETLINK_XFRM_SOCKET__NLMSG_READ },
|
2005-04-16 22:20:36 +00:00
|
|
|
};
|
|
|
|
|
2017-03-06 16:58:08 +00:00
|
|
|
static const struct nlmsg_perm nlmsg_audit_perms[] =
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
[PATCH] SELinux: add finer grained permissions to Netlink audit processing
This patch provides finer grained permissions for the audit family of
Netlink sockets under SELinux.
1. We need a way to differentiate between privileged and unprivileged
reads of kernel data maintained by the audit subsystem. The AUDIT_GET
operation is unprivileged: it returns the current status of the audit
subsystem (e.g. whether it's enabled etc.). The AUDIT_LIST operation
however returns a list of the current audit ruleset, which is considered
privileged by the audit folk. To deal with this, a new SELinux
permission has been implemented and applied to the operation:
nlmsg_readpriv, which can be allocated to appropriately privileged
domains. Unprivileged domains would only be allocated nlmsg_read.
2. There is a requirement for certain domains to generate audit events
from userspace. These events need to be collected by the kernel,
collated and transmitted sequentially back to the audit daemon. An
example is user level login, an auditable event under CAPP, where
login-related domains generate AUDIT_USER messages via PAM which are
relayed back to auditd via the kernel. To prevent handing out
nlmsg_write permissions to such domains, a new permission has been
added, nlmsg_relay, which is intended for this type of purpose: data is
passed via the kernel back to userspace but no privileged information is
written to the kernel.
Also, AUDIT_LOGIN messages are now valid only for kernel->user messaging,
so this value has been removed from the SELinux nlmsgtab (which is only
used to check user->kernel messages).
Signed-off-by: James Morris <jmorris@redhat.com>
Signed-off-by: Stephen Smalley <sds@tycho.nsa.gov>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
2005-05-01 15:58:40 +00:00
|
|
|
{ AUDIT_GET, NETLINK_AUDIT_SOCKET__NLMSG_READ },
|
|
|
|
{ AUDIT_SET, NETLINK_AUDIT_SOCKET__NLMSG_WRITE },
|
|
|
|
{ AUDIT_LIST, NETLINK_AUDIT_SOCKET__NLMSG_READPRIV },
|
|
|
|
{ AUDIT_ADD, NETLINK_AUDIT_SOCKET__NLMSG_WRITE },
|
|
|
|
{ AUDIT_DEL, NETLINK_AUDIT_SOCKET__NLMSG_WRITE },
|
2006-02-07 17:05:27 +00:00
|
|
|
{ AUDIT_LIST_RULES, NETLINK_AUDIT_SOCKET__NLMSG_READPRIV },
|
|
|
|
{ AUDIT_ADD_RULE, NETLINK_AUDIT_SOCKET__NLMSG_WRITE },
|
|
|
|
{ AUDIT_DEL_RULE, NETLINK_AUDIT_SOCKET__NLMSG_WRITE },
|
[PATCH] SELinux: add finer grained permissions to Netlink audit processing
This patch provides finer grained permissions for the audit family of
Netlink sockets under SELinux.
1. We need a way to differentiate between privileged and unprivileged
reads of kernel data maintained by the audit subsystem. The AUDIT_GET
operation is unprivileged: it returns the current status of the audit
subsystem (e.g. whether it's enabled etc.). The AUDIT_LIST operation
however returns a list of the current audit ruleset, which is considered
privileged by the audit folk. To deal with this, a new SELinux
permission has been implemented and applied to the operation:
nlmsg_readpriv, which can be allocated to appropriately privileged
domains. Unprivileged domains would only be allocated nlmsg_read.
2. There is a requirement for certain domains to generate audit events
from userspace. These events need to be collected by the kernel,
collated and transmitted sequentially back to the audit daemon. An
example is user level login, an auditable event under CAPP, where
login-related domains generate AUDIT_USER messages via PAM which are
relayed back to auditd via the kernel. To prevent handing out
nlmsg_write permissions to such domains, a new permission has been
added, nlmsg_relay, which is intended for this type of purpose: data is
passed via the kernel back to userspace but no privileged information is
written to the kernel.
Also, AUDIT_LOGIN messages are now valid only for kernel->user messaging,
so this value has been removed from the SELinux nlmsgtab (which is only
used to check user->kernel messages).
Signed-off-by: James Morris <jmorris@redhat.com>
Signed-off-by: Stephen Smalley <sds@tycho.nsa.gov>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
2005-05-01 15:58:40 +00:00
|
|
|
{ AUDIT_USER, NETLINK_AUDIT_SOCKET__NLMSG_RELAY },
|
2005-05-06 11:38:39 +00:00
|
|
|
{ AUDIT_SIGNAL_INFO, NETLINK_AUDIT_SOCKET__NLMSG_READ },
|
2009-06-02 21:01:16 +00:00
|
|
|
{ AUDIT_TRIM, NETLINK_AUDIT_SOCKET__NLMSG_WRITE },
|
|
|
|
{ AUDIT_MAKE_EQUIV, NETLINK_AUDIT_SOCKET__NLMSG_WRITE },
|
Audit: add TTY input auditing
Add TTY input auditing, used to audit system administrator's actions. This is
required by various security standards such as DCID 6/3 and PCI to provide
non-repudiation of administrator's actions and to allow a review of past
actions if the administrator seems to overstep their duties or if the system
becomes misconfigured for unknown reasons. These requirements do not make it
necessary to audit TTY output as well.
Compared to an user-space keylogger, this approach records TTY input using the
audit subsystem, correlated with other audit events, and it is completely
transparent to the user-space application (e.g. the console ioctls still
work).
TTY input auditing works on a higher level than auditing all system calls
within the session, which would produce an overwhelming amount of mostly
useless audit events.
Add an "audit_tty" attribute, inherited across fork (). Data read from TTYs
by process with the attribute is sent to the audit subsystem by the kernel.
The audit netlink interface is extended to allow modifying the audit_tty
attribute, and to allow sending explanatory audit events from user-space (for
example, a shell might send an event containing the final command, after the
interactive command-line editing and history expansion is performed, which
might be difficult to decipher from the TTY input alone).
Because the "audit_tty" attribute is inherited across fork (), it would be set
e.g. for sshd restarted within an audited session. To prevent this, the
audit_tty attribute is cleared when a process with no open TTY file
descriptors (e.g. after daemon startup) opens a TTY.
See https://www.redhat.com/archives/linux-audit/2007-June/msg00000.html for a
more detailed rationale document for an older version of this patch.
[akpm@linux-foundation.org: build fix]
Signed-off-by: Miloslav Trmac <mitr@redhat.com>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Cc: Alan Cox <alan@lxorguk.ukuu.org.uk>
Cc: Paul Fulghum <paulkf@microgate.com>
Cc: Casey Schaufler <casey@schaufler-ca.com>
Cc: Steve Grubb <sgrubb@redhat.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2007-07-16 06:40:56 +00:00
|
|
|
{ AUDIT_TTY_GET, NETLINK_AUDIT_SOCKET__NLMSG_READ },
|
2009-03-05 18:43:35 +00:00
|
|
|
{ AUDIT_TTY_SET, NETLINK_AUDIT_SOCKET__NLMSG_TTY_AUDIT },
|
2013-05-24 16:09:50 +00:00
|
|
|
{ AUDIT_GET_FEATURE, NETLINK_AUDIT_SOCKET__NLMSG_READ },
|
|
|
|
{ AUDIT_SET_FEATURE, NETLINK_AUDIT_SOCKET__NLMSG_WRITE },
|
2005-04-16 22:20:36 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
2017-03-06 16:58:08 +00:00
|
|
|
static int nlmsg_perm(u16 nlmsg_type, u32 *perm, const struct nlmsg_perm *tab, size_t tabsize)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
|
|
|
int i, err = -EINVAL;
|
|
|
|
|
|
|
|
for (i = 0; i < tabsize/sizeof(struct nlmsg_perm); i++)
|
|
|
|
if (nlmsg_type == tab[i].nlmsg_type) {
|
|
|
|
*perm = tab[i].perm;
|
|
|
|
err = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
int selinux_nlmsg_lookup(u16 sclass, u16 nlmsg_type, u32 *perm)
|
|
|
|
{
|
|
|
|
int err = 0;
|
|
|
|
|
|
|
|
switch (sclass) {
|
|
|
|
case SECCLASS_NETLINK_ROUTE_SOCKET:
|
2015-04-13 13:20:37 +00:00
|
|
|
/* RTM_MAX always point to RTM_SETxxxx, ie RTM_NEWxxx + 3 */
|
2016-04-20 15:43:43 +00:00
|
|
|
BUILD_BUG_ON(RTM_MAX != (RTM_NEWSTATS + 3));
|
2005-04-16 22:20:36 +00:00
|
|
|
err = nlmsg_perm(nlmsg_type, perm, nlmsg_route_perms,
|
|
|
|
sizeof(nlmsg_route_perms));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SECCLASS_NETLINK_TCPDIAG_SOCKET:
|
|
|
|
err = nlmsg_perm(nlmsg_type, perm, nlmsg_tcpdiag_perms,
|
|
|
|
sizeof(nlmsg_tcpdiag_perms));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SECCLASS_NETLINK_XFRM_SOCKET:
|
2015-04-13 13:20:37 +00:00
|
|
|
BUILD_BUG_ON(XFRM_MSG_MAX != XFRM_MSG_MAPPING);
|
2005-04-16 22:20:36 +00:00
|
|
|
err = nlmsg_perm(nlmsg_type, perm, nlmsg_xfrm_perms,
|
|
|
|
sizeof(nlmsg_xfrm_perms));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SECCLASS_NETLINK_AUDIT_SOCKET:
|
2005-11-03 15:48:08 +00:00
|
|
|
if ((nlmsg_type >= AUDIT_FIRST_USER_MSG &&
|
|
|
|
nlmsg_type <= AUDIT_LAST_USER_MSG) ||
|
|
|
|
(nlmsg_type >= AUDIT_FIRST_USER_MSG2 &&
|
2008-04-18 21:38:26 +00:00
|
|
|
nlmsg_type <= AUDIT_LAST_USER_MSG2)) {
|
2005-05-18 09:21:07 +00:00
|
|
|
*perm = NETLINK_AUDIT_SOCKET__NLMSG_RELAY;
|
|
|
|
} else {
|
|
|
|
err = nlmsg_perm(nlmsg_type, perm, nlmsg_audit_perms,
|
|
|
|
sizeof(nlmsg_audit_perms));
|
|
|
|
}
|
2005-04-16 22:20:36 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
/* No messaging from userspace, or class unknown/unhandled */
|
|
|
|
default:
|
|
|
|
err = -ENOENT;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return err;
|
|
|
|
}
|