3

I have code using container_of macros to get parent structure pointer from inner structure pointer. However, it triggers the clang-tidy security.ArrayBound warning.

For instance the following code

#include <stdio.h>
#include <stddef.h>
#include <assert.h>


#define container_of(ptr, type, member) ({ \
    const void *__mptr = (void *)(ptr); \
    (type *)((char *)__mptr - offsetof(type, member)); \
})

/* Simple inner structure */
struct inner_data {
    int id;
    float value;
};

/* Container structure that includes the inner structure */
struct container {
    double extra_value;
    char name[32];
    int count;
    struct inner_data data;  /* Embedded inner structure */
};

/* Function that works with the inner structure */
void update_data(struct inner_data *data, float new_value) {
    /* Get access to the container using container_of */
    struct container *parent = container_of(data, struct container, data);

    /* Now we can update container fields */
    parent->extra_value = new_value * 2.0;
}

int main()
{
    struct container my_container;
    struct inner_data * mydata = &my_container.data;
    update_data(mydata, 200.0);
}

Generates the following warning :

clang-tidy-21 -checks=clang-analyzer-* test.c -- -std=c11 -O2
1 warning generated.
/tmp/test.c:31:13: warning: Out of bound access to memory preceding 'my_container.data' [clang-analyzer-security.ArrayBound]
   31 |     parent->extra_value = new_value * 2.0;
      |             ^
/tmp/test.c:38:5: note: Calling 'update_data'
   38 |     update_data(mydata, 200.0);
      |     ^~~~~~~~~~~~~~~~~~~~~~~~~~
/tmp/test.c:31:13: note: Access of 'my_container.data' at negative byte offset -44
   31 |     parent->extra_value = new_value * 2.0;
      |     ~~~~~~~~^~~~~~~~~~~
4

2 Answers 2

1

I have code using container_of macros to get parent structure pointer from inner structure pointer. However, it triggers the clang-tidy security.ArrayBound warning.

That seems perfectly reasonable for the container_of macro you present ...

#define container_of(ptr, type, member) ({ \
    const void *__mptr = (void *)(ptr); \
    (type *)((char *)__mptr - offsetof(type, member)); \
})

... when when member is not the first member of type, such that its offset is greater than zero. Converting ptr to type void * or (on to) type char * does not make it well defined to use the resulting pointer to compute an address before the beginning of *ptr, much less to access anything using such a derived pointer.

Moreover, in a function such as your update_data(), the compiler can't even determine from the context of the container_of use whether the purported member pointer actually does point to a member of a containing structure of the type you specify. If on some call it does not, then you're that much more so in the weeds.

All of this is excellent reason for Clang to warn.

That some projects nevertheless use such a macro is no reason not to warn. Even if such a project were willing to be dependent on its C implementation to do the expected thing with that macro in cases where it really is fed a pointer to a member of an object of the designated container type, that does not resolve the likelihood of misbehavior when the provided pointer in fact does not point to such a member.

If you want to avoid such a warning then the best alternative is to avoid a need for container_of functionality. At least for members other than the first, which can be served by a plain pointer cast. Since the first-member case is less problematic, it may be that you can work around the issue by reorganizing your structure layouts.

If you already have a commitment to container_of that you cannot easily get out of, and your only concern is the warnings, then your best alternative is to disable that particular warning, at least for the source files that use container_of.

Sign up to request clarification or add additional context in comments.

Comments

1

I used the __clang_analyzer__ macro to trick the analyzer into not computing the pointer offset.

#ifdef __clang_analyzer__
#define container_of(ptr, type, member) ({ void *__consume(void *p); (type *)__consume(ptr); })
#else
#define container_of(ptr, type, member) ({              \
    const typeof(((type *)0)->member) * __mptr = (ptr); \
    (type *)((char *)__mptr - offsetof(type, member)); })
#endif

To me, the decoupling possibility offered by container_of are worth the associated misuse risk.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.