[cfe-dev] [Analyzer] Equality of pointers

Ádám Balogh via cfe-dev cfe-dev at lists.llvm.org
Tue Jun 4 05:53:10 PDT 2019


Hello,

This is not the first time that we are facing the following problem.

Given a code that clears all non-sentinel elements from a circular double-linked list with sentinel element:

typedef struct ListNode {
  struct ListNode *next;
  struct ListNode *prev;
  void *data;
} ListNode;

void ListClear(ListNode *list) {
  while (list->next != list) {
    ListNode *node = list->next;

    node->next->prev = node->prev;
    node->prev->next = node->next;

    node->next = NULL;
    node->prev = NULL;

    free(node);
  }
}

unix.Malloc Use after free is reported in the second iteration of the loop for `node`.

This is not necessarily a false positive if analyzed top level. The function that clears the nodes of the list implicitly assumes that it is a consistent circular double-linked list with a sentinel node. However this invariant is not ensured anywhere inside the function. It is thus not known that the `prev` pointer of the first non-sentinel node points back to the sentinel node, neither is it known that applying a `prev` and a `next` after each other in any order leads back to the original node. Without these invariants the analyzer cannot know that after unlinking and freeing the first non-sentinel element and then taking again the first non-sentinel element is not equal to the original sentinel element. So it assumes that they are equal and used again after freeing its memory space. Thus from the perspective of the standalone function it can be a use-after-free situation.

So I fixed the function by inserting assertions to ensure the invariant:

void ListClear(ListNode *list) {
  while (list->next != list) {
    ListNode *node = list->next;

    assert(node->prev == list);
    assert(node->next->prev == node);
    assert(node->prev->next == node);

    node->next->prev = node->prev;
    node->prev->next = node->next;

    node->next = NULL;
    node->prev = NULL;

    free(node);
  }
}

However, it did not help because the analyzer cannot handle equality of pointers correctly. If we have an assumption that two pointers are equal the analyzer still creates two symbolic regions and records a constraint that the symbols of the symbolic regions are equal. Later this constraint can be used for deciding whether they are equal or not but it does not handle them as the same region. So if we change the pointer `node->prev->next` to `node->next` then `node->prev->next` is changed but `list->next` not even if we assumed that they are the same. That is the reason the analyzer takes the freed region again in the next iteration of the loop and reports an error.

A strange thing is that the constraint manager rearranges the comparison of the two symbols (of the symbolic regions) to a difference without `aggressive-binary-operation-simplification` disabled. (It should not do it even if this option is enabled since the ranges of symbolic values are not constrained to `MAX/4` of the type.)

The question is how to fix this. I think that if two pointers are the same, then they must point to exactly the same memory region which means exactly the same symbol if it is a symbolic region. Thus instead of assigning `$reg2` to `node->prev` and recording whether the range of `$reg2-$reg0` is `[0..0]` the analyzer should assign `$reg0` to `node->prev` which is the region of `list`. Similarly, `node->next->prev` should not become `$reg7` where the range of `$reg7-$reg1` is `[0..0]` and `node->prev->next ` should not become `$reg11` where the range of `$reg11-$reg1` is `[0..0]` but both should become `$reg1`. This way when we unlink `node` `node->next->prev` becomes `reg0` (thus the region of `list`) and `node->prev->next` e.g. `$reg2`. This way in the next iteration `node->next` is `$reg2` instead of the freed `$reg1` so the false warning goes away. Of course I know this is not easy to implement. If both pointers already have regions assigned then which one to chose and how to replace the other one?

Another solution could be iterating all the existing regions upon bindings and do the same binding to the regions equal to the affected region. (I hope I managed to express myself clearly enough here.)

This is an old weakness of the Analyzer which should be solved eventually because it causes false positives. A new constraint manager does not help because the problem is not there.

Any ideas?

Regards,

Ádám



-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/cfe-dev/attachments/20190604/ddda2bca/attachment.html>


More information about the cfe-dev mailing list