mirror of
https://github.com/FEX-Emu/linux.git
synced 2024-12-26 19:36:41 +00:00
42d64e1add
The SELinux/NetLabel glue code has a locking bug that affects systems with NetLabel enabled, see the kernel error message below. This patch corrects this problem by converting the bottom half socket lock to a more conventional, and correct for this call-path, lock_sock() call. =============================== [ INFO: suspicious RCU usage. ] 3.11.0-rc3+ #19 Not tainted ------------------------------- net/ipv4/cipso_ipv4.c:1928 suspicious rcu_dereference_protected() usage! other info that might help us debug this: rcu_scheduler_active = 1, debug_locks = 0 2 locks held by ping/731: #0: (slock-AF_INET/1){+.-...}, at: [...] selinux_netlbl_socket_connect #1: (rcu_read_lock){.+.+..}, at: [<...>] netlbl_conn_setattr stack backtrace: CPU: 1 PID: 731 Comm: ping Not tainted 3.11.0-rc3+ #19 Hardware name: Bochs Bochs, BIOS Bochs 01/01/2011 0000000000000001 ffff88006f659d28 ffffffff81726b6a ffff88003732c500 ffff88006f659d58 ffffffff810e4457 ffff88006b845a00 0000000000000000 000000000000000c ffff880075aa2f50 ffff88006f659d90 ffffffff8169bec7 Call Trace: [<ffffffff81726b6a>] dump_stack+0x54/0x74 [<ffffffff810e4457>] lockdep_rcu_suspicious+0xe7/0x120 [<ffffffff8169bec7>] cipso_v4_sock_setattr+0x187/0x1a0 [<ffffffff8170f317>] netlbl_conn_setattr+0x187/0x190 [<ffffffff8170f195>] ? netlbl_conn_setattr+0x5/0x190 [<ffffffff8131ac9e>] selinux_netlbl_socket_connect+0xae/0xc0 [<ffffffff81303025>] selinux_socket_connect+0x135/0x170 [<ffffffff8119d127>] ? might_fault+0x57/0xb0 [<ffffffff812fb146>] security_socket_connect+0x16/0x20 [<ffffffff815d3ad3>] SYSC_connect+0x73/0x130 [<ffffffff81739a85>] ? sysret_check+0x22/0x5d [<ffffffff810e5e2d>] ? trace_hardirqs_on_caller+0xfd/0x1c0 [<ffffffff81373d4e>] ? trace_hardirqs_on_thunk+0x3a/0x3f [<ffffffff815d52be>] SyS_connect+0xe/0x10 [<ffffffff81739a59>] system_call_fastpath+0x16/0x1b Cc: stable@vger.kernel.org Signed-off-by: Paul Moore <pmoore@redhat.com>
469 lines
12 KiB
C
469 lines
12 KiB
C
/*
|
|
* SELinux NetLabel Support
|
|
*
|
|
* This file provides the necessary glue to tie NetLabel into the SELinux
|
|
* subsystem.
|
|
*
|
|
* Author: Paul Moore <paul@paul-moore.com>
|
|
*
|
|
*/
|
|
|
|
/*
|
|
* (c) Copyright Hewlett-Packard Development Company, L.P., 2007, 2008
|
|
*
|
|
* 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
|
|
*
|
|
*/
|
|
|
|
#include <linux/spinlock.h>
|
|
#include <linux/rcupdate.h>
|
|
#include <linux/gfp.h>
|
|
#include <linux/ip.h>
|
|
#include <linux/ipv6.h>
|
|
#include <net/sock.h>
|
|
#include <net/netlabel.h>
|
|
#include <net/ip.h>
|
|
#include <net/ipv6.h>
|
|
|
|
#include "objsec.h"
|
|
#include "security.h"
|
|
#include "netlabel.h"
|
|
|
|
/**
|
|
* selinux_netlbl_sidlookup_cached - Cache a SID lookup
|
|
* @skb: the packet
|
|
* @secattr: the NetLabel security attributes
|
|
* @sid: the SID
|
|
*
|
|
* Description:
|
|
* Query the SELinux security server to lookup the correct SID for the given
|
|
* security attributes. If the query is successful, cache the result to speed
|
|
* up future lookups. Returns zero on success, negative values on failure.
|
|
*
|
|
*/
|
|
static int selinux_netlbl_sidlookup_cached(struct sk_buff *skb,
|
|
struct netlbl_lsm_secattr *secattr,
|
|
u32 *sid)
|
|
{
|
|
int rc;
|
|
|
|
rc = security_netlbl_secattr_to_sid(secattr, sid);
|
|
if (rc == 0 &&
|
|
(secattr->flags & NETLBL_SECATTR_CACHEABLE) &&
|
|
(secattr->flags & NETLBL_SECATTR_CACHE))
|
|
netlbl_cache_add(skb, secattr);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* selinux_netlbl_sock_genattr - Generate the NetLabel socket secattr
|
|
* @sk: the socket
|
|
*
|
|
* Description:
|
|
* Generate the NetLabel security attributes for a socket, making full use of
|
|
* the socket's attribute cache. Returns a pointer to the security attributes
|
|
* on success, NULL on failure.
|
|
*
|
|
*/
|
|
static struct netlbl_lsm_secattr *selinux_netlbl_sock_genattr(struct sock *sk)
|
|
{
|
|
int rc;
|
|
struct sk_security_struct *sksec = sk->sk_security;
|
|
struct netlbl_lsm_secattr *secattr;
|
|
|
|
if (sksec->nlbl_secattr != NULL)
|
|
return sksec->nlbl_secattr;
|
|
|
|
secattr = netlbl_secattr_alloc(GFP_ATOMIC);
|
|
if (secattr == NULL)
|
|
return NULL;
|
|
rc = security_netlbl_sid_to_secattr(sksec->sid, secattr);
|
|
if (rc != 0) {
|
|
netlbl_secattr_free(secattr);
|
|
return NULL;
|
|
}
|
|
sksec->nlbl_secattr = secattr;
|
|
|
|
return secattr;
|
|
}
|
|
|
|
/**
|
|
* selinux_netlbl_cache_invalidate - Invalidate the NetLabel cache
|
|
*
|
|
* Description:
|
|
* Invalidate the NetLabel security attribute mapping cache.
|
|
*
|
|
*/
|
|
void selinux_netlbl_cache_invalidate(void)
|
|
{
|
|
netlbl_cache_invalidate();
|
|
}
|
|
|
|
/**
|
|
* selinux_netlbl_err - Handle a NetLabel packet error
|
|
* @skb: the packet
|
|
* @error: the error code
|
|
* @gateway: true if host is acting as a gateway, false otherwise
|
|
*
|
|
* Description:
|
|
* When a packet is dropped due to a call to avc_has_perm() pass the error
|
|
* code to the NetLabel subsystem so any protocol specific processing can be
|
|
* done. This is safe to call even if you are unsure if NetLabel labeling is
|
|
* present on the packet, NetLabel is smart enough to only act when it should.
|
|
*
|
|
*/
|
|
void selinux_netlbl_err(struct sk_buff *skb, int error, int gateway)
|
|
{
|
|
netlbl_skbuff_err(skb, error, gateway);
|
|
}
|
|
|
|
/**
|
|
* selinux_netlbl_sk_security_free - Free the NetLabel fields
|
|
* @sksec: the sk_security_struct
|
|
*
|
|
* Description:
|
|
* Free all of the memory in the NetLabel fields of a sk_security_struct.
|
|
*
|
|
*/
|
|
void selinux_netlbl_sk_security_free(struct sk_security_struct *sksec)
|
|
{
|
|
if (sksec->nlbl_secattr != NULL)
|
|
netlbl_secattr_free(sksec->nlbl_secattr);
|
|
}
|
|
|
|
/**
|
|
* selinux_netlbl_sk_security_reset - Reset the NetLabel fields
|
|
* @sksec: the sk_security_struct
|
|
* @family: the socket family
|
|
*
|
|
* Description:
|
|
* Called when the NetLabel state of a sk_security_struct needs to be reset.
|
|
* The caller is responsible for all the NetLabel sk_security_struct locking.
|
|
*
|
|
*/
|
|
void selinux_netlbl_sk_security_reset(struct sk_security_struct *sksec)
|
|
{
|
|
sksec->nlbl_state = NLBL_UNSET;
|
|
}
|
|
|
|
/**
|
|
* selinux_netlbl_skbuff_getsid - Get the sid of a packet using NetLabel
|
|
* @skb: the packet
|
|
* @family: protocol family
|
|
* @type: NetLabel labeling protocol type
|
|
* @sid: the SID
|
|
*
|
|
* Description:
|
|
* Call the NetLabel mechanism to get the security attributes of the given
|
|
* packet and use those attributes to determine the correct context/SID to
|
|
* assign to the packet. Returns zero on success, negative values on failure.
|
|
*
|
|
*/
|
|
int selinux_netlbl_skbuff_getsid(struct sk_buff *skb,
|
|
u16 family,
|
|
u32 *type,
|
|
u32 *sid)
|
|
{
|
|
int rc;
|
|
struct netlbl_lsm_secattr secattr;
|
|
|
|
if (!netlbl_enabled()) {
|
|
*sid = SECSID_NULL;
|
|
return 0;
|
|
}
|
|
|
|
netlbl_secattr_init(&secattr);
|
|
rc = netlbl_skbuff_getattr(skb, family, &secattr);
|
|
if (rc == 0 && secattr.flags != NETLBL_SECATTR_NONE)
|
|
rc = selinux_netlbl_sidlookup_cached(skb, &secattr, sid);
|
|
else
|
|
*sid = SECSID_NULL;
|
|
*type = secattr.type;
|
|
netlbl_secattr_destroy(&secattr);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* selinux_netlbl_skbuff_setsid - Set the NetLabel on a packet given a sid
|
|
* @skb: the packet
|
|
* @family: protocol family
|
|
* @sid: the SID
|
|
*
|
|
* Description
|
|
* Call the NetLabel mechanism to set the label of a packet using @sid.
|
|
* Returns zero on success, negative values on failure.
|
|
*
|
|
*/
|
|
int selinux_netlbl_skbuff_setsid(struct sk_buff *skb,
|
|
u16 family,
|
|
u32 sid)
|
|
{
|
|
int rc;
|
|
struct netlbl_lsm_secattr secattr_storage;
|
|
struct netlbl_lsm_secattr *secattr = NULL;
|
|
struct sock *sk;
|
|
|
|
/* if this is a locally generated packet check to see if it is already
|
|
* being labeled by it's parent socket, if it is just exit */
|
|
sk = skb->sk;
|
|
if (sk != NULL) {
|
|
struct sk_security_struct *sksec = sk->sk_security;
|
|
if (sksec->nlbl_state != NLBL_REQSKB)
|
|
return 0;
|
|
secattr = sksec->nlbl_secattr;
|
|
}
|
|
if (secattr == NULL) {
|
|
secattr = &secattr_storage;
|
|
netlbl_secattr_init(secattr);
|
|
rc = security_netlbl_sid_to_secattr(sid, secattr);
|
|
if (rc != 0)
|
|
goto skbuff_setsid_return;
|
|
}
|
|
|
|
rc = netlbl_skbuff_setattr(skb, family, secattr);
|
|
|
|
skbuff_setsid_return:
|
|
if (secattr == &secattr_storage)
|
|
netlbl_secattr_destroy(secattr);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* selinux_netlbl_inet_conn_request - Label an incoming stream connection
|
|
* @req: incoming connection request socket
|
|
*
|
|
* Description:
|
|
* A new incoming connection request is represented by @req, we need to label
|
|
* the new request_sock here and the stack will ensure the on-the-wire label
|
|
* will get preserved when a full sock is created once the connection handshake
|
|
* is complete. Returns zero on success, negative values on failure.
|
|
*
|
|
*/
|
|
int selinux_netlbl_inet_conn_request(struct request_sock *req, u16 family)
|
|
{
|
|
int rc;
|
|
struct netlbl_lsm_secattr secattr;
|
|
|
|
if (family != PF_INET)
|
|
return 0;
|
|
|
|
netlbl_secattr_init(&secattr);
|
|
rc = security_netlbl_sid_to_secattr(req->secid, &secattr);
|
|
if (rc != 0)
|
|
goto inet_conn_request_return;
|
|
rc = netlbl_req_setattr(req, &secattr);
|
|
inet_conn_request_return:
|
|
netlbl_secattr_destroy(&secattr);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* selinux_netlbl_inet_csk_clone - Initialize the newly created sock
|
|
* @sk: the new sock
|
|
*
|
|
* Description:
|
|
* A new connection has been established using @sk, we've already labeled the
|
|
* socket via the request_sock struct in selinux_netlbl_inet_conn_request() but
|
|
* we need to set the NetLabel state here since we now have a sock structure.
|
|
*
|
|
*/
|
|
void selinux_netlbl_inet_csk_clone(struct sock *sk, u16 family)
|
|
{
|
|
struct sk_security_struct *sksec = sk->sk_security;
|
|
|
|
if (family == PF_INET)
|
|
sksec->nlbl_state = NLBL_LABELED;
|
|
else
|
|
sksec->nlbl_state = NLBL_UNSET;
|
|
}
|
|
|
|
/**
|
|
* selinux_netlbl_socket_post_create - Label a socket using NetLabel
|
|
* @sock: the socket to label
|
|
* @family: protocol family
|
|
*
|
|
* Description:
|
|
* Attempt to label a socket using the NetLabel mechanism using the given
|
|
* SID. Returns zero values on success, negative values on failure.
|
|
*
|
|
*/
|
|
int selinux_netlbl_socket_post_create(struct sock *sk, u16 family)
|
|
{
|
|
int rc;
|
|
struct sk_security_struct *sksec = sk->sk_security;
|
|
struct netlbl_lsm_secattr *secattr;
|
|
|
|
if (family != PF_INET)
|
|
return 0;
|
|
|
|
secattr = selinux_netlbl_sock_genattr(sk);
|
|
if (secattr == NULL)
|
|
return -ENOMEM;
|
|
rc = netlbl_sock_setattr(sk, family, secattr);
|
|
switch (rc) {
|
|
case 0:
|
|
sksec->nlbl_state = NLBL_LABELED;
|
|
break;
|
|
case -EDESTADDRREQ:
|
|
sksec->nlbl_state = NLBL_REQSKB;
|
|
rc = 0;
|
|
break;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* selinux_netlbl_sock_rcv_skb - Do an inbound access check using NetLabel
|
|
* @sksec: the sock's sk_security_struct
|
|
* @skb: the packet
|
|
* @family: protocol family
|
|
* @ad: the audit data
|
|
*
|
|
* Description:
|
|
* Fetch the NetLabel security attributes from @skb and perform an access check
|
|
* against the receiving socket. Returns zero on success, negative values on
|
|
* error.
|
|
*
|
|
*/
|
|
int selinux_netlbl_sock_rcv_skb(struct sk_security_struct *sksec,
|
|
struct sk_buff *skb,
|
|
u16 family,
|
|
struct common_audit_data *ad)
|
|
{
|
|
int rc;
|
|
u32 nlbl_sid;
|
|
u32 perm;
|
|
struct netlbl_lsm_secattr secattr;
|
|
|
|
if (!netlbl_enabled())
|
|
return 0;
|
|
|
|
netlbl_secattr_init(&secattr);
|
|
rc = netlbl_skbuff_getattr(skb, family, &secattr);
|
|
if (rc == 0 && secattr.flags != NETLBL_SECATTR_NONE)
|
|
rc = selinux_netlbl_sidlookup_cached(skb, &secattr, &nlbl_sid);
|
|
else
|
|
nlbl_sid = SECINITSID_UNLABELED;
|
|
netlbl_secattr_destroy(&secattr);
|
|
if (rc != 0)
|
|
return rc;
|
|
|
|
switch (sksec->sclass) {
|
|
case SECCLASS_UDP_SOCKET:
|
|
perm = UDP_SOCKET__RECVFROM;
|
|
break;
|
|
case SECCLASS_TCP_SOCKET:
|
|
perm = TCP_SOCKET__RECVFROM;
|
|
break;
|
|
default:
|
|
perm = RAWIP_SOCKET__RECVFROM;
|
|
}
|
|
|
|
rc = avc_has_perm(sksec->sid, nlbl_sid, sksec->sclass, perm, ad);
|
|
if (rc == 0)
|
|
return 0;
|
|
|
|
if (nlbl_sid != SECINITSID_UNLABELED)
|
|
netlbl_skbuff_err(skb, rc, 0);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* selinux_netlbl_socket_setsockopt - Do not allow users to remove a NetLabel
|
|
* @sock: the socket
|
|
* @level: the socket level or protocol
|
|
* @optname: the socket option name
|
|
*
|
|
* Description:
|
|
* Check the setsockopt() call and if the user is trying to replace the IP
|
|
* options on a socket and a NetLabel is in place for the socket deny the
|
|
* access; otherwise allow the access. Returns zero when the access is
|
|
* allowed, -EACCES when denied, and other negative values on error.
|
|
*
|
|
*/
|
|
int selinux_netlbl_socket_setsockopt(struct socket *sock,
|
|
int level,
|
|
int optname)
|
|
{
|
|
int rc = 0;
|
|
struct sock *sk = sock->sk;
|
|
struct sk_security_struct *sksec = sk->sk_security;
|
|
struct netlbl_lsm_secattr secattr;
|
|
|
|
if (level == IPPROTO_IP && optname == IP_OPTIONS &&
|
|
(sksec->nlbl_state == NLBL_LABELED ||
|
|
sksec->nlbl_state == NLBL_CONNLABELED)) {
|
|
netlbl_secattr_init(&secattr);
|
|
lock_sock(sk);
|
|
rc = netlbl_sock_getattr(sk, &secattr);
|
|
release_sock(sk);
|
|
if (rc == 0)
|
|
rc = -EACCES;
|
|
else if (rc == -ENOMSG)
|
|
rc = 0;
|
|
netlbl_secattr_destroy(&secattr);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* selinux_netlbl_socket_connect - Label a client-side socket on connect
|
|
* @sk: the socket to label
|
|
* @addr: the destination address
|
|
*
|
|
* Description:
|
|
* Attempt to label a connected socket with NetLabel using the given address.
|
|
* Returns zero values on success, negative values on failure.
|
|
*
|
|
*/
|
|
int selinux_netlbl_socket_connect(struct sock *sk, struct sockaddr *addr)
|
|
{
|
|
int rc;
|
|
struct sk_security_struct *sksec = sk->sk_security;
|
|
struct netlbl_lsm_secattr *secattr;
|
|
|
|
if (sksec->nlbl_state != NLBL_REQSKB &&
|
|
sksec->nlbl_state != NLBL_CONNLABELED)
|
|
return 0;
|
|
|
|
lock_sock(sk);
|
|
|
|
/* connected sockets are allowed to disconnect when the address family
|
|
* is set to AF_UNSPEC, if that is what is happening we want to reset
|
|
* the socket */
|
|
if (addr->sa_family == AF_UNSPEC) {
|
|
netlbl_sock_delattr(sk);
|
|
sksec->nlbl_state = NLBL_REQSKB;
|
|
rc = 0;
|
|
goto socket_connect_return;
|
|
}
|
|
secattr = selinux_netlbl_sock_genattr(sk);
|
|
if (secattr == NULL) {
|
|
rc = -ENOMEM;
|
|
goto socket_connect_return;
|
|
}
|
|
rc = netlbl_conn_setattr(sk, addr, secattr);
|
|
if (rc == 0)
|
|
sksec->nlbl_state = NLBL_CONNLABELED;
|
|
|
|
socket_connect_return:
|
|
release_sock(sk);
|
|
return rc;
|
|
}
|