Skip to content

Fix heap OOB read in Literal::CreateFromProto via unchecked dynamic_sizes#44232

Open
foodlook wants to merge 1 commit into
openxla:mainfrom
foodlook:fix/literal-proto-dynamic-sizes-oob
Open

Fix heap OOB read in Literal::CreateFromProto via unchecked dynamic_sizes#44232
foodlook wants to merge 1 commit into
openxla:mainfrom
foodlook:fix/literal-proto-dynamic-sizes-oob

Conversation

@foodlook

Copy link
Copy Markdown

Summary

Piece::CopyFromProto accepts dynamic_sizes values from a serialized LiteralProto and passes them directly to Piece::SetDynamicSize without validating that they fall within [0, shape.dimensions(i)]. Unlike the public MutableLiteralBase::SetDynamicSize (which has CHECK_GE), the internal Piece::SetDynamicSize stores the value unconditionally, breaking the invariant dynamic_size <= static_dimension_bound.

A subsequent call to ToStatic() reads the poisoned dynamic size and uses it as the copy count in CopyElementsWithDynamicBound / std::copy_n, causing an out-of-bounds heap read.

Reproduction

// Shape: f32[<=4]{0}, data = [1,2,3,4], dynamic_sizes = [100]
LiteralProto proto = BuildPoisonedProto();
auto literal = Literal::CreateFromProto(proto).value();  // succeeds (BUG)
literal.GetDynamicSize(0);  // returns 100 (invariant broken)
literal.ToStatic();  // heap-buffer-overflow READ of size 400

AddressSanitizer confirms:

==3895==ERROR: AddressSanitizer: stack-buffer-overflow
READ of size 400 at 0x7b86c9300340 thread T0
SCARINESS: 41 (multi-byte-read-stack-buffer-overflow)

Fix

Add bounds validation in CopyFromProto before calling SetDynamicSize: for each dynamic dimension, reject dynamic_sizes[i] outside [0, shape.dimensions(i)] with InvalidArgument.

Tests added

  • InvalidProtoDynamicSizeExceedsBound: dynamic_sizes=[100] on f32[<=4] -> rejected
  • InvalidProtoDynamicSizeNegative: dynamic_sizes=[-1] -> rejected
  • ValidProtoDynamicSizeWithinBound: dynamic_sizes=[2] on f32[<=4] -> accepted, ToStatic() produces f32[2]

Piece::CopyFromProto accepted arbitrary dynamic_sizes values from
untrusted LiteralProto without validating them against the static
dimension bounds. The internal Piece::SetDynamicSize (unlike its
public counterpart MutableLiteralBase::SetDynamicSize) lacked a
CHECK_GE guard, silently breaking the invariant
  dynamic_size <= static_dimension_bound.

A subsequent call to ToStatic() would then invoke
CopyElementsWithDynamicBound, which used the poisoned dynamic size
as the copy count in std::copy_n, resulting in an out-of-bounds
heap read (confirmed via AddressSanitizer: READ of size 400 from a
16-byte buffer).

The fix adds bounds validation in CopyFromProto before calling
SetDynamicSize: for each dynamic dimension, dynamic_sizes[i] must
be in [0, shape.dimensions(i)]. Out-of-range values now return
InvalidArgument instead of silently corrupting the invariant.

Regression tests:
- InvalidProtoDynamicSizeExceedsBound: dynamic_sizes=[100] on f32[<=4] rejected
- InvalidProtoDynamicSizeNegative: dynamic_sizes=[-1] rejected
- ValidProtoDynamicSizeWithinBound: dynamic_sizes=[2] on f32[<=4] accepted, ToStatic() produces f32[2]

PiperOrigin-RevId: N/A
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant