On Thu, Oct 05, 2017 at 06:43:23PM +0000, Waiman Long wrote:
[...]
> +/*
> + * Find the first entry of the next available list.
> + */
> +extern struct dlock_list_node *
> +__dlock_list_next_list(struct dlock_list_iter *iter);
> +
> +/**
> + * __dlock_list_next_entry - Iterate to the next entry of the dlock list
> + * @curr : Pointer to the current dlock_list_node structure
> + * @iter : Pointer to the dlock list iterator structure
> + * Return: Pointer to the next entry or NULL if all the entries are iterated
> + *
> + * The iterator has to be properly initialized before calling this function.
> + */
> +static inline struct dlock_list_node *
> +__dlock_list_next_entry(struct dlock_list_node *curr,
> +                     struct dlock_list_iter *iter)
> +{
> +     /*
> +      * Find next entry
> +      */
> +     if (curr)
> +             curr = list_next_entry(curr, list);
> +
> +     if (!curr || (&curr->list == &iter->entry->list)) {
> +             /*
> +              * The current list has been exhausted, try the next available
> +              * list.
> +              */
> +             curr = __dlock_list_next_list(iter);
> +     }
> +
> +     return curr;    /* Continue the iteration */
> +}
> +
> +/**
> + * dlock_list_first_entry - get the first element from a list
> + * @iter  : The dlock list iterator.
> + * @type  : The type of the struct this is embedded in.
> + * @member: The name of the dlock_list_node within the struct.
> + * Return : Pointer to the next entry or NULL if all the entries are 
> iterated.
> + */
> +#define dlock_list_first_entry(iter, type, member)                   \
> +     ({                                                              \
> +             struct dlock_list_node *_n;                             \
> +             _n = __dlock_list_next_entry(NULL, iter);               \
> +             _n ? list_entry(_n, type, member) : NULL;               \
> +     })
> +
> +/**
> + * dlock_list_next_entry - iterate to the next entry of the list
> + * @pos   : The type * to cursor
> + * @iter  : The dlock list iterator.
> + * @member: The name of the dlock_list_node within the struct.
> + * Return : Pointer to the next entry or NULL if all the entries are 
> iterated.
> + *
> + * Note that pos can't be NULL.
> + */
> +#define dlock_list_next_entry(pos, iter, member)                     \
> +     ({                                                              \
> +             struct dlock_list_node *_n;                             \
> +             _n = __dlock_list_next_entry(&(pos)->member, iter);     \
> +             _n ? list_entry(_n, typeof(*(pos)), member) : NULL;     \
> +     })
> +

[...]

> +/**
> + * dlist_for_each_entry_safe - iterate over the dlock list & safe over 
> removal
> + * @pos   : Type * to use as a loop cursor
> + * @n          : Another type * to use as temporary storage
> + * @iter  : The dlock list iterator
> + * @member: The name of the dlock_list_node within the struct
> + *
> + * This iteration macro is safe with respect to list entry removal.
> + * However, it cannot correctly iterate newly added entries right after the
> + * current one.
> + */
> +#define dlist_for_each_entry_safe(pos, n, iter, member)                      
> \

So I missed something interesting here ;-)

> +     for (pos = dlock_list_first_entry(iter, typeof(*(pos)), member);\
> +         ({                                                          \
> +             bool _b = (pos != NULL);                                \
> +             if (_b)                                                 \
> +                     n = dlock_list_next_entry(pos, iter, member);   \

If @pos is the last item of the list of the index/cpu, and
dlock_list_next_entry() will eventually call __dlock_list_next_list(),
which will drop the lock for the current list and grab the lock for the
next list, leaving @pos unprotected. But in the meanwhile, there could
be another thread deleting @pos via dlock_lists_del() and freeing it.
This is a use-after-free.

I think we can have something like:

        (by adding a ->prev_entry in dlock_list_iter and severl helper
        functions.)

        bool dlist_is_last_perlist(struct dlock_list_node *n)
        {
                return list_is_last(&n->list, &n->head->list);
        
        }

        void dlock_list_release_prev(struct dlock_list_iter *iter)
        {
                spin_unlock(iter->prev_entry->lock);
                iter->prev_entry = NULL;
        }

        #define dlist_for_each_entry_safe(pos, n, iter, member)         \
                for (pos = dlock_list_first_entry(iter, typeof(*(pos)), 
member);        \
                    ({                                                          
        \
                        bool _b = (pos != NULL);                                
        \
                        if (_b) {                                               
        \
                                if (dlist_is_last_perlist(&(pos)->member)) {    
        \
                                        iter->prev_entry = iter->entry;         
        \
                                        iter->entry = NULL;                     
        \
                                        n = dlock_list_first_entry(NULL, iter, 
member); \
                                }                                               
        \
                                else                                            
        \
                                        n = dlock_list_next_entry(pos, iter, 
member);   \
                        }                                                       
        \
                        _b;                                                     
        \
                    });                                                         
        \
                    pos = n, iter->prev_entry && dlock_list_release_prev(iter))

Of course, the dlock_list_first_entry() here may need a better name ;-)

Thoughts?

Regards,
Boqun

> +             _b;                                                     \
> +         });                                                         \
> +         pos = n)
> +
> +#endif /* __LINUX_DLOCK_LIST_H */

[...]

> +/**
> + * __dlock_list_next_list: Find the first entry of the next available list
> + * @dlist: Pointer to the dlock_list_heads structure
> + * @iter : Pointer to the dlock list iterator structure
> + * Return: true if the entry is found, false if all the lists exhausted
> + *
> + * The information about the next available list will be put into the 
> iterator.
> + */
> +struct dlock_list_node *__dlock_list_next_list(struct dlock_list_iter *iter)
> +{
> +     struct dlock_list_node *next;
> +     struct dlock_list_head *head;
> +
> +restart:
> +     if (iter->entry) {
> +             spin_unlock(&iter->entry->lock);
> +             iter->entry = NULL;
> +     }
> +
> +next_list:
> +     /*
> +      * Try next list
> +      */
> +     if (++iter->index >= nr_cpu_ids)
> +             return NULL;    /* All the entries iterated */
> +
> +     if (list_empty(&iter->head[iter->index].list))
> +             goto next_list;
> +
> +     head = iter->entry = &iter->head[iter->index];
> +     spin_lock(&head->lock);
> +     /*
> +      * There is a slight chance that the list may become empty just
> +      * before the lock is acquired. So an additional check is
> +      * needed to make sure that a valid node will be returned.
> +      */
> +     if (list_empty(&head->list))
> +             goto restart;
> +
> +     next = list_entry(head->list.next, struct dlock_list_node,
> +                       list);
> +     WARN_ON_ONCE(next->head != head);
> +
> +     return next;
> +}
> -- 
> 1.8.3.1
> 

Attachment: signature.asc
Description: PGP signature

Reply via email to