On 2025-04-10 07:48, Martin Uecker wrote:
For the currently implemented subset, updates to the storage of the
annotated FAM and updates to its counted_by size need to be coupled to
prevent reordering because their relationship is otherwise implicit.
This proposed feature would need the counted_by size and the containing
struct of the FAM to be coupled to make that relationship explicit, but
it's not necessarily to prevent reordering because this case needs to
only be considered when the update of the storage for the struct (or its
containing FAM) is not visible. That is, the pointer either comes in as
a parameter or a non-allocator call returning that pointer.
So the assumption here is that you never pass around a pointer
to an uninitialized (or otherwise not validely initialized)
structure.
Yes, that's the hairy bit I think, in the context of __bdos. We tend to
avoid generating code that dereferences the input pointer, but we ended
up relaxing that with counted_by. However, right now it's still limited
to cases where the compiler can see the allocation, so this extension
would indeed violate that self-imposed restriction.
Now it is entirely possible that further inlining gets the allocator in
and it would be possible to thread through the .ACCESS_WITH_SIZE to
reach the allocator, but I would argue that the counted_by + size of
struct should be the canonical result.
I am thinking about use cases where the user does its own memory
management, e.g. there is larger arena the structure is
allocated in or there is a capacity field because the structure
was overallocated.
Wouldn't it be even more true in that case though? When an allocator
sets up an arena and passes a chunk within it to the caller, it would
expect the caller to limit its accesses to within the chunk, in this
case being within bounds of counted_by. A reasonable allocator should
in fact expect a security mechanism to trap when this is violated even
when the overallocation may make the access safe.
This rhymes with the malloc_usable_size discussion, where some
applications (incorrectly) assume that the extra size available due to
alignment requirements is available for them and in practice it is safe
to use it. However for a security-sensitive program, it would be a
healthy habit to stay within the bounds they requested or explicitly
agree with the compiler and runtime that the extra size is usable. For
systemd for example there's a dummy realloc that simply hints to the
compiler that the pointer now has a larger size, encompassing the usable
size. I believe there was a C++ proposal with a similar goal, allowing
the runtime to tell userspace what size was actually allocated.
that inserting the new accesses to the size field are ok (e.g.
one can assume the struct is accessible, there are no new race
conditions under some assumptions, it is initialized). If you
now want to read the size at different points, all this is even
less clear to me.
I'm not sure about race conditions, the coupling was to prevent
reordering. Would you be able to elaborate on the race condition question?
If you materialize a read to the size field it could race
with a write to this field in another thread. I am not sure
whether there are use cases where this can happen, but it is
something to worry about.
This will likely be a problem even with present day use of counted_by.
The barrier enforced by .ACCESS_WITH_SIZE will only prevent reordering
within a thread, there's nothing really preventing concurrent access or
updates.
I suppose one constraint that could be assumed to be imposed is that the
FAM is currently allocated in the same routine as it is accessed, but it
could well be that the pointer that's used to receive the allocation is
shared, thus giving rise to this race.
Maybe we need wording that clearly outlines that allocation of a FAM (or
containing struct) and update of the counted_by size should be atomic?
I may miss something, but I think one would have to first
define a clear criterion when it is ok to access the size
field.
This is a contentious issue I had not thought of; if the pointer ends up
being NULL at runtime, a dereference due to the size expression for the
containing struct will end up validating it for subsequent passes, which
may makes things even worse. The size expression will also need a NULL
check to work around this.
Yes. But there could be other issues. The memory could be
protected.
True, or the pointer could be arbitrary/uninitialized like you mentioned
above. That is indeed a problem. Maybe one way to hedge against this
could be to generate the size expression (and consequently,
.ACCESS_WITH_SIZE) only when the pointer is read *and* dereferenced
(could be separate sites, but for the same pointer) in the routine?
That way we only add a pointer dereference for the size expression when
there already is a dereference; we're not adding any *new* potential
undefined behaviour.
Thanks,
Sid