> On Jan 29, 2024, at 12:25 PM, Kees Cook <[email protected]> wrote:
>
> On Mon, Jan 29, 2024 at 04:00:20PM +0000, Qing Zhao wrote:
>> An update on the kernel building with my version 4 patch.
>>
>> Kees reported two FE issues with the current version 4 patch:
>>
>> 1. The operator “typeof” cannot return correct type for a->array;
>> 2. The operator “&” cannot return correct address for a->array;
>>
>> I fixed both in my local repository.
>>
>> With these additional fix. Kernel with counted-by annotation can be built
>> successfully.
>
> Thanks for the fixes!
>
>>
>> And then, Kees reported one behavioral issue with the current counted-by:
>>
>> When the counted-by value is below zero, my current patch
>>
>> A. Didn’t report any warning for it.
>> B. Accepted the negative value as a wrapped size.
>>
>> i.e. for:
>>
>> struct foo {
>> signed char size;
>> unsigned char array[] __counted_by(size);
>> } *a;
>>
>> ...
>> a->size = -3;
>> report(__builtin_dynamic_object_size(p->array, 1));
>>
>> this reports 253, rather than 0.
>>
>> And the array-bounds sanitizer doesn’t catch negative index bounds neither.
>>
>> a->size = -3;
>> report(a->array[1]); // does not trap
>>
>>
>> So, my questions are:
>>
>> How should we handle the negative counted-by value?
>
> Treat it as always 0-bounded: count < 0 ? 0 : count
Then the size of the object is 0?
>
>>
>> My approach is:
>>
>> I think that this is a user error, the compiler need to Issue warning
>> during runtime about this user error.
>>
>> Since I have one remaining patch that has not been finished yet:
>>
>> 6 Emit warnings when the user breaks the requirments for the new counted_by
>> attribute
>> compilation time: -Wcounted-by
>> run time: -fsanitizer=counted-by
>> * The initialization to the size field should be done before the first
>> reference to the FAM field.
>
> I would hope that regular compile-time warnings would catch this.
If the value is known at compile-time, then compile-time should catch it.
>
>> * the array has at least # of elements specified by the size field all
>> the time during the program.
>> * the value of counted-by should not be negative.
>
> This seems reasonable for a very strict program, but it won't work for
> the kernel as-is: a negative "count" is sometimes used to carry failure
> details back to other users of the structure. This could be refactored in
> the kernel, but I'd prefer that even without -fsanitizer=counted-by the
> runtime behaviors will be "safe".
So, In the kernel’s source code, for example:
struct foo {
int count;
short array[] __counted_by(count);
};
The field “count” will be used for two purposes:
A. As the counted_by for the “array” when its value > 0;
B. As an errno when its value < 0; under such condition, the size of “array”
is zero.
Is the understanding correct?
Is doing this for saving space? (Curious -:)
>
> It does not seem sensible to me that adding a buffer size validation
> primitive to GCC will result in conditions where a size calculation
> will wrap around. I prefer no surprises. :)
Might be a bug here. I guess.
>
>> Let me know your comment and suggestions.
>
> Clang has implemented the safety logic I'd prefer:
>
> * __bdos will report 0 for any sizing where the "counted_by" count
> variable is negative. Effectively, the count variable is always
> processed as: count < 0 ? 0 : count
>
> struct foo {
> int count;
> short array[] __counted_by(count);
> } *p;
>
> __bdos(p->array, 1) ==> sizeof(*p->array) * (count < 0 ? 0 : count)
NOTE, __bdo will use value 0 as UNKNOWN_SIZE for MINMUM SIZE query, i.e:
size_t __builtin_object_size (const void * ptr, int type)
Will return 0 as UNKNOW_SIZE when type= 2 or 3.
So, I am wondering: should the 0 here is UNKNOWN_SIZE or 0 size?
I guess should be the UNKNOWN_SIZE? (I,e, -1 for MAXIMUM type, 0 for MINIMUM
type).
i.e, when the value of “count” is 0 or negative, the __bdos will return
UNKNOWN_SIZE. Is this correct?
>
> The logic for this is that __bdos can be _certain_ that the size is 0
> when the count variable is pathological.
>
> * -fsanitize=array-bounds similarly treats count as above, so that:
>
> printf("%d\n", p->array[index]); ==> trap when index > (count < 0 ? 0 :
> count)
>
> Same logic for the sanitizer: any access to the array when count is
> invalid means the access is invalid and must be trapped.
Okay, when the value of “count” is 0 or negative, bound sanitizer will report
out-of-bound (or trap) for any access to the array.
This should be reasonable.
Qing
>
>
> This means that software can run safely even in pathological conditions.
>
> -Kees
>
> --
> Kees Cook