Skip to content

GH-54936: [C++] Ensure reading an invalid map array from IPC doesn't crash - #54939

Merged
pitrou merged 2 commits into
apache:mainfrom
pitrou:gh54936-invalid-map-array
Oct 29, 2026
Merged

pitrou merged 2 commits into
apache:mainfrom
pitrou:gh54936-invalid-map-array

Conversation

@pitrou

@pitrou pitrou commented Oct 22, 2026 •

Copy link
Copy Markdown
Member

Rationale for this change

When loading a MapArray from IPC, we call MapArray::ValidateChildData to avoid a later crash in MapArray::SetData.

Unfortunately MapArray::ValidateChildData calls GetNullCount() which is unsafe if the advertised length is too large for the actual validity buffer size.

Found by OSS-Fuzz in https://issues.oss-fuzz.com/issues/556994537

What changes are included in this PR?

Check that the null_count of the relevant fields is equal to zero, so that GetNullCount does not actually try to read buffer memory.

Are these changes tested?

Yes, by additional fuzz regression test.

Are there any user-facing changes?

Just a bugfix.

This PR contains a "Critical Fix". An invalid IPC file would lead to reading memory out of bounds for the purpose of counting validity bits. It is not obvious how this could be exploited by an attacker except for causing a denial of service.

Was AI used for this PR?

PR code and description written by:

  • Human
  • AI

Reviewed before submission by:

  • Human
  • AI
  • Not reviewed

@github-actions

Copy link
Copy Markdown

⚠️ GitHub issue #54936 has been automatically assigned in GitHub to PR creator.

@dmitry-chirkov-dremio dmitry-chirkov-dremio left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

p.s for the lineage - this was missed during #51093 review

@github-actions github-actions Bot added awaiting committer review Awaiting committer review and removed awaiting review Awaiting review labels Oct 23, 2026
@pitrou

pitrou commented Oct 26, 2026

Copy link
Copy Markdown
Member Author

@taepper I don't know if you're familiar with this code but I would welcome extra eyes on this.

Comment thread cpp/src/arrow/ipc/reader.cc Outdated

Status Visit(const MapType& type) {
RETURN_NOT_OK(LoadList(type));
if (!out_->child_data.empty()) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can turn this into a

    DCHECK_EQ(out_->child_data.size(), 1);

after LoadList succeeds without an error

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you're right. Will do.

Comment thread cpp/src/arrow/ipc/reader.cc Outdated
Comment on lines +460 to +461
// Ensure that GetNullCount() calls in MapArray::ValidateChildData
// don't access invalid memory.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is because when null_count == -1, it will be inferred from the unvalidated data? Can we add a (non-fuzzing based) test for this directly?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is because when null_count == -1, it will be inferred from the unvalidated data?

Yes.

Can we add a (non-fuzzing based) test for this directly?

Well, for the test to be relevant, it would have to create invalid IPC data (a null bitmap that's too short, for example). Otherwise calling GetNullCount() would succeed anyway.

@taepper taepper Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True. If we want to validate that the invalid IPC data will be covered by the respective branches, something like this could be added (mostly generated by Claude Opus 5.5 as I am not very familiar with IPC code):

// GH-54936: a map array whose entries or keys have an unknown (-1) null count must be
// rejected, rather than computing the null count from an unvalidated validity bitmap.
TEST(TestReadRecordBatch, InvalidMapNullCount) {
  auto batch = RecordBatchFromJSON(schema({field("f", map(int32(), int32()))}),
                                   R"([{"f": [[1, 10], [2, 20]]}])");
  ASSERT_OK_AND_ASSIGN(auto serialized,
                       SerializeRecordBatch(*batch, IpcWriteOptions::Defaults()));
  io::BufferReader stream(serialized);
  ASSERT_OK_AND_ASSIGN(auto message, ReadMessage(&stream));

  // The message metadata has one FieldNode per array, in depth-first order:
  // 0 = map, 1 = entries, 2 = keys, 3 = items.
  auto checkValidationWithInvalidIPCField = [&](int invalid_field_node_index, const std::string& error) {
    ASSERT_OK_AND_ASSIGN(auto metadata,
                         message->metadata()->CopySlice(0, message->metadata()->size()));
    auto* fb_batch = flatbuf::GetMessage(metadata->data())->header_as_RecordBatch();
    // The writer emitted an empty validity bitmap for this node. With a huge length
    // and an unknown null count, computing the null count would read far out of
    // bounds of that bitmap and crash.
    *const_cast<flatbuf::FieldNode*>(fb_batch->nodes()->Get(invalid_field_node_index)) =
        flatbuf::FieldNode(/*length=*/int64_t{1} << 60, /*null_count=*/-1);

    io::BufferReader body(message->body());
    DictionaryMemo memo;
    EXPECT_RAISES_WITH_MESSAGE_THAT(Invalid, ::testing::HasSubstr(error),
                                    ReadRecordBatch(*metadata, batch->schema(), &memo,
                                                    IpcReadOptions::Defaults(), &body));
  };
  checkValidationWithInvalidIPCField(/*invalid_field_node_index=*/1, "Map array child array should have no nulls");
  checkValidationWithInvalidIPCField(/*invalid_field_node_index=*/2, "Map array keys array should have no nulls");
}

I concede that this could be too reliant on implementation details and be mostly a maintenance burden without much gain, so I am fine either way. For what it's worth, it does lead to crashes without your fix :)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I'd rather rely on the fuzz-generated file (or any other "bad data" file) than adding such maintenance burden to the unit test suite.

@pitrou
pitrou force-pushed the gh54936-invalid-map-array branch from 33751eb to 91bd531 Compare October 29, 2026 09:45
@pitrou

pitrou commented Oct 29, 2026

Copy link
Copy Markdown
Member Author

Thanks for the review @taepper !

@pitrou
pitrou merged commit d24791f into apache:main Oct 29, 2026
63 checks passed
@pitrou pitrou removed the awaiting committer review Awaiting committer review label Oct 29, 2026
@pitrou
pitrou deleted the gh54936-invalid-map-array branch October 29, 2026 10:19
raulcd pushed a commit that referenced this pull request Oct 29, 2026
…crash (#54939)

### Rationale for this change

When loading a MapArray from IPC, we call [`MapArray::ValidateChildData`](https://github.com/apache/arrow/blob/b9479757dd5c1914c2b007f11f6679c4fc080c05/cpp/src/arrow/ipc/reader.cc#L456-L459) to avoid a later crash in `MapArray::SetData`.

Unfortunately `MapArray::ValidateChildData` calls `GetNullCount()` which is unsafe if the advertised length is too large for the actual validity buffer size.

Found by OSS-Fuzz in https://issues.oss-fuzz.com/issues/556994537

### What changes are included in this PR?

Check that the `null_count` of the relevant fields is equal to zero, so that `GetNullCount` does not actually try to read buffer memory.

### Are these changes tested?

Yes, by additional fuzz regression test.

### Are there any user-facing changes?

Just a bugfix.

**This PR contains a "Critical Fix".** An invalid IPC file would lead to reading memory out of bounds for the purpose of counting validity bits. It is not obvious how this could be exploited by an attacker except for causing a denial of service.

### Was AI used for this PR?

**PR code and description written by:**

- [x] Human
- [ ] AI

**Reviewed before submission by:**

- [x] Human
- [ ] AI
- [ ] Not reviewed

* GitHub Issue: #54936

Authored-by: Antoine Pitrou <antoine@python.org>
Signed-off-by: Antoine Pitrou <antoine@python.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants