Skip to content

Adding OOB checks in the xclbin_parser#9589

Open
ManojTakasi wants to merge 1 commit intoXilinx:masterfrom
ManojTakasi:checks
Open

Adding OOB checks in the xclbin_parser#9589
ManojTakasi wants to merge 1 commit intoXilinx:masterfrom
ManojTakasi:checks

Conversation

@ManojTakasi
Copy link
Copy Markdown
Collaborator

Problem solved by the commit

https://amd.atlassian.net/wiki/spaces/~saifuddi/pages/1407497203/XRT+UserSpace+Bug+Bounty
SWSPLAT-9168: OOB read in xclbin SOFT_KERNEL parsing via unvalidated mpo_* offsets, crafted xclbins could cause crash or info leak.
SWSPLAT-9114 / 9170 / 9154: OOB read and integer overflow in AIE_PARTITION parsing (get_aie_partition), file-controlled offsets/sizes used without bounds checks, leading to crash or metadata corruption.

SWSPLAT-9150: Use-after-free and OOB read in xclbinutil SectionAIEResourcesBin (init, copyBufferUpdateMetadata, writeObjImage, writeMetadata) when processing malformed AIE_RESOURCES_BIN sections.

SWSPLAT-9110: Command injection in xball via prog_args concatenated into a shell command passed to os.system().

Bug / issue (if any) fixed, which PR introduced the bug, how it was discovered

How problem was solved, alternative solutions (if any) and why they were rejected

Added section bounds checks and overflow checks and use strnlen for all string fields from section data.
Added offset/size validation and bounded string reads and store subsection names by value to avoid UAF
Run command via subprocess.run(cmd_list) with shlex.split(prog_args) instead of os.system(cmd).

Risks (if any) associated the changes in the commit

low

What has been tested and how, request additional testing if necessary

Tested by building XRT package on ubuntu 22.04

Documentation impact (if any)

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds defensive parsing and execution hardening to address multiple security issues in xclbin parsing and tooling (OOB reads/UAF/overflow, plus command injection).

Changes:

  • Added bounds/overflow checks and bounded string reads (via strnlen) in xclbin/AIE section parsing.
  • Avoided potential UAF by storing subsection names by value in SectionAIEResourcesBin.
  • Replaced os.system() with subprocess.run() + shlex.split() in xball to prevent command injection.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 9 comments.

File Description
src/runtime_src/tools/xclbinutil/SectionAIEResourcesBin.cxx Adds offset validation and bounded string extraction for AIE_RESOURCES_BIN processing.
src/runtime_src/core/tools/common/xball Switches to argv-style subprocess execution to avoid shell command injection.
src/runtime_src/core/common/xclbin_parser.cpp Adds bounds checks, overflow-resistant parsing, and bounded string reads for SOFT_KERNEL and AIE_PARTITION sections.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 166 to +173
auto pHdr = reinterpret_cast<const aie_resources_bin*>(_pOrigDataSection);

if (pHdr->mpo_name >= _origSectionSize ||
pHdr->mpo_version >= _origSectionSize ||
pHdr->m_image_offset >= _origSectionSize ||
(pHdr->m_image_offset + pHdr->m_image_size) > _origSectionSize) {
throw std::runtime_error("ERROR: Invalid offsets in aie_resources_bin structure");
}
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

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

This dereferences pHdr->... before proving _origSectionSize >= sizeof(aie_resources_bin). If the section is smaller than the header, simply reading these fields is already an OOB read. Add an early size check (similar to writeObjImage/writeMetadata) before casting/accessing header fields.

Copilot uses AI. Check for mistakes.
Comment on lines +849 to +850
soft->m_image_offset >= section_size ||
(soft->m_image_offset + soft->m_image_size) > section_size)
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

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

The check (soft->m_image_offset + soft->m_image_size) > section_size can be bypassed via unsigned integer overflow if the fields are file-controlled. Use an overflow-safe pattern instead (e.g., validate m_image_size <= section_size and m_image_offset <= section_size - m_image_size).

Suggested change
soft->m_image_offset >= section_size ||
(soft->m_image_offset + soft->m_image_size) > section_size)
soft->m_image_size > section_size ||
soft->m_image_offset > section_size - soft->m_image_size)

Copilot uses AI. Check for mistakes.
Comment on lines +888 to +895
size_t start_cols_bytes = static_cast<size_t>(aiep->info.start_columns.size) * sizeof(uint16_t);
if (start_cols_bytes > section_size ||
aiep->info.start_columns.offset > section_size - start_cols_bytes)
return {};
if (aiep->aie_pdi.offset >= section_size)
return {};
size_t aie_pdi_bytes = static_cast<size_t>(aiep->aie_pdi.size) * sizeof(aie_pdi);
if (aie_pdi_bytes > section_size || aiep->aie_pdi.offset > section_size - aie_pdi_bytes)
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

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

Several size computations/offset+size checks are still overflow-prone with attacker-controlled values: multiplications like size * sizeof(T) can wrap, and additions like offset + size can wrap. Replace with overflow-safe comparisons (e.g., size > section_size / sizeof(T) before multiplying, and offset > section_size - size instead of offset + size > section_size) for start_cols_bytes, aie_pdi_bytes, cdo_groups_bytes, ids_bytes, and pdi_image.offset + pdi_image.size.

Suggested change
size_t start_cols_bytes = static_cast<size_t>(aiep->info.start_columns.size) * sizeof(uint16_t);
if (start_cols_bytes > section_size ||
aiep->info.start_columns.offset > section_size - start_cols_bytes)
return {};
if (aiep->aie_pdi.offset >= section_size)
return {};
size_t aie_pdi_bytes = static_cast<size_t>(aiep->aie_pdi.size) * sizeof(aie_pdi);
if (aie_pdi_bytes > section_size || aiep->aie_pdi.offset > section_size - aie_pdi_bytes)
if (aiep->info.start_columns.size > section_size / sizeof(uint16_t))
return {};
size_t start_cols_bytes = static_cast<size_t>(aiep->info.start_columns.size) * sizeof(uint16_t);
if (aiep->info.start_columns.offset > section_size - start_cols_bytes)
return {};
if (aiep->aie_pdi.offset >= section_size)
return {};
if (aiep->aie_pdi.size > section_size / sizeof(aie_pdi))
return {};
size_t aie_pdi_bytes = static_cast<size_t>(aiep->aie_pdi.size) * sizeof(aie_pdi);
if (aiep->aie_pdi.offset > section_size - aie_pdi_bytes)

Copilot uses AI. Check for mistakes.
Comment on lines +910 to +917
if (aiepdip->pdi_image.offset >= section_size ||
(aiepdip->pdi_image.offset + aiepdip->pdi_image.size) > section_size ||
aiepdip->cdo_groups.offset >= section_size)
continue;
size_t cdo_groups_bytes = static_cast<size_t>(aiepdip->cdo_groups.size) * sizeof(cdo_group);
if (cdo_groups_bytes > section_size ||
aiepdip->cdo_groups.offset > section_size - cdo_groups_bytes)
continue;
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

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

Several size computations/offset+size checks are still overflow-prone with attacker-controlled values: multiplications like size * sizeof(T) can wrap, and additions like offset + size can wrap. Replace with overflow-safe comparisons (e.g., size > section_size / sizeof(T) before multiplying, and offset > section_size - size instead of offset + size > section_size) for start_cols_bytes, aie_pdi_bytes, cdo_groups_bytes, ids_bytes, and pdi_image.offset + pdi_image.size.

Copilot uses AI. Check for mistakes.
Comment on lines +936 to +938
size_t ids_bytes = static_cast<size_t>(cdop->dpu_kernel_ids.size) * sizeof(uint64_t);
if (ids_bytes > section_size || cdop->dpu_kernel_ids.offset > section_size - ids_bytes)
continue;
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

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

Several size computations/offset+size checks are still overflow-prone with attacker-controlled values: multiplications like size * sizeof(T) can wrap, and additions like offset + size can wrap. Replace with overflow-safe comparisons (e.g., size > section_size / sizeof(T) before multiplying, and offset > section_size - size instead of offset + size > section_size) for start_cols_bytes, aie_pdi_bytes, cdo_groups_bytes, ids_bytes, and pdi_image.offset + pdi_image.size.

Copilot uses AI. Check for mistakes.
Comment on lines 427 to +432

auto safe_str = [this, pHdr](uint32_t off) -> std::string {
if (off >= m_bufferSize)
return "";
return std::string(reinterpret_cast<const char*>(pHdr) + off,
strnlen(reinterpret_cast<const char*>(pHdr) + off, m_bufferSize - off));
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

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

Using strnlen(..., m_bufferSize - off) can still allocate/copy up to the remainder of the section if the string is unterminated. With large sections, this can become a memory/time DoS vector. Consider enforcing a reasonable maximum string length cap (e.g., a few KB) in addition to the section bound, and treat longer/unterminated strings as invalid (throw) or truncate intentionally.

Suggested change
auto safe_str = [this, pHdr](uint32_t off) -> std::string {
if (off >= m_bufferSize)
return "";
return std::string(reinterpret_cast<const char*>(pHdr) + off,
strnlen(reinterpret_cast<const char*>(pHdr) + off, m_bufferSize - off));
const size_t kMaxMetadataStringLen = 4096; // Cap individual metadata strings to a reasonable size
auto safe_str = [this, pHdr, kMaxMetadataStringLen](uint32_t off) -> std::string {
if (off >= m_bufferSize)
return "";
const char* ptr = reinterpret_cast<const char*>(pHdr) + off;
size_t remaining = m_bufferSize - off;
size_t maxSearch = (remaining > kMaxMetadataStringLen) ? kMaxMetadataStringLen : remaining;
size_t len = strnlen(ptr, maxSearch);
// If there is more data beyond the maximum allowed length and no terminator was found
if (remaining > kMaxMetadataStringLen && len == maxSearch) {
auto errMsg = boost::format("ERROR: Metadata string at offset 0x%lx exceeds maximum allowed length (%d bytes) or is unterminated")
% static_cast<unsigned long>(off) % kMaxMetadataStringLen;
throw std::runtime_error(errMsg.str());
}
return std::string(ptr, len);

Copilot uses AI. Check for mistakes.
Comment on lines +855 to +860
sko.symbol_name = std::string(begin + soft->mpo_symbol_name,
strnlen(begin + soft->mpo_symbol_name, section_size - soft->mpo_symbol_name));
sko.mpo_name = std::string(begin + soft->mpo_name,
strnlen(begin + soft->mpo_name, section_size - soft->mpo_name));
sko.mpo_version = std::string(begin + soft->mpo_version,
strnlen(begin + soft->mpo_version, section_size - soft->mpo_version));
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

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

These bounded reads prevent OOB, but can still copy very large substrings if the binary omits NUL terminators and the remaining section size is large. Consider adding a small maximum string length cap (like in tooling metadata) to avoid excessive allocations/copies from malformed inputs.

Copilot uses AI. Check for mistakes.

auto scp = reinterpret_cast<const uint16_t*>(topbase + aiep->info.start_columns.offset);
aie_partition_obj obj{aiep->info.column_width, {scp, scp + aiep->info.start_columns.size},
std::string(topbase + aiep->mpo_name, strnlen(topbase + aiep->mpo_name, section_size - aiep->mpo_name)),
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

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

These bounded reads prevent OOB, but can still copy very large substrings if the binary omits NUL terminators and the remaining section size is large. Consider adding a small maximum string length cap (like in tooling metadata) to avoid excessive allocations/copies from malformed inputs.

Copilot uses AI. Check for mistakes.
Comment on lines +945 to +946
std::string cdo_name(topbase + cdop->mpo_name,
strnlen(topbase + cdop->mpo_name, section_size - cdop->mpo_name));
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

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

These bounded reads prevent OOB, but can still copy very large substrings if the binary omits NUL terminators and the remaining section size is large. Consider adding a small maximum string length cap (like in tooling metadata) to avoid excessive allocations/copies from malformed inputs.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

clang-tidy made some suggestions

sectionInfo->subSections.push_back(getSubSectionName(SubSection::obj));
sectionInfo->subSections.push_back(getSubSectionName(SubSection::metadata));

sectionInfo->subSections.push_back(std::string(getSubSectionName(SubSection::obj)));
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.

warning: use emplace_back instead of push_back [hicpp-use-emplace]

Suggested change
sectionInfo->subSections.push_back(std::string(getSubSectionName(SubSection::obj)));
sectionInfo->subSections.emplace_back(getSubSectionName(SubSection::obj));

sectionInfo->subSections.push_back(getSubSectionName(SubSection::metadata));

sectionInfo->subSections.push_back(std::string(getSubSectionName(SubSection::obj)));
sectionInfo->subSections.push_back(std::string(getSubSectionName(SubSection::metadata)));
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.

warning: use emplace_back instead of push_back [hicpp-use-emplace]

Suggested change
sectionInfo->subSections.push_back(std::string(getSubSectionName(SubSection::metadata)));
sectionInfo->subSections.emplace_back(getSubSectionName(SubSection::metadata));

auto safe_str = [this, pHdr](uint32_t off) -> std::string {
if (off >= m_bufferSize)
return "";
return std::string(reinterpret_cast<const char*>(pHdr) + off,
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.

warning: avoid repeating the return type from the declaration; use a braced initializer list instead [modernize-return-braced-init-list]

    return std::string(reinterpret_cast<const char*>(pHdr) + off,
           ^

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.

2 participants