lsm: make security_socket_getpeersec_stream() sockptr_t safe
[ Upstream commit b10b9c342f7571f287fd422be5d5c0beb26ba974 ] Commit 4ff09db1b79b ("bpf: net: Change sk_getsockopt() to take the sockptr_t argument") made it possible to call sk_getsockopt() with both user and kernel address space buffers through the use of the sockptr_t type. Unfortunately at the time of conversion the security_socket_getpeersec_stream() LSM hook was written to only accept userspace buffers, and in a desire to avoid having to change the LSM hook the commit author simply passed the sockptr_t's userspace buffer pointer. Since the only sk_getsockopt() callers at the time of conversion which used kernel sockptr_t buffers did not allow SO_PEERSEC, and hence the security_socket_getpeersec_stream() hook, this was acceptable but also very fragile as future changes presented the possibility of silently passing kernel space pointers to the LSM hook. There are several ways to protect against this, including careful code review of future commits, but since relying on code review to catch bugs is a recipe for disaster and the upstream eBPF maintainer is "strongly against defensive programming", this patch updates the LSM hook, and all of the implementations to support sockptr_t and safely handle both user and kernel space buffers. Acked-by: Casey Schaufler <casey@schaufler-ca.com> Acked-by: John Johansen <john.johansen@canonical.com> Signed-off-by: Paul Moore <paul@paul-moore.com> Stable-dep-of: 5a287d3d2b9d ("lsm: fix default return value of the socket_getpeersec_*() hooks") Signed-off-by: Sasha Levin <sashal@kernel.org>
This commit is contained in:
@@ -294,7 +294,7 @@ LSM_HOOK(int, 0, socket_setsockopt, struct socket *sock, int level, int optname)
|
|||||||
LSM_HOOK(int, 0, socket_shutdown, struct socket *sock, int how)
|
LSM_HOOK(int, 0, socket_shutdown, struct socket *sock, int how)
|
||||||
LSM_HOOK(int, 0, socket_sock_rcv_skb, struct sock *sk, struct sk_buff *skb)
|
LSM_HOOK(int, 0, socket_sock_rcv_skb, struct sock *sk, struct sk_buff *skb)
|
||||||
LSM_HOOK(int, 0, socket_getpeersec_stream, struct socket *sock,
|
LSM_HOOK(int, 0, socket_getpeersec_stream, struct socket *sock,
|
||||||
char __user *optval, int __user *optlen, unsigned len)
|
sockptr_t optval, sockptr_t optlen, unsigned int len)
|
||||||
LSM_HOOK(int, 0, socket_getpeersec_dgram, struct socket *sock,
|
LSM_HOOK(int, 0, socket_getpeersec_dgram, struct socket *sock,
|
||||||
struct sk_buff *skb, u32 *secid)
|
struct sk_buff *skb, u32 *secid)
|
||||||
LSM_HOOK(int, 0, sk_alloc_security, struct sock *sk, int family, gfp_t priority)
|
LSM_HOOK(int, 0, sk_alloc_security, struct sock *sk, int family, gfp_t priority)
|
||||||
|
@@ -926,8 +926,8 @@
|
|||||||
* SO_GETPEERSEC. For tcp sockets this can be meaningful if the
|
* SO_GETPEERSEC. For tcp sockets this can be meaningful if the
|
||||||
* socket is associated with an ipsec SA.
|
* socket is associated with an ipsec SA.
|
||||||
* @sock is the local socket.
|
* @sock is the local socket.
|
||||||
* @optval userspace memory where the security state is to be copied.
|
* @optval memory where the security state is to be copied.
|
||||||
* @optlen userspace int where the module should copy the actual length
|
* @optlen memory where the module should copy the actual length
|
||||||
* of the security state.
|
* of the security state.
|
||||||
* @len as input is the maximum length to copy to userspace provided
|
* @len as input is the maximum length to copy to userspace provided
|
||||||
* by the caller.
|
* by the caller.
|
||||||
|
@@ -31,6 +31,7 @@
|
|||||||
#include <linux/err.h>
|
#include <linux/err.h>
|
||||||
#include <linux/string.h>
|
#include <linux/string.h>
|
||||||
#include <linux/mm.h>
|
#include <linux/mm.h>
|
||||||
|
#include <linux/sockptr.h>
|
||||||
|
|
||||||
struct linux_binprm;
|
struct linux_binprm;
|
||||||
struct cred;
|
struct cred;
|
||||||
@@ -1366,8 +1367,8 @@ int security_socket_getsockopt(struct socket *sock, int level, int optname);
|
|||||||
int security_socket_setsockopt(struct socket *sock, int level, int optname);
|
int security_socket_setsockopt(struct socket *sock, int level, int optname);
|
||||||
int security_socket_shutdown(struct socket *sock, int how);
|
int security_socket_shutdown(struct socket *sock, int how);
|
||||||
int security_sock_rcv_skb(struct sock *sk, struct sk_buff *skb);
|
int security_sock_rcv_skb(struct sock *sk, struct sk_buff *skb);
|
||||||
int security_socket_getpeersec_stream(struct socket *sock, char __user *optval,
|
int security_socket_getpeersec_stream(struct socket *sock, sockptr_t optval,
|
||||||
int __user *optlen, unsigned len);
|
sockptr_t optlen, unsigned int len);
|
||||||
int security_socket_getpeersec_dgram(struct socket *sock, struct sk_buff *skb, u32 *secid);
|
int security_socket_getpeersec_dgram(struct socket *sock, struct sk_buff *skb, u32 *secid);
|
||||||
int security_sk_alloc(struct sock *sk, int family, gfp_t priority);
|
int security_sk_alloc(struct sock *sk, int family, gfp_t priority);
|
||||||
void security_sk_free(struct sock *sk);
|
void security_sk_free(struct sock *sk);
|
||||||
@@ -1501,8 +1502,10 @@ static inline int security_sock_rcv_skb(struct sock *sk,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline int security_socket_getpeersec_stream(struct socket *sock, char __user *optval,
|
static inline int security_socket_getpeersec_stream(struct socket *sock,
|
||||||
int __user *optlen, unsigned len)
|
sockptr_t optval,
|
||||||
|
sockptr_t optlen,
|
||||||
|
unsigned int len)
|
||||||
{
|
{
|
||||||
return -ENOPROTOOPT;
|
return -ENOPROTOOPT;
|
||||||
}
|
}
|
||||||
|
@@ -1503,7 +1503,8 @@ static int sk_getsockopt(struct sock *sk, int level, int optname,
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case SO_PEERSEC:
|
case SO_PEERSEC:
|
||||||
return security_socket_getpeersec_stream(sock, optval.user, optlen.user, len);
|
return security_socket_getpeersec_stream(sock,
|
||||||
|
optval, optlen, len);
|
||||||
|
|
||||||
case SO_MARK:
|
case SO_MARK:
|
||||||
v.val = sk->sk_mark;
|
v.val = sk->sk_mark;
|
||||||
|
@@ -1070,11 +1070,10 @@ static struct aa_label *sk_peer_label(struct sock *sk)
|
|||||||
* Note: for tcp only valid if using ipsec or cipso on lan
|
* Note: for tcp only valid if using ipsec or cipso on lan
|
||||||
*/
|
*/
|
||||||
static int apparmor_socket_getpeersec_stream(struct socket *sock,
|
static int apparmor_socket_getpeersec_stream(struct socket *sock,
|
||||||
char __user *optval,
|
sockptr_t optval, sockptr_t optlen,
|
||||||
int __user *optlen,
|
|
||||||
unsigned int len)
|
unsigned int len)
|
||||||
{
|
{
|
||||||
char *name;
|
char *name = NULL;
|
||||||
int slen, error = 0;
|
int slen, error = 0;
|
||||||
struct aa_label *label;
|
struct aa_label *label;
|
||||||
struct aa_label *peer;
|
struct aa_label *peer;
|
||||||
@@ -1091,23 +1090,21 @@ static int apparmor_socket_getpeersec_stream(struct socket *sock,
|
|||||||
/* don't include terminating \0 in slen, it breaks some apps */
|
/* don't include terminating \0 in slen, it breaks some apps */
|
||||||
if (slen < 0) {
|
if (slen < 0) {
|
||||||
error = -ENOMEM;
|
error = -ENOMEM;
|
||||||
} else {
|
goto done;
|
||||||
|
}
|
||||||
if (slen > len) {
|
if (slen > len) {
|
||||||
error = -ERANGE;
|
error = -ERANGE;
|
||||||
} else if (copy_to_user(optval, name, slen)) {
|
goto done_len;
|
||||||
error = -EFAULT;
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
if (put_user(slen, optlen))
|
|
||||||
error = -EFAULT;
|
|
||||||
out:
|
|
||||||
kfree(name);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (copy_to_sockptr(optval, name, slen))
|
||||||
|
error = -EFAULT;
|
||||||
|
done_len:
|
||||||
|
if (copy_to_sockptr(optlen, &slen, sizeof(slen)))
|
||||||
|
error = -EFAULT;
|
||||||
done:
|
done:
|
||||||
end_current_label_crit_section(label);
|
end_current_label_crit_section(label);
|
||||||
|
kfree(name);
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -2224,8 +2224,8 @@ int security_sock_rcv_skb(struct sock *sk, struct sk_buff *skb)
|
|||||||
}
|
}
|
||||||
EXPORT_SYMBOL(security_sock_rcv_skb);
|
EXPORT_SYMBOL(security_sock_rcv_skb);
|
||||||
|
|
||||||
int security_socket_getpeersec_stream(struct socket *sock, char __user *optval,
|
int security_socket_getpeersec_stream(struct socket *sock, sockptr_t optval,
|
||||||
int __user *optlen, unsigned len)
|
sockptr_t optlen, unsigned int len)
|
||||||
{
|
{
|
||||||
return call_int_hook(socket_getpeersec_stream, -ENOPROTOOPT, sock,
|
return call_int_hook(socket_getpeersec_stream, -ENOPROTOOPT, sock,
|
||||||
optval, optlen, len);
|
optval, optlen, len);
|
||||||
|
@@ -5110,11 +5110,12 @@ static int selinux_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb)
|
|||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int selinux_socket_getpeersec_stream(struct socket *sock, char __user *optval,
|
static int selinux_socket_getpeersec_stream(struct socket *sock,
|
||||||
int __user *optlen, unsigned len)
|
sockptr_t optval, sockptr_t optlen,
|
||||||
|
unsigned int len)
|
||||||
{
|
{
|
||||||
int err = 0;
|
int err = 0;
|
||||||
char *scontext;
|
char *scontext = NULL;
|
||||||
u32 scontext_len;
|
u32 scontext_len;
|
||||||
struct sk_security_struct *sksec = sock->sk->sk_security;
|
struct sk_security_struct *sksec = sock->sk->sk_security;
|
||||||
u32 peer_sid = SECSID_NULL;
|
u32 peer_sid = SECSID_NULL;
|
||||||
@@ -5130,17 +5131,15 @@ static int selinux_socket_getpeersec_stream(struct socket *sock, char __user *op
|
|||||||
&scontext_len);
|
&scontext_len);
|
||||||
if (err)
|
if (err)
|
||||||
return err;
|
return err;
|
||||||
|
|
||||||
if (scontext_len > len) {
|
if (scontext_len > len) {
|
||||||
err = -ERANGE;
|
err = -ERANGE;
|
||||||
goto out_len;
|
goto out_len;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (copy_to_user(optval, scontext, scontext_len))
|
if (copy_to_sockptr(optval, scontext, scontext_len))
|
||||||
err = -EFAULT;
|
err = -EFAULT;
|
||||||
|
|
||||||
out_len:
|
out_len:
|
||||||
if (put_user(scontext_len, optlen))
|
if (copy_to_sockptr(optlen, &scontext_len, sizeof(scontext_len)))
|
||||||
err = -EFAULT;
|
err = -EFAULT;
|
||||||
kfree(scontext);
|
kfree(scontext);
|
||||||
return err;
|
return err;
|
||||||
|
@@ -4022,12 +4022,12 @@ static int smack_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb)
|
|||||||
* returns zero on success, an error code otherwise
|
* returns zero on success, an error code otherwise
|
||||||
*/
|
*/
|
||||||
static int smack_socket_getpeersec_stream(struct socket *sock,
|
static int smack_socket_getpeersec_stream(struct socket *sock,
|
||||||
char __user *optval,
|
sockptr_t optval, sockptr_t optlen,
|
||||||
int __user *optlen, unsigned len)
|
unsigned int len)
|
||||||
{
|
{
|
||||||
struct socket_smack *ssp;
|
struct socket_smack *ssp;
|
||||||
char *rcp = "";
|
char *rcp = "";
|
||||||
int slen = 1;
|
u32 slen = 1;
|
||||||
int rc = 0;
|
int rc = 0;
|
||||||
|
|
||||||
ssp = sock->sk->sk_security;
|
ssp = sock->sk->sk_security;
|
||||||
@@ -4035,15 +4035,16 @@ static int smack_socket_getpeersec_stream(struct socket *sock,
|
|||||||
rcp = ssp->smk_packet->smk_known;
|
rcp = ssp->smk_packet->smk_known;
|
||||||
slen = strlen(rcp) + 1;
|
slen = strlen(rcp) + 1;
|
||||||
}
|
}
|
||||||
|
if (slen > len) {
|
||||||
if (slen > len)
|
|
||||||
rc = -ERANGE;
|
rc = -ERANGE;
|
||||||
else if (copy_to_user(optval, rcp, slen) != 0)
|
goto out_len;
|
||||||
rc = -EFAULT;
|
}
|
||||||
|
|
||||||
if (put_user(slen, optlen) != 0)
|
if (copy_to_sockptr(optval, rcp, slen))
|
||||||
|
rc = -EFAULT;
|
||||||
|
out_len:
|
||||||
|
if (copy_to_sockptr(optlen, &slen, sizeof(slen)))
|
||||||
rc = -EFAULT;
|
rc = -EFAULT;
|
||||||
|
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user