From c4cd59507021cfc59b53c61d0bbcf04ea562ab7f Mon Sep 17 00:00:00 2001 From: David Fan Date: Thu, 2 Jul 2026 23:14:51 +0000 Subject: [PATCH 1/3] Add bounds checking to OgaSequencesGetSequence C API functions OgaSequencesGetSequenceCount and OgaSequencesGetSequenceData accessed the underlying vector via operator[] without validating the sequence index, allowing an out-of-bounds read when the caller supplied an index >= the number of sequences. Return 0 / nullptr for out-of-bounds indices so no C++ exception crosses the C API boundary. Also harden OgaCreateTensorFromBuffer against negative dimensions and byte-count multiplication overflow when computing the tensor size. Add a CAPITests.SequencesOutOfBoundsAccess unit test covering the out-of-bounds behavior. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/ort_genai_c.cpp | 17 +++++++++++++++-- test/c_api_tests.cpp | 17 +++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/ort_genai_c.cpp b/src/ort_genai_c.cpp index 6bf1443a56..ffed82a0e3 100644 --- a/src/ort_genai_c.cpp +++ b/src/ort_genai_c.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "span.h" #include "ort_genai_c.h" #include "generators.h" @@ -161,10 +162,16 @@ size_t OGA_API_CALL OgaSequencesCount(const OgaSequences* p) { } size_t OGA_API_CALL OgaSequencesGetSequenceCount(const OgaSequences* p, size_t sequence) { + if (sequence >= p->size()) { + return 0; + } return (*p)[sequence].size(); } const int32_t* OGA_API_CALL OgaSequencesGetSequenceData(const OgaSequences* p, size_t sequence) { + if (sequence >= p->size()) { + return nullptr; + } return (*p)[sequence].data(); } @@ -750,8 +757,14 @@ OgaResult* OGA_API_CALL OgaCreateTensorFromBuffer(void* data, const int64_t* sha auto ort_element_type = static_cast(element_type); size_t byte_count = Ort::SizeOf(ort_element_type); auto shape = std::span{shape_dims, shape_dims_count}; - for (size_t i = 0; i < shape_dims_count; i++) - byte_count *= shape_dims[i]; + for (size_t i = 0; i < shape_dims_count; i++) { + if (shape_dims[i] < 0) + throw std::runtime_error("shape dimension must be non-negative"); + const size_t dim = static_cast(shape_dims[i]); + if (dim != 0 && byte_count > std::numeric_limits::max() / dim) + throw std::runtime_error("tensor byte count overflow"); + byte_count *= dim; + } std::unique_ptr ort_tensor; if (data) ort_tensor = OrtValue::CreateTensor(*p_memory_info, data, byte_count, shape, ort_element_type); diff --git a/test/c_api_tests.cpp b/test/c_api_tests.cpp index f15fc170e3..a388429909 100644 --- a/test/c_api_tests.cpp +++ b/test/c_api_tests.cpp @@ -288,6 +288,23 @@ TEST(CAPITests, AppendTokensToSequence) { #endif } +TEST(CAPITests, SequencesOutOfBoundsAccess) { + auto sequences = OgaSequences::Create(); + + std::vector tokens{100, 200, 300}; + sequences->Append(tokens.data(), tokens.size()); + + ASSERT_EQ(sequences->Count(), 1u); + EXPECT_EQ(sequences->SequenceCount(0), tokens.size()); + EXPECT_NE(sequences->SequenceData(0), nullptr); + + // Out-of-bounds indices must not read past the underlying storage. + EXPECT_EQ(sequences->SequenceCount(1), 0u); + EXPECT_EQ(sequences->SequenceData(1), nullptr); + EXPECT_EQ(sequences->SequenceCount(1000), 0u); + EXPECT_EQ(sequences->SequenceData(1000), nullptr); +} + TEST(CAPITests, MaxLength) { // Batch size 1 case std::vector input_ids_0{1, 2, 3, 5, 8}; From d65dde98ea7b46acef5f2d366424583200cbe060 Mon Sep 17 00:00:00 2001 From: David Fan Date: Thu, 2 Jul 2026 23:23:00 +0000 Subject: [PATCH 2/3] Reject null shape_dims with non-zero count in OgaCreateTensorFromBuffer Addresses review feedback: a caller of this C ABI entry point could pass shape_dims=nullptr with a non-zero shape_dims_count, causing a null dereference in the shape loop. Reject that combination explicitly before constructing the span. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/ort_genai_c.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ort_genai_c.cpp b/src/ort_genai_c.cpp index ffed82a0e3..327841d706 100644 --- a/src/ort_genai_c.cpp +++ b/src/ort_genai_c.cpp @@ -756,6 +756,8 @@ OgaResult* OGA_API_CALL OgaCreateTensorFromBuffer(void* data, const int64_t* sha auto p_memory_info = OrtMemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPU); auto ort_element_type = static_cast(element_type); size_t byte_count = Ort::SizeOf(ort_element_type); + if (shape_dims_count > 0 && shape_dims == nullptr) + throw std::runtime_error("shape_dims must not be null when shape_dims_count is non-zero"); auto shape = std::span{shape_dims, shape_dims_count}; for (size_t i = 0; i < shape_dims_count; i++) { if (shape_dims[i] < 0) From a1a372048e76c9a62742a330bcce00fa117b4640 Mon Sep 17 00:00:00 2001 From: David Fan Date: Thu, 2 Jul 2026 23:42:11 +0000 Subject: [PATCH 3/3] Address review feedback on sequence getters and tensor validation - Document the out-of-bounds behavior of OgaSequencesGetSequenceCount (returns 0) and OgaSequencesGetSequenceData (returns nullptr) in the public C header so consumers don't misread it as a valid empty sequence. - Guard against int64_t shape dimensions exceeding size_t range before casting in OgaCreateTensorFromBuffer, so the overflow check is not defeated by truncation on 32-bit builds. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/ort_genai_c.cpp | 2 ++ src/ort_genai_c.h | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/ort_genai_c.cpp b/src/ort_genai_c.cpp index 327841d706..69a123a0fc 100644 --- a/src/ort_genai_c.cpp +++ b/src/ort_genai_c.cpp @@ -762,6 +762,8 @@ OgaResult* OGA_API_CALL OgaCreateTensorFromBuffer(void* data, const int64_t* sha for (size_t i = 0; i < shape_dims_count; i++) { if (shape_dims[i] < 0) throw std::runtime_error("shape dimension must be non-negative"); + if (static_cast(shape_dims[i]) > std::numeric_limits::max()) + throw std::runtime_error("shape dimension exceeds size_t range"); const size_t dim = static_cast(shape_dims[i]); if (dim != 0 && byte_count > std::numeric_limits::max() / dim) throw std::runtime_error("tensor byte count overflow"); diff --git a/src/ort_genai_c.h b/src/ort_genai_c.h index c553caa17c..2f0cc86aab 100644 --- a/src/ort_genai_c.h +++ b/src/ort_genai_c.h @@ -174,7 +174,8 @@ OGA_EXPORT OgaResult* OGA_API_CALL OgaAppendTokenToSequence(int32_t token, OgaSe * \brief Returns the number of tokens in the sequence at the given index. * \param[in] sequences OgaSequences to use. * \param[in] sequence_index index of the sequence to use. - * \return The number of tokens in the sequence at the given index + * \return The number of tokens in the sequence at the given index. Returns 0 if + * sequence_index is out of bounds (i.e. >= OgaSequencesCount(sequences)). */ OGA_EXPORT size_t OGA_API_CALL OgaSequencesGetSequenceCount(const OgaSequences* sequences, size_t sequence_index); @@ -184,6 +185,7 @@ OGA_EXPORT size_t OGA_API_CALL OgaSequencesGetSequenceCount(const OgaSequences* * \param[in] sequences OgaSequences to use. * \param[in] sequence_index index of the sequence to use. * \return The pointer to the sequence data at the given index. The pointer is valid until the OgaSequences is destroyed. + * Returns nullptr if sequence_index is out of bounds (i.e. >= OgaSequencesCount(sequences)). */ OGA_EXPORT const int32_t* OGA_API_CALL OgaSequencesGetSequenceData(const OgaSequences* sequences, size_t sequence_index);