2019-03-23 22:38:43 +00:00
|
|
|
/* Scenarios of parent/child FSM instances cleaning up and deallocating from various triggers. */
|
|
|
|
|
|
|
|
#include <talloc.h>
|
|
|
|
|
|
|
|
#include <osmocom/core/application.h>
|
|
|
|
#include <osmocom/core/logging.h>
|
|
|
|
#include <osmocom/core/fsm.h>
|
|
|
|
#include <osmocom/core/use_count.h>
|
|
|
|
|
|
|
|
enum event {
|
|
|
|
EV_DESTROY,
|
|
|
|
EV_CHILD_GONE,
|
|
|
|
EV_OTHER_GONE,
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct value_string test_fsm_event_names[] = {
|
|
|
|
OSMO_VALUE_STRING(EV_DESTROY),
|
|
|
|
OSMO_VALUE_STRING(EV_CHILD_GONE),
|
|
|
|
OSMO_VALUE_STRING(EV_OTHER_GONE),
|
|
|
|
{}
|
|
|
|
};
|
|
|
|
|
|
|
|
enum state {
|
|
|
|
ST_ALIVE,
|
|
|
|
};
|
|
|
|
|
|
|
|
enum objname {
|
|
|
|
root = 0,
|
|
|
|
branch0,
|
|
|
|
twig0a,
|
|
|
|
twig0b,
|
|
|
|
branch1,
|
|
|
|
twig1a,
|
|
|
|
twig1b,
|
|
|
|
|
|
|
|
other,
|
|
|
|
scene_size
|
|
|
|
};
|
|
|
|
|
|
|
|
struct scene {
|
|
|
|
struct obj *o[scene_size];
|
|
|
|
|
|
|
|
/* The use count is actually just to help tracking what functions have not exited yet */
|
|
|
|
struct osmo_use_count use_count;
|
|
|
|
};
|
|
|
|
|
|
|
|
int use_cb(struct osmo_use_count_entry *use_count_entry, int32_t old_use_count, const char *file, int line)
|
|
|
|
{
|
|
|
|
char buf[128];
|
|
|
|
LOGP(DLGLOBAL, LOGL_DEBUG, "%s\n", osmo_use_count_name_buf(buf, sizeof(buf), use_count_entry->use_count));
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* References to related actual objects that are tied to FSM instances. */
|
|
|
|
struct obj {
|
|
|
|
struct osmo_fsm_inst *fi;
|
|
|
|
struct scene *s;
|
|
|
|
struct obj *parent;
|
|
|
|
struct obj *child[2];
|
|
|
|
struct obj *other[3];
|
|
|
|
};
|
|
|
|
|
|
|
|
static void scene_forget_obj(struct scene *s, struct obj *obj)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(obj->s->o); i++) {
|
|
|
|
if (obj->s->o[i] != obj)
|
|
|
|
continue;
|
|
|
|
LOGPFSML(obj->fi, LOGL_DEBUG, "scene forgets %s\n", obj->fi->id);
|
|
|
|
obj->s->o[i] = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct scene *g_scene = NULL;
|
|
|
|
|
|
|
|
#define GET() \
|
|
|
|
char *token = talloc_asprintf(g_scene, "%s.%s()", obj->fi->id, __func__); \
|
|
|
|
osmo_use_count_get_put(&g_scene->use_count, token, 1)
|
|
|
|
|
|
|
|
#define PUT() osmo_use_count_get_put(&g_scene->use_count, token, -1)
|
|
|
|
|
|
|
|
void alive_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
|
|
|
|
{
|
|
|
|
LOGPFSML(fi, LOGL_DEBUG, "%s()\n", __func__);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Remove obj->other[*] reference, return true if found and removed, false if not. */
|
|
|
|
bool other_gone(struct obj *obj, struct obj *other)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
GET();
|
|
|
|
for (i = 0; i < ARRAY_SIZE(obj->other); i++) {
|
|
|
|
if (obj->other[i] == other) {
|
|
|
|
obj->other[i] = NULL;
|
|
|
|
LOGPFSML(obj->fi, LOGL_DEBUG, "EV_OTHER_GONE: Dropped reference %s.other[%d] = %s\n", obj->fi->id, i,
|
|
|
|
other->fi->id);
|
|
|
|
PUT();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
PUT();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Remove obj->child[*] reference, return true if more children remain after this, false if all are gone */
|
|
|
|
bool child_gone(struct obj *obj, struct obj *child)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
bool found;
|
|
|
|
if (!child) {
|
|
|
|
LOGPFSML(obj->fi, LOGL_DEBUG, "EV_CHILD_GONE with NULL data, must be a parent_term event. Ignore.\n");
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
GET();
|
|
|
|
found = false;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(obj->child); i++) {
|
|
|
|
if (obj->child[i] == child) {
|
|
|
|
obj->child[i] = NULL;
|
|
|
|
LOGPFSML(obj->fi, LOGL_DEBUG, "EV_CHILD_GONE: Dropped reference %s.child[%d] = %s\n", obj->fi->id, i,
|
|
|
|
child->fi->id);
|
|
|
|
found = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!found)
|
|
|
|
LOGPFSML(obj->fi, LOGL_ERROR, "EV_CHILD_GONE: cannot find child %s\n",
|
|
|
|
child && child->fi ? child->fi->id : "(null)");
|
|
|
|
|
|
|
|
/* Any children left? */
|
|
|
|
for (i = 0; i < ARRAY_SIZE(obj->child); i++) {
|
|
|
|
if (obj->child[i]) {
|
|
|
|
LOGPFSML(obj->fi, LOGL_DEBUG, "still exists: child[%d]\n", i);
|
|
|
|
PUT();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
LOGPFSML(obj->fi, LOGL_DEBUG, "No more children\n");
|
|
|
|
PUT();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void alive(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
|
|
{
|
|
|
|
struct obj *obj = fi->priv;
|
|
|
|
GET();
|
|
|
|
LOGPFSML(fi, LOGL_DEBUG, "%s(%s)\n", __func__, osmo_fsm_event_name(fi->fsm, event));
|
|
|
|
switch (event) {
|
|
|
|
case EV_OTHER_GONE:
|
|
|
|
if (other_gone(obj, data)) {
|
|
|
|
/* Something this object depends on is gone, trigger deallocation */
|
2019-03-24 04:43:04 +00:00
|
|
|
osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, 0);
|
2019-03-23 22:38:43 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case EV_CHILD_GONE:
|
|
|
|
if (!child_gone(obj, data)) {
|
|
|
|
/* All children are gone. Deallocate. */
|
2019-03-24 04:43:04 +00:00
|
|
|
osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, 0);
|
2019-03-23 22:38:43 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case EV_DESTROY:
|
2019-03-24 04:43:04 +00:00
|
|
|
osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, 0);
|
2019-03-23 22:38:43 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
OSMO_ASSERT(false);
|
|
|
|
}
|
|
|
|
PUT();
|
|
|
|
}
|
|
|
|
|
|
|
|
#define S(x) (1 << (x))
|
|
|
|
|
|
|
|
static const struct osmo_fsm_state test_fsm_states[] = {
|
|
|
|
[ST_ALIVE] = {
|
|
|
|
.name = "alive",
|
|
|
|
.in_event_mask = 0
|
|
|
|
| S(EV_CHILD_GONE)
|
|
|
|
| S(EV_OTHER_GONE)
|
|
|
|
| S(EV_DESTROY)
|
|
|
|
,
|
|
|
|
.out_state_mask = 0
|
|
|
|
| S(ST_ALIVE)
|
|
|
|
,
|
|
|
|
.onenter = alive_onenter,
|
|
|
|
.action = alive,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
void cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
|
|
|
|
{
|
|
|
|
struct obj *obj = fi->priv;
|
|
|
|
int i;
|
|
|
|
GET();
|
|
|
|
LOGPFSML(fi, LOGL_DEBUG, "%s()\n", __func__);
|
|
|
|
|
|
|
|
/* Remove from the scene overview for this test */
|
|
|
|
scene_forget_obj(obj->s, obj);
|
|
|
|
|
|
|
|
/* Signal "other" objects */
|
|
|
|
for (i = 0; i < ARRAY_SIZE(obj->other); i++) {
|
|
|
|
struct obj *other = obj->other[i];
|
|
|
|
if (!other)
|
|
|
|
continue;
|
|
|
|
LOGPFSML(fi, LOGL_DEBUG, "removing reference %s.other[%d] -> %s\n",
|
|
|
|
obj->fi->id, i, other->fi->id);
|
|
|
|
obj->other[i] = NULL;
|
|
|
|
osmo_fsm_inst_dispatch(other->fi, EV_OTHER_GONE, obj);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (obj->parent)
|
|
|
|
osmo_fsm_inst_dispatch(obj->parent->fi, EV_CHILD_GONE, obj);
|
|
|
|
|
|
|
|
/* children are handled by fsm.c: term event / osmo_fsm_inst_term_children() */
|
|
|
|
LOGPFSML(fi, LOGL_DEBUG, "%s() done\n", __func__);
|
|
|
|
PUT();
|
|
|
|
}
|
|
|
|
|
|
|
|
int timer_cb(struct osmo_fsm_inst *fi)
|
|
|
|
{
|
|
|
|
LOGPFSML(fi, LOGL_DEBUG, "%s()\n", __func__);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
void pre_term(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
|
|
|
|
{
|
|
|
|
LOGPFSML(fi, LOGL_DEBUG, "%s()\n", __func__);
|
|
|
|
}
|
|
|
|
|
|
|
|
struct osmo_fsm test_fsm = {
|
|
|
|
.name = "test",
|
|
|
|
.states = test_fsm_states,
|
|
|
|
.num_states = ARRAY_SIZE(test_fsm_states),
|
|
|
|
.cleanup = cleanup,
|
|
|
|
.timer_cb = timer_cb,
|
|
|
|
.event_names = test_fsm_event_names,
|
|
|
|
.pre_term = pre_term,
|
|
|
|
.log_subsys = DLGLOBAL,
|
|
|
|
};
|
|
|
|
|
|
|
|
void *ctx = NULL;
|
|
|
|
|
|
|
|
static struct obj *obj_alloc(struct scene *s, struct obj *parent, const char *id) {
|
|
|
|
struct osmo_fsm_inst *fi;
|
|
|
|
struct obj *obj;
|
|
|
|
if (!parent) {
|
|
|
|
fi = osmo_fsm_inst_alloc(&test_fsm, s, NULL, LOGL_DEBUG, id);
|
|
|
|
OSMO_ASSERT(fi);
|
|
|
|
} else {
|
|
|
|
fi = osmo_fsm_inst_alloc_child(&test_fsm, parent->fi, EV_CHILD_GONE);
|
|
|
|
OSMO_ASSERT(fi);
|
|
|
|
osmo_fsm_inst_update_id(fi, id);
|
|
|
|
}
|
|
|
|
|
|
|
|
obj = talloc_zero(fi, struct obj);
|
|
|
|
fi->priv = obj;
|
|
|
|
*obj = (struct obj){
|
|
|
|
.fi = fi,
|
|
|
|
.s = s,
|
|
|
|
.parent = parent,
|
|
|
|
};
|
|
|
|
|
|
|
|
if (parent) {
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(parent->child); i++) {
|
|
|
|
if (parent->child[i])
|
|
|
|
continue;
|
|
|
|
parent->child[i] = obj;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return obj;
|
|
|
|
};
|
|
|
|
|
|
|
|
void obj_add_other(struct obj *a, struct obj *b)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(a->other); i++) {
|
|
|
|
if (a->other[i])
|
|
|
|
i++;
|
|
|
|
a->other[i] = b;
|
|
|
|
LOGPFSML(a->fi, LOGL_DEBUG, "%s.other[%d] = %s\n", a->fi->id, i, b->fi->id);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void obj_set_other(struct obj *a, struct obj *b)
|
|
|
|
{
|
|
|
|
obj_add_other(a, b);
|
|
|
|
obj_add_other(b, a);
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct scene *scene_alloc()
|
|
|
|
{
|
|
|
|
struct scene *s = talloc_zero(ctx, struct scene);
|
|
|
|
s->use_count.talloc_object = s;
|
|
|
|
s->use_count.use_cb = use_cb;
|
|
|
|
|
|
|
|
LOGP(DLGLOBAL, LOGL_DEBUG, "%s()\n", __func__);
|
|
|
|
|
|
|
|
s->o[root] = obj_alloc(s, NULL, "root");
|
|
|
|
|
|
|
|
s->o[branch0] = obj_alloc(s, s->o[root], "_branch0");
|
|
|
|
|
|
|
|
s->o[twig0a] = obj_alloc(s, s->o[branch0], "__twig0a");
|
|
|
|
|
|
|
|
s->o[twig0b] = obj_alloc(s, s->o[branch0], "__twig0b");
|
|
|
|
|
|
|
|
s->o[branch1] = obj_alloc(s, s->o[root], "_branch1");
|
|
|
|
s->o[twig1a] = obj_alloc(s, s->o[branch1], "__twig1a");
|
|
|
|
s->o[twig1b] = obj_alloc(s, s->o[branch1], "__twig1b");
|
|
|
|
|
|
|
|
s->o[other] = obj_alloc(s, NULL, "other");
|
|
|
|
|
|
|
|
obj_set_other(s->o[branch0], s->o[other]);
|
|
|
|
obj_set_other(s->o[twig0a], s->o[other]);
|
fsm: support graceful osmo_fsm_inst_term() cascades
Add global flag osmo_fsm_term_safely() -- if set to true, enable the following
behavior:
Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term():
- collect deallocations until the outermost osmo_fsm_inst_term() is done.
- call osmo_fsm_inst_free() *after* dispatching the parent event.
If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already
within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent
it to a separate talloc context, to be deallocated with the outermost FSM inst.
The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term()
cascade will stay allocated until all osmo_fsm_inst_term() are complete and all
of them will be deallocated at the same time.
Mark the deferred deallocation state as __thread in an attempt to make cascaded
deallocation handling threadsafe. Keep the enable/disable flag separate, so
that it is global and not per-thread.
The feature is showcased by fsm_dealloc_test.c: with this feature, all of those
wild deallocation scenarios succeed.
Make fsm_dealloc_test a normal regression test in testsuite.at.
Rationale:
It is difficult to gracefully handle deallocations of groups of FSM instances
that reference each other. As soon as one child dispatching a cleanup event
causes its parent to deallocate before fsm.c was ready for it, deallocation
will hit a use-after-free. Before this patch, by using parent_term events and
distinct "terminating" FSM states, parent/child FSMs can be taught to wait for
all children to deallocate before deallocating the parent. But as soon as a
non-child / non-parent FSM instance is involved, or actually any other
cleanup() action that triggers parent FSMs or parent talloc contexts to become
unused, it is near impossible to think of all possible deallocation events
ricocheting, and to avoid running into freeing FSM instances that were still in
the middle of osmo_fsm_inst_term(), or FSM instances to enter
osmo_fsm_inst_term() more than once. This patch makes deallocation of "all
possible" setups of complex cross referencing FSM instances easy to handle
correctly, without running into use-after-free or double free situations, and,
notably, without changing calling code.
Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
|
|
|
obj_set_other(s->o[branch1], s->o[other]);
|
|
|
|
obj_set_other(s->o[twig1a], s->o[root]);
|
2019-03-23 22:38:43 +00:00
|
|
|
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int scene_dump(struct scene *s)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
int got = 0;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(s->o); i++) {
|
|
|
|
if (!s->o[i])
|
|
|
|
continue;
|
|
|
|
LOGP(DLGLOBAL, LOGL_DEBUG, " %s\n", s->o[i]->fi->id);
|
|
|
|
got++;
|
|
|
|
}
|
|
|
|
return got;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void scene_clean(struct scene *s)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(s->o); i++) {
|
|
|
|
if (!s->o[i])
|
|
|
|
continue;
|
|
|
|
osmo_fsm_inst_term(s->o[i]->fi, OSMO_FSM_TERM_ERROR, 0);
|
|
|
|
s->o[i] = NULL;
|
|
|
|
}
|
|
|
|
talloc_free(s);
|
|
|
|
}
|
|
|
|
|
|
|
|
void obj_destroy(struct obj *obj)
|
|
|
|
{
|
|
|
|
osmo_fsm_inst_dispatch(obj->fi, EV_DESTROY, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
void obj_term(struct obj *obj)
|
|
|
|
{
|
|
|
|
osmo_fsm_inst_term(obj->fi, OSMO_FSM_TERM_REGULAR, NULL);
|
|
|
|
}
|
|
|
|
|
add osmo_fsm_set_dealloc_ctx(), to help with use-after-free
This is a simpler and more general solution to the problem so far solved by
osmo_fsm_term_safely(true). This extends use-after-free fixes to arbitrary
functions, not only FSM instances during termination.
The aim is to defer talloc_free() until back in the main loop.
Rationale: I discovered an osmo-msc use-after-free crash from an invalid
message, caused by this pattern:
void event_action()
{
osmo_fsm_inst_dispatch(foo, FOO_EVENT, NULL);
osmo_fsm_inst_dispatch(bar, BAR_EVENT, NULL);
}
Usually, FOO_EVENT takes successful action, and afterwards we also notify bar.
However, in this particular case, FOO_EVENT caused failure, and the immediate
error handling directly terminated and deallocated bar. In such a case,
dispatching BAR_EVENT causes a use-after-free; this constituted a DoS vector
just from sending messages that cause *any* failure during the first event
dispatch.
Instead, when this is enabled, we do not deallocate 'foo' until event_action()
has returned back to the main loop.
Test: duplicate fsm_dealloc_test.c using this, and print the number of items
deallocated in each test loop, to ensure the feature works. We also verify that
the deallocation safety works simply by fsm_dealloc_test.c not crashing.
We should probably follow up by refusing event dispatch and state transitions
for FSM instances that are terminating or already terminated:
see I0adc13a1a998e953b6c850efa2761350dd07e03a.
Change-Id: Ief4dba9ea587c9b4aea69993e965fbb20fb80e78
2019-10-04 18:37:17 +00:00
|
|
|
void test_dealloc(enum objname trigger, bool by_destroy_event, void *loop_ctx)
|
2019-03-23 22:38:43 +00:00
|
|
|
{
|
|
|
|
struct scene *s = scene_alloc();
|
|
|
|
const char *label = by_destroy_event ? "destroy-event" : "term";
|
|
|
|
int remain;
|
|
|
|
g_scene = s;
|
|
|
|
if (!s->o[trigger]) {
|
|
|
|
LOGP(DLGLOBAL, LOGL_DEBUG, "--- Test disabled: object %d was not created. Cleaning up.\n",
|
|
|
|
trigger);
|
|
|
|
scene_clean(s);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
LOGP(DLGLOBAL, LOGL_DEBUG, "------ before %s cascade, got:\n", label);
|
|
|
|
scene_dump(s);
|
|
|
|
LOGP(DLGLOBAL, LOGL_DEBUG, "---\n");
|
|
|
|
LOGP(DLGLOBAL, LOGL_DEBUG, "--- %s at %s\n", label, s->o[trigger]->fi->id);
|
|
|
|
|
|
|
|
if (by_destroy_event)
|
|
|
|
obj_destroy(s->o[trigger]);
|
|
|
|
else
|
|
|
|
obj_term(s->o[trigger]);
|
|
|
|
|
|
|
|
LOGP(DLGLOBAL, LOGL_DEBUG, "--- after %s cascade:\n", label);
|
|
|
|
remain = scene_dump(s);
|
|
|
|
if (remain) {
|
|
|
|
LOGP(DLGLOBAL, LOGL_DEBUG, "--- %d objects remain. cleaning up\n", remain);
|
|
|
|
} else
|
|
|
|
LOGP(DLGLOBAL, LOGL_DEBUG, "--- all deallocated.\n");
|
add osmo_fsm_set_dealloc_ctx(), to help with use-after-free
This is a simpler and more general solution to the problem so far solved by
osmo_fsm_term_safely(true). This extends use-after-free fixes to arbitrary
functions, not only FSM instances during termination.
The aim is to defer talloc_free() until back in the main loop.
Rationale: I discovered an osmo-msc use-after-free crash from an invalid
message, caused by this pattern:
void event_action()
{
osmo_fsm_inst_dispatch(foo, FOO_EVENT, NULL);
osmo_fsm_inst_dispatch(bar, BAR_EVENT, NULL);
}
Usually, FOO_EVENT takes successful action, and afterwards we also notify bar.
However, in this particular case, FOO_EVENT caused failure, and the immediate
error handling directly terminated and deallocated bar. In such a case,
dispatching BAR_EVENT causes a use-after-free; this constituted a DoS vector
just from sending messages that cause *any* failure during the first event
dispatch.
Instead, when this is enabled, we do not deallocate 'foo' until event_action()
has returned back to the main loop.
Test: duplicate fsm_dealloc_test.c using this, and print the number of items
deallocated in each test loop, to ensure the feature works. We also verify that
the deallocation safety works simply by fsm_dealloc_test.c not crashing.
We should probably follow up by refusing event dispatch and state transitions
for FSM instances that are terminating or already terminated:
see I0adc13a1a998e953b6c850efa2761350dd07e03a.
Change-Id: Ief4dba9ea587c9b4aea69993e965fbb20fb80e78
2019-10-04 18:37:17 +00:00
|
|
|
|
|
|
|
if (loop_ctx) {
|
|
|
|
fprintf(stderr, "*** loop_ctx contains %zu blocks, deallocating.\n",
|
|
|
|
talloc_total_blocks(loop_ctx));
|
|
|
|
talloc_free_children(loop_ctx);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Silently free the remaining objects. */
|
2019-03-23 22:38:43 +00:00
|
|
|
scene_clean(s);
|
add osmo_fsm_set_dealloc_ctx(), to help with use-after-free
This is a simpler and more general solution to the problem so far solved by
osmo_fsm_term_safely(true). This extends use-after-free fixes to arbitrary
functions, not only FSM instances during termination.
The aim is to defer talloc_free() until back in the main loop.
Rationale: I discovered an osmo-msc use-after-free crash from an invalid
message, caused by this pattern:
void event_action()
{
osmo_fsm_inst_dispatch(foo, FOO_EVENT, NULL);
osmo_fsm_inst_dispatch(bar, BAR_EVENT, NULL);
}
Usually, FOO_EVENT takes successful action, and afterwards we also notify bar.
However, in this particular case, FOO_EVENT caused failure, and the immediate
error handling directly terminated and deallocated bar. In such a case,
dispatching BAR_EVENT causes a use-after-free; this constituted a DoS vector
just from sending messages that cause *any* failure during the first event
dispatch.
Instead, when this is enabled, we do not deallocate 'foo' until event_action()
has returned back to the main loop.
Test: duplicate fsm_dealloc_test.c using this, and print the number of items
deallocated in each test loop, to ensure the feature works. We also verify that
the deallocation safety works simply by fsm_dealloc_test.c not crashing.
We should probably follow up by refusing event dispatch and state transitions
for FSM instances that are terminating or already terminated:
see I0adc13a1a998e953b6c850efa2761350dd07e03a.
Change-Id: Ief4dba9ea587c9b4aea69993e965fbb20fb80e78
2019-10-04 18:37:17 +00:00
|
|
|
if (loop_ctx)
|
|
|
|
talloc_free_children(loop_ctx);
|
2019-03-23 22:38:43 +00:00
|
|
|
}
|
|
|
|
|
add osmo_fsm_set_dealloc_ctx(), to help with use-after-free
This is a simpler and more general solution to the problem so far solved by
osmo_fsm_term_safely(true). This extends use-after-free fixes to arbitrary
functions, not only FSM instances during termination.
The aim is to defer talloc_free() until back in the main loop.
Rationale: I discovered an osmo-msc use-after-free crash from an invalid
message, caused by this pattern:
void event_action()
{
osmo_fsm_inst_dispatch(foo, FOO_EVENT, NULL);
osmo_fsm_inst_dispatch(bar, BAR_EVENT, NULL);
}
Usually, FOO_EVENT takes successful action, and afterwards we also notify bar.
However, in this particular case, FOO_EVENT caused failure, and the immediate
error handling directly terminated and deallocated bar. In such a case,
dispatching BAR_EVENT causes a use-after-free; this constituted a DoS vector
just from sending messages that cause *any* failure during the first event
dispatch.
Instead, when this is enabled, we do not deallocate 'foo' until event_action()
has returned back to the main loop.
Test: duplicate fsm_dealloc_test.c using this, and print the number of items
deallocated in each test loop, to ensure the feature works. We also verify that
the deallocation safety works simply by fsm_dealloc_test.c not crashing.
We should probably follow up by refusing event dispatch and state transitions
for FSM instances that are terminating or already terminated:
see I0adc13a1a998e953b6c850efa2761350dd07e03a.
Change-Id: Ief4dba9ea587c9b4aea69993e965fbb20fb80e78
2019-10-04 18:37:17 +00:00
|
|
|
static void trigger_tests(void *loop_ctx)
|
2019-03-23 22:38:43 +00:00
|
|
|
{
|
|
|
|
size_t ctx_blocks;
|
|
|
|
size_t ctx_size;
|
add osmo_fsm_set_dealloc_ctx(), to help with use-after-free
This is a simpler and more general solution to the problem so far solved by
osmo_fsm_term_safely(true). This extends use-after-free fixes to arbitrary
functions, not only FSM instances during termination.
The aim is to defer talloc_free() until back in the main loop.
Rationale: I discovered an osmo-msc use-after-free crash from an invalid
message, caused by this pattern:
void event_action()
{
osmo_fsm_inst_dispatch(foo, FOO_EVENT, NULL);
osmo_fsm_inst_dispatch(bar, BAR_EVENT, NULL);
}
Usually, FOO_EVENT takes successful action, and afterwards we also notify bar.
However, in this particular case, FOO_EVENT caused failure, and the immediate
error handling directly terminated and deallocated bar. In such a case,
dispatching BAR_EVENT causes a use-after-free; this constituted a DoS vector
just from sending messages that cause *any* failure during the first event
dispatch.
Instead, when this is enabled, we do not deallocate 'foo' until event_action()
has returned back to the main loop.
Test: duplicate fsm_dealloc_test.c using this, and print the number of items
deallocated in each test loop, to ensure the feature works. We also verify that
the deallocation safety works simply by fsm_dealloc_test.c not crashing.
We should probably follow up by refusing event dispatch and state transitions
for FSM instances that are terminating or already terminated:
see I0adc13a1a998e953b6c850efa2761350dd07e03a.
Change-Id: Ief4dba9ea587c9b4aea69993e965fbb20fb80e78
2019-10-04 18:37:17 +00:00
|
|
|
enum objname trigger;
|
2019-03-23 22:38:43 +00:00
|
|
|
int by_destroy_event;
|
|
|
|
|
add osmo_fsm_set_dealloc_ctx(), to help with use-after-free
This is a simpler and more general solution to the problem so far solved by
osmo_fsm_term_safely(true). This extends use-after-free fixes to arbitrary
functions, not only FSM instances during termination.
The aim is to defer talloc_free() until back in the main loop.
Rationale: I discovered an osmo-msc use-after-free crash from an invalid
message, caused by this pattern:
void event_action()
{
osmo_fsm_inst_dispatch(foo, FOO_EVENT, NULL);
osmo_fsm_inst_dispatch(bar, BAR_EVENT, NULL);
}
Usually, FOO_EVENT takes successful action, and afterwards we also notify bar.
However, in this particular case, FOO_EVENT caused failure, and the immediate
error handling directly terminated and deallocated bar. In such a case,
dispatching BAR_EVENT causes a use-after-free; this constituted a DoS vector
just from sending messages that cause *any* failure during the first event
dispatch.
Instead, when this is enabled, we do not deallocate 'foo' until event_action()
has returned back to the main loop.
Test: duplicate fsm_dealloc_test.c using this, and print the number of items
deallocated in each test loop, to ensure the feature works. We also verify that
the deallocation safety works simply by fsm_dealloc_test.c not crashing.
We should probably follow up by refusing event dispatch and state transitions
for FSM instances that are terminating or already terminated:
see I0adc13a1a998e953b6c850efa2761350dd07e03a.
Change-Id: Ief4dba9ea587c9b4aea69993e965fbb20fb80e78
2019-10-04 18:37:17 +00:00
|
|
|
ctx_blocks = talloc_total_blocks(ctx);
|
|
|
|
ctx_size = talloc_total_size(ctx);
|
|
|
|
|
|
|
|
for (trigger = 0; trigger < scene_size; trigger++) {
|
|
|
|
for (by_destroy_event = 0; by_destroy_event < 2; by_destroy_event++) {
|
|
|
|
test_dealloc(trigger, (bool)by_destroy_event, loop_ctx);
|
|
|
|
|
|
|
|
if (ctx_blocks != talloc_total_blocks(ctx)
|
|
|
|
|| ctx_size != talloc_total_size(ctx)) {
|
|
|
|
talloc_report_full(ctx, stderr);
|
|
|
|
OSMO_ASSERT(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void test_osmo_fsm_term_safely()
|
|
|
|
{
|
|
|
|
fprintf(stderr, "\n\n%s()\n", __func__);
|
|
|
|
osmo_fsm_term_safely(true);
|
|
|
|
trigger_tests(NULL);
|
|
|
|
osmo_fsm_term_safely(false);
|
|
|
|
fprintf(stderr, "\n\n%s() done\n", __func__);
|
|
|
|
}
|
|
|
|
|
|
|
|
void test_osmo_fsm_set_dealloc_ctx()
|
|
|
|
{
|
|
|
|
fprintf(stderr, "\n\n%s()\n", __func__);
|
|
|
|
void *dealloc_ctx = talloc_named_const(ctx, 0, "fsm_dealloc");
|
|
|
|
osmo_fsm_set_dealloc_ctx(dealloc_ctx);
|
|
|
|
trigger_tests(dealloc_ctx);
|
|
|
|
osmo_fsm_set_dealloc_ctx(NULL);
|
|
|
|
fprintf(stderr, "\n\n%s() done\n", __func__);
|
|
|
|
}
|
|
|
|
|
|
|
|
int main(void)
|
|
|
|
{
|
2019-03-23 22:38:43 +00:00
|
|
|
ctx = talloc_named_const(NULL, 0, "main");
|
|
|
|
osmo_init_logging2(ctx, NULL);
|
|
|
|
|
2021-02-18 18:25:44 +00:00
|
|
|
log_set_print_filename2(osmo_stderr_target, LOG_FILENAME_NONE);
|
2019-03-23 22:38:43 +00:00
|
|
|
log_set_print_level(osmo_stderr_target, 1);
|
|
|
|
log_set_print_category(osmo_stderr_target, 1);
|
|
|
|
log_set_print_category_hex(osmo_stderr_target, 0);
|
|
|
|
log_set_use_color(osmo_stderr_target, 0);
|
|
|
|
osmo_fsm_log_addr(false);
|
|
|
|
|
|
|
|
log_set_category_filter(osmo_stderr_target, DLGLOBAL, 1, LOGL_DEBUG);
|
|
|
|
|
2019-12-01 12:38:20 +00:00
|
|
|
OSMO_ASSERT(osmo_fsm_register(&test_fsm) == 0);
|
2019-03-23 22:38:43 +00:00
|
|
|
|
add osmo_fsm_set_dealloc_ctx(), to help with use-after-free
This is a simpler and more general solution to the problem so far solved by
osmo_fsm_term_safely(true). This extends use-after-free fixes to arbitrary
functions, not only FSM instances during termination.
The aim is to defer talloc_free() until back in the main loop.
Rationale: I discovered an osmo-msc use-after-free crash from an invalid
message, caused by this pattern:
void event_action()
{
osmo_fsm_inst_dispatch(foo, FOO_EVENT, NULL);
osmo_fsm_inst_dispatch(bar, BAR_EVENT, NULL);
}
Usually, FOO_EVENT takes successful action, and afterwards we also notify bar.
However, in this particular case, FOO_EVENT caused failure, and the immediate
error handling directly terminated and deallocated bar. In such a case,
dispatching BAR_EVENT causes a use-after-free; this constituted a DoS vector
just from sending messages that cause *any* failure during the first event
dispatch.
Instead, when this is enabled, we do not deallocate 'foo' until event_action()
has returned back to the main loop.
Test: duplicate fsm_dealloc_test.c using this, and print the number of items
deallocated in each test loop, to ensure the feature works. We also verify that
the deallocation safety works simply by fsm_dealloc_test.c not crashing.
We should probably follow up by refusing event dispatch and state transitions
for FSM instances that are terminating or already terminated:
see I0adc13a1a998e953b6c850efa2761350dd07e03a.
Change-Id: Ief4dba9ea587c9b4aea69993e965fbb20fb80e78
2019-10-04 18:37:17 +00:00
|
|
|
test_osmo_fsm_term_safely();
|
|
|
|
test_osmo_fsm_set_dealloc_ctx();
|
2019-03-23 22:38:43 +00:00
|
|
|
|
|
|
|
talloc_free(ctx);
|
|
|
|
return 0;
|
|
|
|
}
|