[PATCH 1/8] cyclic: Prevent corruption of cyclic list on reassignment

Stefan Roese sr at denx.de
Wed Jan 22 10:25:02 CET 2025


Hi Marek,
Hi Rasmus,

On 20.01.25 10:17, Rasmus Villemoes wrote:
> On Sat, Jan 18 2025, Marek Vasut <marek.vasut+renesas at mailbox.org> wrote:
> 
>> Make cyclic_register() return error code, 0 in case of success,
>> -EALREADY in case the called attempts to re-register already
>> registered struct cyclic_info. The re-registration would lead
>> to corruption of gd->cyclic_list because the re-registration
>> would memset() one of its nodes, prevent that. Unregister only
>> initialized struct cyclic_info.
> 
> I had considered something like this, but I don't like it, because it
> relies on the cyclic structure (or more likely whatever structure it is
> embedded in) being initially zero-initialized. And if the caller doesn't
> know whether the cyclic_info is already registered or not, he can't do a
> memset() of it.
> 
> So my preference would be that we instead simply iterate the current
> list to see if the struct cyclic_info is already registered that
> way. Also, I think I'd prefer if double cyclic_register() is allowed and
> always succeeds; this could be used to change the period of an already
> registered instance, for example. Also, that avoids making the
> interfaces fallible.
> 
> And cyclic_unregister() could similarly just check
> whether the passed pointer is already on the list, and be a no-op in
> case it's not. Those extra list traversals are not expensive (we're
> traversing them thousands of times per second anyway in cyclic_run), and
> I doubt one would ever has more than about 10 items on the list.
> 
> IOW, I'd suggest adding an internal
> 
> bool cyclic_is_registered(struct cyclic_info *info)
> {
>    struct cyclic_info *c;
>    hlist_for_each(...) if (c == info) return true;
>    return false;
> }
> 
> add
> 
>    if (!cyclic_is_registered(c))
>      return;
> 
> to cyclic_unregister(), and have cyclic_register() unconditionally start
> by a
> 
>    cyclic_unregister(c);
> 
> and then proceed to initialize it as it does currently. No other
> changes, apart from documentation, would be needed.
>      
> 
> 
>>   common/cyclic.c  | 14 +++++++++++---
>>   include/cyclic.h |  9 ++++++---
>>   2 files changed, 17 insertions(+), 6 deletions(-)
>>
>> diff --git a/common/cyclic.c b/common/cyclic.c
>> index 196797fd61e..53156a704cc 100644
>> --- a/common/cyclic.c
>> +++ b/common/cyclic.c
>> @@ -27,9 +27,13 @@ struct hlist_head *cyclic_get_list(void)
>>   	return (struct hlist_head *)&gd->cyclic_list;
>>   }
>>   
>> -void cyclic_register(struct cyclic_info *cyclic, cyclic_func_t func,
>> -		     uint64_t delay_us, const char *name)
>> +int cyclic_register(struct cyclic_info *cyclic, cyclic_func_t func,
>> +		    uint64_t delay_us, const char *name)
>>   {
>> +	/* Reassignment of function would corrupt cyclic list, exit */
>> +	if (cyclic->func)
>> +		return -EALREADY;
>> +
>>   	memset(cyclic, 0, sizeof(*cyclic));
>>   
>>   	/* Store values in struct */
>> @@ -38,11 +42,15 @@ void cyclic_register(struct cyclic_info *cyclic, cyclic_func_t func,
>>   	cyclic->delay_us = delay_us;
>>   	cyclic->start_time_us = timer_get_us();
>>   	hlist_add_head(&cyclic->list, cyclic_get_list());
>> +
>> +	return 0;
>>   }
>>   
>>   void cyclic_unregister(struct cyclic_info *cyclic)
>>   {
>> -	hlist_del(&cyclic->list);
>> +	/* Unregister only initialized struct cyclic_info */
>> +	if (cyclic->func)
>> +		hlist_del(&cyclic->list);
>>   }
> 
> So this already shows how error prone this approach is. You are not
> clearing cyclic->func, so if the caller subsequently tries to register
> that struct again, he would get -EALREADY, while a subsequent unregister
> could would lead to exactly the list corruption you want to avoid.
> 
> And unless the caller immediately himself clears ->func, other code in
> the client cannot rely on ->func being NULL or not as a proxy for
> whether the struct is already registered (and the caller shouldn't do
> either of those things, as the struct cyclic_info should be considered
> opaque).

I like the approach suggest by Rasmus. Marek, do you see any flaws using
this version? If not, please send an updated version.

Thanks,
Stefan



More information about the U-Boot mailing list