[PATCH] lsm: make security_socket_getpeersec_stream() sockptr_t safe
Casey Schaufler
casey at schaufler-ca.com
Thu Oct 20 13:16:25 UTC 2022
On 10/10/2022 2:58 PM, Paul Moore wrote:
> 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.
>
> Signed-off-by: Paul Moore <paul at paul-moore.com>
Smack part looks ok, I haven't had the opportunity to test it.
Will do so as I crunch through the backlog.
Acked-by: Casey Schaufler <casey at schaufler-ca.com>
> ---
> include/linux/lsm_hook_defs.h | 2 +-
> include/linux/lsm_hooks.h | 4 ++--
> include/linux/security.h | 11 +++++++----
> net/core/sock.c | 3 ++-
> security/apparmor/lsm.c | 29 +++++++++++++----------------
> security/security.c | 6 +++---
> security/selinux/hooks.c | 13 ++++++-------
> security/smack/smack_lsm.c | 19 ++++++++++---------
> 8 files changed, 44 insertions(+), 43 deletions(-)
>
> diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
> index ec119da1d89b4..6abde829b6e5e 100644
> --- a/include/linux/lsm_hook_defs.h
> +++ b/include/linux/lsm_hook_defs.h
> @@ -302,7 +302,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_sock_rcv_skb, struct sock *sk, struct sk_buff *skb)
> 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,
> struct sk_buff *skb, u32 *secid)
> LSM_HOOK(int, 0, sk_alloc_security, struct sock *sk, int family, gfp_t priority)
> diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h
> index 4ec80b96c22e7..883f0f252f062 100644
> --- a/include/linux/lsm_hooks.h
> +++ b/include/linux/lsm_hooks.h
> @@ -962,8 +962,8 @@
> * SO_GETPEERSEC. For tcp sockets this can be meaningful if the
> * socket is associated with an ipsec SA.
> * @sock is the local socket.
> - * @optval userspace memory where the security state is to be copied.
> - * @optlen userspace int where the module should copy the actual length
> + * @optval memory where the security state is to be copied.
> + * @optlen memory where the module should copy the actual length
> * of the security state.
> * @len as input is the maximum length to copy to userspace provided
> * by the caller.
> diff --git a/include/linux/security.h b/include/linux/security.h
> index ca1b7109c0dbb..0e419c595cee5 100644
> --- a/include/linux/security.h
> +++ b/include/linux/security.h
> @@ -31,6 +31,7 @@
> #include <linux/err.h>
> #include <linux/string.h>
> #include <linux/mm.h>
> +#include <linux/sockptr.h>
>
> struct linux_binprm;
> struct cred;
> @@ -1411,8 +1412,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_shutdown(struct socket *sock, int how);
> int security_sock_rcv_skb(struct sock *sk, struct sk_buff *skb);
> -int security_socket_getpeersec_stream(struct socket *sock, char __user *optval,
> - int __user *optlen, unsigned len);
> +int security_socket_getpeersec_stream(struct socket *sock, sockptr_t optval,
> + sockptr_t optlen, unsigned int len);
> 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);
> void security_sk_free(struct sock *sk);
> @@ -1548,8 +1549,10 @@ static inline int security_sock_rcv_skb(struct sock *sk,
> return 0;
> }
>
> -static inline int security_socket_getpeersec_stream(struct socket *sock, char __user *optval,
> - int __user *optlen, unsigned len)
> +static inline int security_socket_getpeersec_stream(struct socket *sock,
> + sockptr_t optval,
> + sockptr_t optlen,
> + unsigned int len)
> {
> return -ENOPROTOOPT;
> }
> diff --git a/net/core/sock.c b/net/core/sock.c
> index eeb6cbac6f499..70064415349d6 100644
> --- a/net/core/sock.c
> +++ b/net/core/sock.c
> @@ -1793,7 +1793,8 @@ int sk_getsockopt(struct sock *sk, int level, int optname,
> break;
>
> 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:
> v.val = sk->sk_mark;
> diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c
> index f56070270c69d..89e84ef54e8eb 100644
> --- a/security/apparmor/lsm.c
> +++ b/security/apparmor/lsm.c
> @@ -1103,11 +1103,10 @@ static struct aa_label *sk_peer_label(struct sock *sk)
> * Note: for tcp only valid if using ipsec or cipso on lan
> */
> static int apparmor_socket_getpeersec_stream(struct socket *sock,
> - char __user *optval,
> - int __user *optlen,
> + sockptr_t optval, sockptr_t optlen,
> unsigned int len)
> {
> - char *name;
> + char *name = NULL;
> int slen, error = 0;
> struct aa_label *label;
> struct aa_label *peer;
> @@ -1124,23 +1123,21 @@ static int apparmor_socket_getpeersec_stream(struct socket *sock,
> /* don't include terminating \0 in slen, it breaks some apps */
> if (slen < 0) {
> error = -ENOMEM;
> - } else {
> - if (slen > len) {
> - error = -ERANGE;
> - } else if (copy_to_user(optval, name, slen)) {
> - error = -EFAULT;
> - goto out;
> - }
> - if (put_user(slen, optlen))
> - error = -EFAULT;
> -out:
> - kfree(name);
> -
> + goto done;
> + }
> + if (slen > len) {
> + error = -ERANGE;
> + goto done_len;
> }
>
> + if (copy_to_sockptr(optval, name, slen))
> + error = -EFAULT;
> +done_len:
> + if (copy_to_sockptr(optlen, &slen, sizeof(slen)))
> + error = -EFAULT;
> done:
> end_current_label_crit_section(label);
> -
> + kfree(name);
> return error;
> }
>
> diff --git a/security/security.c b/security/security.c
> index 79d82cb6e4696..f27c885ee98db 100644
> --- a/security/security.c
> +++ b/security/security.c
> @@ -2267,11 +2267,11 @@ int security_sock_rcv_skb(struct sock *sk, struct sk_buff *skb)
> }
> EXPORT_SYMBOL(security_sock_rcv_skb);
>
> -int security_socket_getpeersec_stream(struct socket *sock, char __user *optval,
> - int __user *optlen, unsigned len)
> +int security_socket_getpeersec_stream(struct socket *sock, sockptr_t optval,
> + sockptr_t optlen, unsigned int len)
> {
> return call_int_hook(socket_getpeersec_stream, -ENOPROTOOPT, sock,
> - optval, optlen, len);
> + optval, optlen, len);
> }
>
> int security_socket_getpeersec_dgram(struct socket *sock, struct sk_buff *skb, u32 *secid)
> diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
> index f553c370397ee..0bdddeba90a6c 100644
> --- a/security/selinux/hooks.c
> +++ b/security/selinux/hooks.c
> @@ -5119,11 +5119,12 @@ static int selinux_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb)
> return err;
> }
>
> -static int selinux_socket_getpeersec_stream(struct socket *sock, char __user *optval,
> - int __user *optlen, unsigned len)
> +static int selinux_socket_getpeersec_stream(struct socket *sock,
> + sockptr_t optval, sockptr_t optlen,
> + unsigned int len)
> {
> int err = 0;
> - char *scontext;
> + char *scontext = NULL;
> u32 scontext_len;
> struct sk_security_struct *sksec = sock->sk->sk_security;
> u32 peer_sid = SECSID_NULL;
> @@ -5139,17 +5140,15 @@ static int selinux_socket_getpeersec_stream(struct socket *sock, char __user *op
> &scontext_len);
> if (err)
> return err;
> -
> if (scontext_len > len) {
> err = -ERANGE;
> goto out_len;
> }
>
> - if (copy_to_user(optval, scontext, scontext_len))
> + if (copy_to_sockptr(optval, scontext, scontext_len))
> err = -EFAULT;
> -
> out_len:
> - if (put_user(scontext_len, optlen))
> + if (copy_to_sockptr(optlen, &scontext_len, sizeof(scontext_len)))
> err = -EFAULT;
> kfree(scontext);
> return err;
> diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c
> index b6306d71c9088..2bd7fadf7fb4c 100644
> --- a/security/smack/smack_lsm.c
> +++ b/security/smack/smack_lsm.c
> @@ -4006,12 +4006,12 @@ static int smack_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb)
> * returns zero on success, an error code otherwise
> */
> static int smack_socket_getpeersec_stream(struct socket *sock,
> - char __user *optval,
> - int __user *optlen, unsigned len)
> + sockptr_t optval, sockptr_t optlen,
> + unsigned int len)
> {
> struct socket_smack *ssp;
> char *rcp = "";
> - int slen = 1;
> + u32 slen = 1;
> int rc = 0;
>
> ssp = sock->sk->sk_security;
> @@ -4019,15 +4019,16 @@ static int smack_socket_getpeersec_stream(struct socket *sock,
> rcp = ssp->smk_packet->smk_known;
> slen = strlen(rcp) + 1;
> }
> -
> - if (slen > len)
> + if (slen > len) {
> rc = -ERANGE;
> - else if (copy_to_user(optval, rcp, slen) != 0)
> - rc = -EFAULT;
> + goto out_len;
> + }
>
> - 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;
> -
> return rc;
> }
>
>
More information about the Linux-security-module-archive
mailing list