Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

netfilter: nft_set_rbtree: Detect partial overlaps on insertion

...and return -ENOTEMPTY to the front-end in this case, instead of
proceeding. Currently, nft takes care of checking for these cases
and not sending them to the kernel, but if we drop the set_overlap()
call in nft we can end up in situations like:

# nft add table t
# nft add set t s '{ type inet_service ; flags interval ; }'
# nft add element t s '{ 1 - 5 }'
# nft add element t s '{ 6 - 10 }'
# nft add element t s '{ 4 - 7 }'
# nft list set t s
table ip t {
set s {
type inet_service
flags interval
elements = { 1-3, 4-5, 6-7 }
}
}

This change has the primary purpose of making the behaviour
consistent with nft_set_pipapo, but is also functional to avoid
inconsistent behaviour if userspace sends overlapping elements for
any reason.

v2: When we meet the same key data in the tree, as start element while
inserting an end element, or as end element while inserting a start
element, actually check that the existing element is active, before
resetting the overlap flag (Pablo Neira Ayuso)

Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>

authored by

Stefano Brivio and committed by
Pablo Neira Ayuso
7c84d414 6f7c9caf

+67 -3
+67 -3
net/netfilter/nft_set_rbtree.c
··· 213 213 u8 genmask = nft_genmask_next(net); 214 214 struct nft_rbtree_elem *rbe; 215 215 struct rb_node *parent, **p; 216 + bool overlap = false; 216 217 int d; 218 + 219 + /* Detect overlaps as we descend the tree. Set the flag in these cases: 220 + * 221 + * a1. |__ _ _? >|__ _ _ (insert start after existing start) 222 + * a2. _ _ __>| ?_ _ __| (insert end before existing end) 223 + * a3. _ _ ___| ?_ _ _>| (insert end after existing end) 224 + * a4. >|__ _ _ _ _ __| (insert start before existing end) 225 + * 226 + * and clear it later on, as we eventually reach the points indicated by 227 + * '?' above, in the cases described below. We'll always meet these 228 + * later, locally, due to tree ordering, and overlaps for the intervals 229 + * that are the closest together are always evaluated last. 230 + * 231 + * b1. |__ _ _! >|__ _ _ (insert start after existing end) 232 + * b2. _ _ __>| !_ _ __| (insert end before existing start) 233 + * b3. !_____>| (insert end after existing start) 234 + * 235 + * Case a4. resolves to b1.: 236 + * - if the inserted start element is the leftmost, because the '0' 237 + * element in the tree serves as end element 238 + * - otherwise, if an existing end is found. Note that end elements are 239 + * always inserted after corresponding start elements. 240 + * 241 + * For a new, rightmost pair of elements, we'll hit cases b1. and b3., 242 + * in that order. 243 + * 244 + * The flag is also cleared in two special cases: 245 + * 246 + * b4. |__ _ _!|<_ _ _ (insert start right before existing end) 247 + * b5. |__ _ >|!__ _ _ (insert end right after existing start) 248 + * 249 + * which always happen as last step and imply that no further 250 + * overlapping is possible. 251 + */ 217 252 218 253 parent = NULL; 219 254 p = &priv->root.rb_node; ··· 258 223 d = memcmp(nft_set_ext_key(&rbe->ext), 259 224 nft_set_ext_key(&new->ext), 260 225 set->klen); 261 - if (d < 0) 226 + if (d < 0) { 262 227 p = &parent->rb_left; 263 - else if (d > 0) 228 + 229 + if (nft_rbtree_interval_start(new)) { 230 + overlap = nft_rbtree_interval_start(rbe) && 231 + nft_set_elem_active(&rbe->ext, 232 + genmask); 233 + } else { 234 + overlap = nft_rbtree_interval_end(rbe) && 235 + nft_set_elem_active(&rbe->ext, 236 + genmask); 237 + } 238 + } else if (d > 0) { 264 239 p = &parent->rb_right; 265 - else { 240 + 241 + if (nft_rbtree_interval_end(new)) { 242 + overlap = nft_rbtree_interval_end(rbe) && 243 + nft_set_elem_active(&rbe->ext, 244 + genmask); 245 + } else if (nft_rbtree_interval_end(rbe) && 246 + nft_set_elem_active(&rbe->ext, genmask)) { 247 + overlap = true; 248 + } 249 + } else { 266 250 if (nft_rbtree_interval_end(rbe) && 267 251 nft_rbtree_interval_start(new)) { 268 252 p = &parent->rb_left; 253 + 254 + if (nft_set_elem_active(&rbe->ext, genmask)) 255 + overlap = false; 269 256 } else if (nft_rbtree_interval_start(rbe) && 270 257 nft_rbtree_interval_end(new)) { 271 258 p = &parent->rb_right; 259 + 260 + if (nft_set_elem_active(&rbe->ext, genmask)) 261 + overlap = false; 272 262 } else if (nft_set_elem_active(&rbe->ext, genmask)) { 273 263 *ext = &rbe->ext; 274 264 return -EEXIST; ··· 302 242 } 303 243 } 304 244 } 245 + 246 + if (overlap) 247 + return -ENOTEMPTY; 248 + 305 249 rb_link_node_rcu(&new->node, parent, p); 306 250 rb_insert_color(&new->node, &priv->root); 307 251 return 0;