On 09/19/2018 05:18 PM, Sean Tranchetti wrote:
> pfkey_broadcast() can make calls to pfkey_broadcast_one() which
> will clone or copy the passed in SKB. This new SKB will be assigned
> the sock_rfree() function as its destructor, which requires that
> the socket reference the SKB contains is valid when the SKB is freed.
>
> If this SKB is ever passed to pfkey_broadcast() again by some other
> function (such as pkfey_dump() or pfkey_promisc) it will then be
> freed there. However, since this free occurs outside of RCU protection,
> it is possible that userspace could close the socket and trigger
> pfkey_release() to free the socket before sock_rfree() can run, creating
> the following race condition:
>
> 1: An SKB belonging to the pfkey socket is passed to pfkey_broadcast().
> It performs the broadcast to any other sockets, and calls
> rcu_read_unlock(), but does not yet reach kfree_skb().
> 2: Userspace closes the socket, triggering pfkey_realse(). Since no one
> holds the RCU lock, synchronize_rcu() returns and it is allowed to
> continue. It calls sock_put() to free the socket.
> 3: pfkey_broadcast() now calls kfree_skb() on the original SKB it was
> passed, triggering a call to sock_rfree(). This function now accesses
> the freed struct sock * via skb->sk, and attempts to update invalid
> memory.
>
> By ensuring that the pfkey_broadcast() also frees the SKBs while it holds
> the RCU lock, we can ensure that the socket will remain valid when the SKB
> is freed, avoiding crashes like the following:
>
> Unable to handle kernel paging request at virtual address 6b6b6b6b6b6c4b
> [006b6b6b6b6b6c4b] address between user and kernel address ranges
> Internal error: Oops: 96000004 [#1] PREEMPT SMP
> task: fffffff78f65b380 task.stack: ffffff8049a88000
> pc : sock_rfree+0x38/0x6c
> lr : skb_release_head_state+0x6c/0xcc
> Process repro (pid: 7117, stack limit = 0xffffff8049a88000)
> Call trace:
> sock_rfree+0x38/0x6c
> skb_release_head_state+0x6c/0xcc
> skb_release_all+0x1c/0x38
> __kfree_skb+0x1c/0x30
> kfree_skb+0xd0/0xf4
> pfkey_broadcast+0x14c/0x18c
> pfkey_sendmsg+0x1d8/0x408
> sock_sendmsg+0x44/0x60
> ___sys_sendmsg+0x1d0/0x2a8
> __sys_sendmsg+0x64/0xb4
> SyS_sendmsg+0x34/0x4c
> el0_svc_naked+0x34/0x38
> Kernel panic - not syncing: Fatal exception
>
> Signed-off-by: Sean Tranchetti <stran...@codeaurora.org>
> ---
> net/key/af_key.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/net/key/af_key.c b/net/key/af_key.c
> index 9d61266..dd257c7 100644
> --- a/net/key/af_key.c
> +++ b/net/key/af_key.c
> @@ -275,13 +275,13 @@ static int pfkey_broadcast(struct sk_buff *skb, gfp_t
> allocation,
> if ((broadcast_flags & BROADCAST_REGISTERED) && err)
> err = err2;
> }
> - rcu_read_unlock();
>
> if (one_sk != NULL)
> err = pfkey_broadcast_one(skb, &skb2, allocation, one_sk);
>
> kfree_skb(skb2);
> kfree_skb(skb);
> + rcu_read_unlock();
> return err;
> }
>
>
I do not believe the changelog or the patch makes sense.
Having skb still referencing a socket prevents this socket being released.
If you think about it, what would prevent the freeing happening
_before_ the rcu_read_lock() in pfkey_broadcast() ?
Maybe the correct fix is that pfkey_broadcast_one() should ensure the socket is
still valid.
I would suggest something like :
diff --git a/net/key/af_key.c b/net/key/af_key.c
index
9d61266526e767770d9a1ce184ac8cdd59de309a..5ce309d020dda5e46e4426c4a639bfb551e2260d
100644
--- a/net/key/af_key.c
+++ b/net/key/af_key.c
@@ -201,7 +201,9 @@ static int pfkey_broadcast_one(struct sk_buff *skb, struct
sk_buff **skb2,
{
int err = -ENOBUFS;
- sock_hold(sk);
+ if (!refcount_inc_not_zero(&sk->sk_refcnt))
+ return -ENOENT;
+
if (*skb2 == NULL) {
if (refcount_read(&skb->users) != 1) {
*skb2 = skb_clone(skb, allocation);