Commit d45ed4a4 authored by Alexei Starovoitov's avatar Alexei Starovoitov Committed by David S. Miller

net: fix unsafe set_memory_rw from softirq

on x86 system with net.core.bpf_jit_enable = 1

sudo tcpdump -i eth1 'tcp port 22'

causes the warning:
[   56.766097]  Possible unsafe locking scenario:
[   56.766097]
[   56.780146]        CPU0
[   56.786807]        ----
[   56.793188]   lock(&(&vb->lock)->rlock);
[   56.799593]   <Interrupt>
[   56.805889]     lock(&(&vb->lock)->rlock);
[   56.812266]
[   56.812266]  *** DEADLOCK ***
[   56.812266]
[   56.830670] 1 lock held by ksoftirqd/1/13:
[   56.836838]  #0:  (rcu_read_lock){.+.+..}, at: [<ffffffff8118f44c>] vm_unmap_aliases+0x8c/0x380
[   56.849757]
[   56.849757] stack backtrace:
[   56.862194] CPU: 1 PID: 13 Comm: ksoftirqd/1 Not tainted 3.12.0-rc3+ #45
[   56.868721] Hardware name: System manufacturer System Product Name/P8Z77 WS, BIOS 3007 07/26/2012
[   56.882004]  ffffffff821944c0 ffff88080bbdb8c8 ffffffff8175a145 0000000000000007
[   56.895630]  ffff88080bbd5f40 ffff88080bbdb928 ffffffff81755b14 0000000000000001
[   56.909313]  ffff880800000001 ffff880800000000 ffffffff8101178f 0000000000000001
[   56.923006] Call Trace:
[   56.929532]  [<ffffffff8175a145>] dump_stack+0x55/0x76
[   56.936067]  [<ffffffff81755b14>] print_usage_bug+0x1f7/0x208
[   56.942445]  [<ffffffff8101178f>] ? save_stack_trace+0x2f/0x50
[   56.948932]  [<ffffffff810cc0a0>] ? check_usage_backwards+0x150/0x150
[   56.955470]  [<ffffffff810ccb52>] mark_lock+0x282/0x2c0
[   56.961945]  [<ffffffff810ccfed>] __lock_acquire+0x45d/0x1d50
[   56.968474]  [<ffffffff810cce6e>] ? __lock_acquire+0x2de/0x1d50
[   56.975140]  [<ffffffff81393bf5>] ? cpumask_next_and+0x55/0x90
[   56.981942]  [<ffffffff810cef72>] lock_acquire+0x92/0x1d0
[   56.988745]  [<ffffffff8118f52a>] ? vm_unmap_aliases+0x16a/0x380
[   56.995619]  [<ffffffff817628f1>] _raw_spin_lock+0x41/0x50
[   57.002493]  [<ffffffff8118f52a>] ? vm_unmap_aliases+0x16a/0x380
[   57.009447]  [<ffffffff8118f52a>] vm_unmap_aliases+0x16a/0x380
[   57.016477]  [<ffffffff8118f44c>] ? vm_unmap_aliases+0x8c/0x380
[   57.023607]  [<ffffffff810436b0>] change_page_attr_set_clr+0xc0/0x460
[   57.030818]  [<ffffffff810cfb8d>] ? trace_hardirqs_on+0xd/0x10
[   57.037896]  [<ffffffff811a8330>] ? kmem_cache_free+0xb0/0x2b0
[   57.044789]  [<ffffffff811b59c3>] ? free_object_rcu+0x93/0xa0
[   57.051720]  [<ffffffff81043d9f>] set_memory_rw+0x2f/0x40
[   57.058727]  [<ffffffff8104e17c>] bpf_jit_free+0x2c/0x40
[   57.065577]  [<ffffffff81642cba>] sk_filter_release_rcu+0x1a/0x30
[   57.072338]  [<ffffffff811108e2>] rcu_process_callbacks+0x202/0x7c0
[   57.078962]  [<ffffffff81057f17>] __do_softirq+0xf7/0x3f0
[   57.085373]  [<ffffffff81058245>] run_ksoftirqd+0x35/0x70

cannot reuse jited filter memory, since it's readonly,
so use original bpf insns memory to hold work_struct

defer kfree of sk_filter until jit completed freeing

tested on x86_64 and i386
Signed-off-by: default avatarAlexei Starovoitov <ast@plumgrid.com>
Acked-by: default avatarEric Dumazet <edumazet@google.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 582442d6
...@@ -930,4 +930,5 @@ void bpf_jit_free(struct sk_filter *fp) ...@@ -930,4 +930,5 @@ void bpf_jit_free(struct sk_filter *fp)
{ {
if (fp->bpf_func != sk_run_filter) if (fp->bpf_func != sk_run_filter)
module_free(NULL, fp->bpf_func); module_free(NULL, fp->bpf_func);
kfree(fp);
} }
...@@ -691,4 +691,5 @@ void bpf_jit_free(struct sk_filter *fp) ...@@ -691,4 +691,5 @@ void bpf_jit_free(struct sk_filter *fp)
{ {
if (fp->bpf_func != sk_run_filter) if (fp->bpf_func != sk_run_filter)
module_free(NULL, fp->bpf_func); module_free(NULL, fp->bpf_func);
kfree(fp);
} }
...@@ -881,7 +881,9 @@ void bpf_jit_free(struct sk_filter *fp) ...@@ -881,7 +881,9 @@ void bpf_jit_free(struct sk_filter *fp)
struct bpf_binary_header *header = (void *)addr; struct bpf_binary_header *header = (void *)addr;
if (fp->bpf_func == sk_run_filter) if (fp->bpf_func == sk_run_filter)
return; goto free_filter;
set_memory_rw(addr, header->pages); set_memory_rw(addr, header->pages);
module_free(NULL, header); module_free(NULL, header);
free_filter:
kfree(fp);
} }
...@@ -808,4 +808,5 @@ void bpf_jit_free(struct sk_filter *fp) ...@@ -808,4 +808,5 @@ void bpf_jit_free(struct sk_filter *fp)
{ {
if (fp->bpf_func != sk_run_filter) if (fp->bpf_func != sk_run_filter)
module_free(NULL, fp->bpf_func); module_free(NULL, fp->bpf_func);
kfree(fp);
} }
...@@ -772,13 +772,21 @@ cond_branch: f_offset = addrs[i + filter[i].jf] - addrs[i]; ...@@ -772,13 +772,21 @@ cond_branch: f_offset = addrs[i + filter[i].jf] - addrs[i];
return; return;
} }
void bpf_jit_free(struct sk_filter *fp) static void bpf_jit_free_deferred(struct work_struct *work)
{ {
if (fp->bpf_func != sk_run_filter) { struct sk_filter *fp = container_of(work, struct sk_filter, work);
unsigned long addr = (unsigned long)fp->bpf_func & PAGE_MASK; unsigned long addr = (unsigned long)fp->bpf_func & PAGE_MASK;
struct bpf_binary_header *header = (void *)addr; struct bpf_binary_header *header = (void *)addr;
set_memory_rw(addr, header->pages); set_memory_rw(addr, header->pages);
module_free(NULL, header); module_free(NULL, header);
kfree(fp);
}
void bpf_jit_free(struct sk_filter *fp)
{
if (fp->bpf_func != sk_run_filter) {
INIT_WORK(&fp->work, bpf_jit_free_deferred);
schedule_work(&fp->work);
} }
} }
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
#include <linux/atomic.h> #include <linux/atomic.h>
#include <linux/compat.h> #include <linux/compat.h>
#include <linux/workqueue.h>
#include <uapi/linux/filter.h> #include <uapi/linux/filter.h>
#ifdef CONFIG_COMPAT #ifdef CONFIG_COMPAT
...@@ -25,15 +26,19 @@ struct sk_filter ...@@ -25,15 +26,19 @@ struct sk_filter
{ {
atomic_t refcnt; atomic_t refcnt;
unsigned int len; /* Number of filter blocks */ unsigned int len; /* Number of filter blocks */
struct rcu_head rcu;
unsigned int (*bpf_func)(const struct sk_buff *skb, unsigned int (*bpf_func)(const struct sk_buff *skb,
const struct sock_filter *filter); const struct sock_filter *filter);
struct rcu_head rcu; union {
struct sock_filter insns[0]; struct sock_filter insns[0];
struct work_struct work;
};
}; };
static inline unsigned int sk_filter_len(const struct sk_filter *fp) static inline unsigned int sk_filter_size(unsigned int proglen)
{ {
return fp->len * sizeof(struct sock_filter) + sizeof(*fp); return max(sizeof(struct sk_filter),
offsetof(struct sk_filter, insns[proglen]));
} }
extern int sk_filter(struct sock *sk, struct sk_buff *skb); extern int sk_filter(struct sock *sk, struct sk_buff *skb);
...@@ -67,11 +72,13 @@ static inline void bpf_jit_dump(unsigned int flen, unsigned int proglen, ...@@ -67,11 +72,13 @@ static inline void bpf_jit_dump(unsigned int flen, unsigned int proglen,
} }
#define SK_RUN_FILTER(FILTER, SKB) (*FILTER->bpf_func)(SKB, FILTER->insns) #define SK_RUN_FILTER(FILTER, SKB) (*FILTER->bpf_func)(SKB, FILTER->insns)
#else #else
#include <linux/slab.h>
static inline void bpf_jit_compile(struct sk_filter *fp) static inline void bpf_jit_compile(struct sk_filter *fp)
{ {
} }
static inline void bpf_jit_free(struct sk_filter *fp) static inline void bpf_jit_free(struct sk_filter *fp)
{ {
kfree(fp);
} }
#define SK_RUN_FILTER(FILTER, SKB) sk_run_filter(SKB, FILTER->insns) #define SK_RUN_FILTER(FILTER, SKB) sk_run_filter(SKB, FILTER->insns)
#endif #endif
......
...@@ -1630,16 +1630,14 @@ static inline void sk_filter_release(struct sk_filter *fp) ...@@ -1630,16 +1630,14 @@ static inline void sk_filter_release(struct sk_filter *fp)
static inline void sk_filter_uncharge(struct sock *sk, struct sk_filter *fp) static inline void sk_filter_uncharge(struct sock *sk, struct sk_filter *fp)
{ {
unsigned int size = sk_filter_len(fp); atomic_sub(sk_filter_size(fp->len), &sk->sk_omem_alloc);
atomic_sub(size, &sk->sk_omem_alloc);
sk_filter_release(fp); sk_filter_release(fp);
} }
static inline void sk_filter_charge(struct sock *sk, struct sk_filter *fp) static inline void sk_filter_charge(struct sock *sk, struct sk_filter *fp)
{ {
atomic_inc(&fp->refcnt); atomic_inc(&fp->refcnt);
atomic_add(sk_filter_len(fp), &sk->sk_omem_alloc); atomic_add(sk_filter_size(fp->len), &sk->sk_omem_alloc);
} }
/* /*
......
...@@ -644,7 +644,6 @@ void sk_filter_release_rcu(struct rcu_head *rcu) ...@@ -644,7 +644,6 @@ void sk_filter_release_rcu(struct rcu_head *rcu)
struct sk_filter *fp = container_of(rcu, struct sk_filter, rcu); struct sk_filter *fp = container_of(rcu, struct sk_filter, rcu);
bpf_jit_free(fp); bpf_jit_free(fp);
kfree(fp);
} }
EXPORT_SYMBOL(sk_filter_release_rcu); EXPORT_SYMBOL(sk_filter_release_rcu);
...@@ -683,7 +682,7 @@ int sk_unattached_filter_create(struct sk_filter **pfp, ...@@ -683,7 +682,7 @@ int sk_unattached_filter_create(struct sk_filter **pfp,
if (fprog->filter == NULL) if (fprog->filter == NULL)
return -EINVAL; return -EINVAL;
fp = kmalloc(fsize + sizeof(*fp), GFP_KERNEL); fp = kmalloc(sk_filter_size(fprog->len), GFP_KERNEL);
if (!fp) if (!fp)
return -ENOMEM; return -ENOMEM;
memcpy(fp->insns, fprog->filter, fsize); memcpy(fp->insns, fprog->filter, fsize);
...@@ -723,6 +722,7 @@ int sk_attach_filter(struct sock_fprog *fprog, struct sock *sk) ...@@ -723,6 +722,7 @@ int sk_attach_filter(struct sock_fprog *fprog, struct sock *sk)
{ {
struct sk_filter *fp, *old_fp; struct sk_filter *fp, *old_fp;
unsigned int fsize = sizeof(struct sock_filter) * fprog->len; unsigned int fsize = sizeof(struct sock_filter) * fprog->len;
unsigned int sk_fsize = sk_filter_size(fprog->len);
int err; int err;
if (sock_flag(sk, SOCK_FILTER_LOCKED)) if (sock_flag(sk, SOCK_FILTER_LOCKED))
...@@ -732,11 +732,11 @@ int sk_attach_filter(struct sock_fprog *fprog, struct sock *sk) ...@@ -732,11 +732,11 @@ int sk_attach_filter(struct sock_fprog *fprog, struct sock *sk)
if (fprog->filter == NULL) if (fprog->filter == NULL)
return -EINVAL; return -EINVAL;
fp = sock_kmalloc(sk, fsize+sizeof(*fp), GFP_KERNEL); fp = sock_kmalloc(sk, sk_fsize, GFP_KERNEL);
if (!fp) if (!fp)
return -ENOMEM; return -ENOMEM;
if (copy_from_user(fp->insns, fprog->filter, fsize)) { if (copy_from_user(fp->insns, fprog->filter, fsize)) {
sock_kfree_s(sk, fp, fsize+sizeof(*fp)); sock_kfree_s(sk, fp, sk_fsize);
return -EFAULT; return -EFAULT;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment