diff options
| author | Neels Hofmeyr <neels@hofmeyr.de> | 2019-03-23 23:38:43 +0100 | 
|---|---|---|
| committer | Neels Hofmeyr <nhofmeyr@sysmocom.de> | 2019-04-11 05:36:36 +0000 | 
| commit | 223d66a41455033e77e8c2cbb8170eaf0217b954 (patch) | |
| tree | 942da4d24ba98f1fc7d457f035a8adb05954fa7c /tests | |
| parent | ed8e26309640f1ef9d9b17453e62e17f535b5029 (diff) | |
add fsm_dealloc_test.c
Despite efforts to properly handle "GONE" events and entering a ST_DESTROYING
only once, so far this test runs straight into a heap use-after-free. With
current fsm.c, it is hard to resolve the situation with the objects named
"other" also causing deallocations besides the FSM instance parent/child
relations.
For illustration, add an "expected" test output file fsm_dealloc_test.err,
making this pass will follow in a subsequent patch.
Change-Id: If801907c541bca9f524c9e5fd22ac280ca16979a
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/Makefile.am | 9 | ||||
| -rw-r--r-- | tests/fsm/fsm_dealloc_test.c | 476 | ||||
| -rw-r--r-- | tests/fsm/fsm_dealloc_test.err | 122 | 
3 files changed, 606 insertions, 1 deletions
| diff --git a/tests/Makefile.am b/tests/Makefile.am index d123ee2a..09a1c189 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -51,7 +51,11 @@ check_PROGRAMS += vty/vty_test  endif  if ENABLE_CTRL -check_PROGRAMS += ctrl/ctrl_test fsm/fsm_test +check_PROGRAMS += \ +	ctrl/ctrl_test \ +	fsm/fsm_test \ +	fsm/fsm_dealloc_test \ +	$(NULL)  endif  if ENABLE_STATS_TEST @@ -207,6 +211,9 @@ fsm_fsm_test_LDADD = \  	$(top_builddir)/src/gsm/libosmogsm.la \  	$(top_builddir)/src/vty/libosmovty.la +fsm_fsm_dealloc_test_SOURCES = fsm/fsm_dealloc_test.c +fsm_fsm_dealloc_test_LDADD = $(LDADD) +  write_queue_wqueue_test_SOURCES = write_queue/wqueue_test.c  socket_socket_test_SOURCES = socket/socket_test.c diff --git a/tests/fsm/fsm_dealloc_test.c b/tests/fsm/fsm_dealloc_test.c new file mode 100644 index 00000000..5a493ad2 --- /dev/null +++ b/tests/fsm/fsm_dealloc_test.c @@ -0,0 +1,476 @@ +/* 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, +	ST_DESTROYING, +}; + +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 */ +			osmo_fsm_inst_state_chg(fi, ST_DESTROYING, 0, 0); +		} +		break; + +	case EV_CHILD_GONE: +		if (!child_gone(obj, data)) { +			/* All children are gone. Deallocate. */ +			osmo_fsm_inst_state_chg(fi, ST_DESTROYING, 0, 0); +		} +		break; + +	case EV_DESTROY: +		osmo_fsm_inst_state_chg(fi, ST_DESTROYING, 0, 0); +		break; + +	default: +		OSMO_ASSERT(false); +	} +	PUT(); +} + +void destroying_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ +	struct obj *obj = fi->priv; +	GET(); +	LOGPFSML(fi, LOGL_DEBUG, "%s() from %s\n", __func__, osmo_fsm_state_name(fi->fsm, prev_state)); +	osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, 0); +	PUT(); +} + +void destroying(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: +		other_gone(obj, data); +		break; + +	case EV_CHILD_GONE: +		child_gone(obj, data); +		break; + +	case EV_DESTROY: +		LOGPFSML(fi, LOGL_DEBUG, "already destroying\n"); +		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) +			| S(ST_DESTROYING) +			, +		.onenter = alive_onenter, +		.action = alive, +	}, +	[ST_DESTROYING] = { +		.name = "destroying", +		.in_event_mask = 0 +			| S(EV_CHILD_GONE) +			| S(EV_OTHER_GONE) +			| S(EV_DESTROY) +			, +		.out_state_mask = 0 +			| S(ST_DESTROYING) +			, +		.onenter = destroying_onenter, +		.action = destroying, +	}, +}; + +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]); + +	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); +} + +void test_dealloc(enum objname trigger, bool by_destroy_event) +{ +	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"); +	scene_clean(s); +} + +int main(void) +{ +	enum objname trigger; +	size_t ctx_blocks; +	size_t ctx_size; +	int by_destroy_event; + +	ctx = talloc_named_const(NULL, 0, "main"); +	osmo_init_logging2(ctx, NULL); + +	log_set_print_filename(osmo_stderr_target, 0); +	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); + +	osmo_fsm_register(&test_fsm); + +	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); +			if (ctx_blocks != talloc_total_blocks(ctx) +			    || ctx_size != talloc_total_size(ctx)) { +				talloc_report_full(ctx, stderr); +				OSMO_ASSERT(false); +			} +		} +	} + +	talloc_free(ctx); +	return 0; +} diff --git a/tests/fsm/fsm_dealloc_test.err b/tests/fsm/fsm_dealloc_test.err new file mode 100644 index 00000000..1719677b --- /dev/null +++ b/tests/fsm/fsm_dealloc_test.err @@ -0,0 +1,122 @@ +DLGLOBAL DEBUG scene_alloc() +DLGLOBAL DEBUG test(_branch0){alive}: Allocated +DLGLOBAL DEBUG test(_branch0){alive}: Allocated +DLGLOBAL DEBUG test(_branch0){alive}: is child of test(_branch0) +DLGLOBAL DEBUG test(other){alive}: Allocated +DLGLOBAL DEBUG test(_branch0){alive}: _branch0.other[0] = other +DLGLOBAL DEBUG test(other){alive}: other.other[0] = _branch0 +DLGLOBAL DEBUG test(__twig0a){alive}: __twig0a.other[0] = other +DLGLOBAL DEBUG test(other){alive}: other.other[1] = __twig0a +DLGLOBAL DEBUG --- Test disabled: object 0 was not created. Cleaning up. +DLGLOBAL DEBUG test(_branch0){alive}: Terminating (cause = OSMO_FSM_TERM_ERROR) +DLGLOBAL DEBUG test(_branch0){alive}: pre_term() +DLGLOBAL DEBUG test(__twig0a){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT) +DLGLOBAL DEBUG test(__twig0a){alive}: pre_term() +DLGLOBAL DEBUG test(__twig0a){alive}: Removing from parent test(_branch0) +DLGLOBAL DEBUG 1 (__twig0a.cleanup()) +DLGLOBAL DEBUG test(__twig0a){alive}: cleanup() +DLGLOBAL DEBUG test(__twig0a){alive}: scene forgets __twig0a +DLGLOBAL DEBUG test(__twig0a){alive}: removing reference __twig0a.other[0] -> other +DLGLOBAL DEBUG test(other){alive}: Received Event EV_OTHER_GONE +DLGLOBAL DEBUG 2 (__twig0a.cleanup(),other.alive()) +DLGLOBAL DEBUG test(other){alive}: alive(EV_OTHER_GONE) +DLGLOBAL DEBUG 3 (__twig0a.cleanup(),other.alive(),other.other_gone()) +DLGLOBAL DEBUG test(other){alive}: EV_OTHER_GONE: Dropped reference other.other[1] = __twig0a +DLGLOBAL DEBUG 2 (__twig0a.cleanup(),other.alive()) +DLGLOBAL DEBUG test(other){alive}: state_chg to destroying +DLGLOBAL DEBUG 3 (__twig0a.cleanup(),other.alive(),other.destroying_onenter()) +DLGLOBAL DEBUG test(other){destroying}: destroying_onenter() from alive +DLGLOBAL DEBUG test(other){destroying}: Terminating (cause = OSMO_FSM_TERM_REGULAR) +DLGLOBAL DEBUG test(other){destroying}: pre_term() +DLGLOBAL DEBUG 4 (__twig0a.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup()) +DLGLOBAL DEBUG test(other){destroying}: cleanup() +DLGLOBAL DEBUG test(other){destroying}: scene forgets other +DLGLOBAL DEBUG test(other){destroying}: removing reference other.other[0] -> _branch0 +DLGLOBAL DEBUG test(_branch0){alive}: Received Event EV_OTHER_GONE +DLGLOBAL DEBUG 5 (__twig0a.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive()) +DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_OTHER_GONE) +DLGLOBAL DEBUG 6 (__twig0a.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.other_gone()) +DLGLOBAL DEBUG test(_branch0){alive}: EV_OTHER_GONE: Dropped reference _branch0.other[0] = other +DLGLOBAL DEBUG 5 (__twig0a.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive()) +DLGLOBAL DEBUG test(_branch0){alive}: state_chg to destroying +DLGLOBAL DEBUG 6 (__twig0a.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter()) +DLGLOBAL DEBUG test(_branch0){destroying}: destroying_onenter() from alive +DLGLOBAL DEBUG test(_branch0){destroying}: Terminating (cause = OSMO_FSM_TERM_REGULAR) +DLGLOBAL DEBUG test(_branch0){destroying}: pre_term() +DLGLOBAL DEBUG 7 (__twig0a.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_ +DLGLOBAL DEBUG test(_branch0){destroying}: cleanup() +DLGLOBAL DEBUG test(_branch0){destroying}: scene forgets _branch0 +DLGLOBAL DEBUG test(_branch0){destroying}: cleanup() done +DLGLOBAL DEBUG 6 (__twig0a.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter()) +DLGLOBAL DEBUG test(_branch0){destroying}: Freeing instance +DLGLOBAL DEBUG test(_branch0){destroying}: Deallocated +DLGLOBAL DEBUG 5 (__twig0a.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive()) +DLGLOBAL DEBUG 4 (__twig0a.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup()) +DLGLOBAL DEBUG test(other){destroying}: cleanup() done +DLGLOBAL DEBUG 3 (__twig0a.cleanup(),other.alive(),other.destroying_onenter()) +DLGLOBAL DEBUG test(other){destroying}: Freeing instance +DLGLOBAL DEBUG test(other){destroying}: Deallocated +================================================================= +==12545==ERROR: AddressSanitizer: heap-use-after-free on address 0x6120000003a8 at pc 0x7fa96fdc9149 bp 0x7fff6045b000 sp 0x7fff6045aff8 +WRITE of size 8 at 0x6120000003a8 thread T0 +    #0 0x7fa96fdc9148 in __llist_del ../../../src/libosmocore/include/osmocom/core/linuxlist.h:114 +    #1 0x7fa96fdc9280 in llist_del ../../../src/libosmocore/include/osmocom/core/linuxlist.h:126 +    #2 0x7fa96fdcddaa in osmo_fsm_inst_free ../../../src/libosmocore/src/fsm.c:404 +    #3 0x7fa96fdd599c in _osmo_fsm_inst_term ../../../src/libosmocore/src/fsm.c:738 +    #4 0x55dde97cb9e3 in destroying_onenter ../../../src/libosmocore/tests/fsm/fsm_dealloc_test.c:177 +    #5 0x7fa96fdd26be in state_chg ../../../src/libosmocore/src/fsm.c:521 +    #6 0x7fa96fdd2770 in _osmo_fsm_inst_state_chg ../../../src/libosmocore/src/fsm.c:577 +    #7 0x55dde97cb2e6 in alive ../../../src/libosmocore/tests/fsm/fsm_dealloc_test.c:151 +    #8 0x7fa96fdd3d2f in _osmo_fsm_inst_dispatch ../../../src/libosmocore/src/fsm.c:685 +    #9 0x55dde97cd0ee in cleanup ../../../src/libosmocore/tests/fsm/fsm_dealloc_test.c:255 +    #10 0x7fa96fdd5192 in _osmo_fsm_inst_term ../../../src/libosmocore/src/fsm.c:733 +    #11 0x7fa96fdd60b1 in _osmo_fsm_inst_term_children ../../../src/libosmocore/src/fsm.c:784 +    #12 0x7fa96fdd475e in _osmo_fsm_inst_term ../../../src/libosmocore/src/fsm.c:720 +    #13 0x55dde97cf5d5 in scene_clean ../../../src/libosmocore/tests/fsm/fsm_dealloc_test.c:392 +    #14 0x55dde97cf92f in test_dealloc ../../../src/libosmocore/tests/fsm/fsm_dealloc_test.c:417 +    #15 0x55dde97cfffe in main ../../../src/libosmocore/tests/fsm/fsm_dealloc_test.c:465 +    #16 0x7fa96eff409a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2409a) +    #17 0x55dde97c7329 in _start (/n/s/dev/make/libosmocore/tests/fsm/fsm_dealloc_test+0x11329) + +0x6120000003a8 is located 104 bytes inside of 288-byte region [0x612000000340,0x612000000460) +freed by thread T0 here: +    #0 0x7fa970061b50 in free (/usr/lib/x86_64-linux-gnu/libasan.so.5+0xe8b50) +    #1 0x7fa96fcbd5d2  (/usr/lib/x86_64-linux-gnu/libtalloc.so.2+0xb5d2) + +previously allocated by thread T0 here: +    #0 0x7fa970061ed0 in __interceptor_malloc (/usr/lib/x86_64-linux-gnu/libasan.so.5+0xe8ed0) +    #1 0x7fa96fcbb140 in _talloc_zero (/usr/lib/x86_64-linux-gnu/libtalloc.so.2+0x9140) + +SUMMARY: AddressSanitizer: heap-use-after-free ../../../src/libosmocore/include/osmocom/core/linuxlist.h:114 in __llist_del +Shadow bytes around the buggy address: +  0x0c247fff8020: 00 00 00 00 00 00 00 00 00 fa fa fa fa fa fa fa +  0x0c247fff8030: fa fa fa fa fa fa fa fa fd fd fd fd fd fd fd fd +  0x0c247fff8040: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd +  0x0c247fff8050: fd fd fd fd fd fd fd fd fd fd fd fd fa fa fa fa +  0x0c247fff8060: fa fa fa fa fa fa fa fa fd fd fd fd fd fd fd fd +=>0x0c247fff8070: fd fd fd fd fd[fd]fd fd fd fd fd fd fd fd fd fd +  0x0c247fff8080: fd fd fd fd fd fd fd fd fd fd fd fd fa fa fa fa +  0x0c247fff8090: fa fa fa fa fa fa fa fa 00 00 00 00 00 00 00 00 +  0x0c247fff80a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +  0x0c247fff80b0: 00 00 00 00 00 00 00 00 00 00 00 00 fa fa fa fa +  0x0c247fff80c0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa +Shadow byte legend (one shadow byte represents 8 application bytes): +  Addressable:           00 +  Partially addressable: 01 02 03 04 05 06 07  +  Heap left redzone:       fa +  Freed heap region:       fd +  Stack left redzone:      f1 +  Stack mid redzone:       f2 +  Stack right redzone:     f3 +  Stack after return:      f5 +  Stack use after scope:   f8 +  Global redzone:          f9 +  Global init order:       f6 +  Poisoned by user:        f7 +  Container overflow:      fc +  Array cookie:            ac +  Intra object redzone:    bb +  ASan internal:           fe +  Left alloca redzone:     ca +  Right alloca redzone:    cb +==12545==ABORTING | 
