REDHAT-BUG-2481972: Use After Free
In the Linux kernel, the following vulnerability has been resolved:
net: ipv6: fix NOREF dst use in seg6 and rpl lwtunnels
seg6inputcore() and rplinput() call ip6routeinput() which sets a NOREF dst on the skb, then pass it to dstcachesetip6() invoking dsthold() unconditionally. On PREEMPTRT, ksoftirqd is preemptible and a higher-priority task can release the underlying pcpurt between the lookup and the caching through a concurrent FIB lookup on a shared nexthop. Simplified race sequence:
ksoftirqd/X higher-prio task (same CPU X) ----------- -------------------------------- seg6inputcore(,skb)/rplinput(skb) dstcacheget() -> miss ip6routeinput(skb) -> ip6polroute(,skb,flags) [RT6LOOKUPFDSTNOREF in flags] -> FIB lookup resolves fib6nh [nhid=N route] -> rt6makepcpuroute() [creates pcpurt, refcount=1] pcpurt->sernum = fib6sernum [fib6sernum=W] -> cmpxchg(fib6nh.rt6ipcpu, NULL, pcpurt) [slot was empty, store succeeds] -> skbdstsetnoref(skb, dst) [dst is pcpurt, refcount still 1]
rtgenidbumpipv6() -> bumps fib6sernum [fib6sernum from W to Z] ip6routeoutput() -> ip6polroute() -> FIB lookup resolves fib6nh [nhid=N] -> rt6getpcpuroute() pcpurt->sernum != fib6sernum [W <> Z, stale] -> prev = xchg(rt6ipcpu, NULL) -> dstrelease(prev) [prev is pcpurt, refcount 1->0, dead]
dst = skbdst(skb) [dst is the dead pcpurt] dstcachesetip6(dst) -> dsthold() on dead dst -> WARN / use-after-free
For the race to occur, ksoftirqd must be preemptible (PREEMPTRT without PREEMPTRTNEEDSBHLOCK) and a concurrent task must be able to release the pcpurt. Shared nexthop objects provide such a path, as two routes pointing to the same nhid share the same fib6nh and its rt6ipcpu entry.
Fix seg6inputcore() and rplinput() by calling skbdstforce() after ip6routeinput() to force the NOREF dst into a refcounted one before caching. The output path is not affected as ip6routeoutput() already returns a refcounted dst.