Bug 270283 - would like an even safer LIST_FOREACH_SAFE()
Summary: would like an even safer LIST_FOREACH_SAFE()
Status: New
Alias: None
Product: Base System
Classification: Unclassified
Component: kern (show other bugs)
Version: CURRENT
Hardware: Any Any
: --- Affects Only Me
Assignee: freebsd-bugs (Nobody)
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2023-03-17 10:23 UTC by John Levon
Modified: 2023-03-17 11:26 UTC (History)
2 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description John Levon 2023-03-17 10:23:57 UTC
We hit the following situation:

 - we have a component with a list of callbacks.
 - those callbacks are themselves allowed to call back in and remove entries from the list
 - LIST_FOREACH_SAFE() is used to safely protect against removal of the current item
 - however, a callback is also legitimately allowed to remove any other item on the list

This falls down when a callback removes the *next* item on the list - the macro has already saved this in "tvar", so it will then try to use freed memory on the next iteration.

We have fixed this with LIST_FOREACH_SAFER() / LIST_REMOVE_SAFER() variants:

#define	LIST_FOREACH_SAFER(var, head, field, tvarp)	\	
	for ((var) = LIST_FIRST((head));				        \
	    (var) && ((*tvarp) = LIST_NEXT((var), field), 1);		\
	    (var) = (*tvarp))

#define	LIST_REMOVE_SAFER(elm, field, elmp) do {		\
	if (elmp == elm) {						                \
		elmp = LIST_NEXT(elm, field);				\
	};								                        \
	LIST_REMOVE(elm, field);					        \
} while (0)


Would like thoughts on whether this would be something more widely useful before I prepare a PR and so on, thanks.
Comment 1 Konstantin Belousov freebsd_committer freebsd_triage 2023-03-17 11:26:49 UTC
In such situation, where you have a possibility of uncontrolled removal of the
list elements, usual approach is to put a marker element on the list. The
marker should be not subject for removal by any regular operations.

See e.g. MNT_VNODE_FOREACH* and vm_pageout.c (search for marker).