Fixes a tricky bug in route filtering.
Route attributes was used after rta was freed during copy-on-write in filter code. This causes some random crashes, esp. with multipath routes.
This commit is contained in:
parent
69a8259c5e
commit
a03ede6493
2 changed files with 77 additions and 24 deletions
100
filter/filter.c
100
filter/filter.c
|
@ -485,24 +485,42 @@ val_print(struct f_val v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct rte **f_rte, *f_rte_old;
|
static struct rte **f_rte;
|
||||||
static struct linpool *f_pool;
|
static struct rta *f_old_rta;
|
||||||
static struct ea_list **f_tmp_attrs;
|
static struct ea_list **f_tmp_attrs;
|
||||||
|
static struct linpool *f_pool;
|
||||||
static int f_flags;
|
static int f_flags;
|
||||||
|
|
||||||
|
static inline void f_rte_cow(void)
|
||||||
|
{
|
||||||
|
*f_rte = rte_cow(*f_rte);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* rta_cow - prepare rta for modification by filter
|
* rta_cow - prepare rta for modification by filter
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
rta_cow(void)
|
f_rta_cow(void)
|
||||||
{
|
{
|
||||||
if ((*f_rte)->attrs->aflags & RTAF_CACHED) {
|
if ((*f_rte)->attrs->aflags & RTAF_CACHED) {
|
||||||
rta *f_rta_copy = lp_alloc(f_pool, sizeof(rta));
|
|
||||||
memcpy(f_rta_copy, (*f_rte)->attrs, sizeof(rta));
|
/* Prepare to modify rte */
|
||||||
f_rta_copy->aflags = 0;
|
f_rte_cow();
|
||||||
*f_rte = rte_cow(*f_rte);
|
|
||||||
rta_free((*f_rte)->attrs);
|
/* Store old rta to free it later */
|
||||||
(*f_rte)->attrs = f_rta_copy;
|
f_old_rta = (*f_rte)->attrs;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Alloc new rta, do shallow copy and update rte. Fields eattrs
|
||||||
|
* and nexthops of rta are shared with f_old_rta (they will be
|
||||||
|
* copied when the cached rta will be obtained at the end of
|
||||||
|
* f_run()), also the lock of hostentry is inherited (we suppose
|
||||||
|
* hostentry is not changed by filters).
|
||||||
|
*/
|
||||||
|
rta *ra = lp_alloc(f_pool, sizeof(rta));
|
||||||
|
memcpy(ra, f_old_rta, sizeof(rta));
|
||||||
|
ra->aflags = 0;
|
||||||
|
(*f_rte)->attrs = ra;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -819,7 +837,7 @@ interpret(struct f_inst *what)
|
||||||
ONEARG;
|
ONEARG;
|
||||||
if (what->aux != v1.type)
|
if (what->aux != v1.type)
|
||||||
runtime( "Attempt to set static attribute to incompatible type" );
|
runtime( "Attempt to set static attribute to incompatible type" );
|
||||||
rta_cow();
|
f_rta_cow();
|
||||||
{
|
{
|
||||||
struct rta *rta = (*f_rte)->attrs;
|
struct rta *rta = (*f_rte)->attrs;
|
||||||
switch (what->aux) {
|
switch (what->aux) {
|
||||||
|
@ -955,7 +973,7 @@ interpret(struct f_inst *what)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(what->aux & EAF_TEMP) && (!(f_flags & FF_FORCE_TMPATTR))) {
|
if (!(what->aux & EAF_TEMP) && (!(f_flags & FF_FORCE_TMPATTR))) {
|
||||||
rta_cow();
|
f_rta_cow();
|
||||||
l->next = (*f_rte)->attrs->eattrs;
|
l->next = (*f_rte)->attrs->eattrs;
|
||||||
(*f_rte)->attrs->eattrs = l;
|
(*f_rte)->attrs->eattrs = l;
|
||||||
} else {
|
} else {
|
||||||
|
@ -974,7 +992,7 @@ interpret(struct f_inst *what)
|
||||||
runtime( "Can't set preference to non-integer" );
|
runtime( "Can't set preference to non-integer" );
|
||||||
if ((v1.val.i < 0) || (v1.val.i > 0xFFFF))
|
if ((v1.val.i < 0) || (v1.val.i > 0xFFFF))
|
||||||
runtime( "Setting preference value out of bounds" );
|
runtime( "Setting preference value out of bounds" );
|
||||||
*f_rte = rte_cow(*f_rte);
|
f_rte_cow();
|
||||||
(*f_rte)->pref = v1.val.i;
|
(*f_rte)->pref = v1.val.i;
|
||||||
break;
|
break;
|
||||||
case 'L': /* Get length of */
|
case 'L': /* Get length of */
|
||||||
|
@ -1300,29 +1318,64 @@ i_same(struct f_inst *f1, struct f_inst *f2)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* f_run - external entry point to filters
|
* f_run - run a filter for a route
|
||||||
* @filter: pointer to filter to run
|
* @filter: filter to run
|
||||||
* @tmp_attrs: where to store newly generated temporary attributes
|
* @rte: route being filtered, may be modified
|
||||||
* @rte: pointer to pointer to &rte being filtered. When route is modified, this is changed with rte_cow().
|
* @tmp_attrs: temporary attributes, prepared by caller or generated by f_run()
|
||||||
* @tmp_pool: all filter allocations go from this pool
|
* @tmp_pool: all filter allocations go from this pool
|
||||||
* @flags: flags
|
* @flags: flags
|
||||||
|
*
|
||||||
|
* If filter needs to modify the route, there are several
|
||||||
|
* posibilities. @rte might be read-only (with REF_COW flag), in that
|
||||||
|
* case rw copy is obtained by rte_cow() and @rte is replaced. If
|
||||||
|
* @rte is originally rw, it may be directly modified (and it is never
|
||||||
|
* copied).
|
||||||
|
*
|
||||||
|
* The returned rte may reuse the (possibly cached, cloned) rta, or
|
||||||
|
* (if rta was modificied) contains a modified uncached rta, which
|
||||||
|
* uses parts allocated from @tmp_pool and parts shared from original
|
||||||
|
* rta. There is one exception - if @rte is rw but contains a cached
|
||||||
|
* rta and that is modified, rta in returned rte is also cached.
|
||||||
|
*
|
||||||
|
* Ownership of cached rtas is consistent with rte, i.e.
|
||||||
|
* if a new rte is returned, it has its own clone of cached rta
|
||||||
|
* (and cached rta of read-only source rte is intact), if rte is
|
||||||
|
* modified in place, old cached rta is possibly freed.
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
f_run(struct filter *filter, struct rte **rte, struct ea_list **tmp_attrs, struct linpool *tmp_pool, int flags)
|
f_run(struct filter *filter, struct rte **rte, struct ea_list **tmp_attrs, struct linpool *tmp_pool, int flags)
|
||||||
{
|
{
|
||||||
struct f_inst *inst;
|
int rte_cow = ((*rte)->flags & REF_COW);
|
||||||
struct f_val res;
|
|
||||||
DBG( "Running filter `%s'...", filter->name );
|
DBG( "Running filter `%s'...", filter->name );
|
||||||
|
|
||||||
f_flags = flags;
|
|
||||||
f_tmp_attrs = tmp_attrs;
|
|
||||||
f_rte = rte;
|
f_rte = rte;
|
||||||
f_rte_old = *rte;
|
f_old_rta = NULL;
|
||||||
|
f_tmp_attrs = tmp_attrs;
|
||||||
f_pool = tmp_pool;
|
f_pool = tmp_pool;
|
||||||
inst = filter->root;
|
f_flags = flags;
|
||||||
|
|
||||||
log_reset();
|
log_reset();
|
||||||
res = interpret(inst);
|
struct f_val res = interpret(filter->root);
|
||||||
|
|
||||||
|
if (f_old_rta) {
|
||||||
|
/*
|
||||||
|
* Cached rta was modified and f_rte contains now an uncached one,
|
||||||
|
* sharing some part with the cached one. The cached rta should
|
||||||
|
* be freed (if rte was originally COW, f_old_rta is a clone
|
||||||
|
* obtained during rte_cow()).
|
||||||
|
*
|
||||||
|
* This also implements the exception mentioned in f_run()
|
||||||
|
* description. The reason for this is that rta reuses parts of
|
||||||
|
* f_old_rta, and these may be freed during rta_free(f_old_rta).
|
||||||
|
* This is not the problem if rte was COW, because original rte
|
||||||
|
* also holds the same rta.
|
||||||
|
*/
|
||||||
|
if (!rte_cow)
|
||||||
|
(*f_rte)->attrs = rta_lookup((*f_rte)->attrs);
|
||||||
|
|
||||||
|
rta_free(f_old_rta);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (res.type != T_RETURN) {
|
if (res.type != T_RETURN) {
|
||||||
log( L_ERR "Filter %s did not return accept nor reject. Make up your mind", filter->name);
|
log( L_ERR "Filter %s did not return accept nor reject. Make up your mind", filter->name);
|
||||||
|
@ -1341,7 +1394,6 @@ f_eval_int(struct f_inst *expr)
|
||||||
f_flags = 0;
|
f_flags = 0;
|
||||||
f_tmp_attrs = NULL;
|
f_tmp_attrs = NULL;
|
||||||
f_rte = NULL;
|
f_rte = NULL;
|
||||||
f_rte_old = NULL;
|
|
||||||
f_pool = cfg_mem;
|
f_pool = cfg_mem;
|
||||||
|
|
||||||
log_reset();
|
log_reset();
|
||||||
|
|
|
@ -60,6 +60,7 @@ static struct resclass sl_class = {
|
||||||
sizeof(struct slab),
|
sizeof(struct slab),
|
||||||
slab_free,
|
slab_free,
|
||||||
slab_dump,
|
slab_dump,
|
||||||
|
NULL,
|
||||||
slab_memsize
|
slab_memsize
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue