KyleKim107 opened a new issue, #554:
URL: https://github.com/apache/avro-rs/issues/554

   # `avro_generic_map_class` is calling the wrong schema accessor (looks like 
a copy-paste bug)
   
   Hey — I think I found a small copy-paste bug in the C library.
   
   ## What's going on
   
   In `lang/c/src/generic.c`, `avro_generic_map_class()` calls 
`avro_schema_array_items()` on a **map** schema. Pretty sure it should be 
calling `avro_schema_map_values()` instead.
   
   `avro_generic_array_class()` right above it does the right thing:
   
   ```c
   avro_schema_t  child_schema = avro_schema_array_items(schema);  // fine for 
arrays
   ```
   
   But the map version copied the same line:
   
   ```c
   avro_schema_t  child_schema = avro_schema_array_items(schema);  // wrong for 
maps
   ```
   
   Other map code in the repo already uses the right helper — e.g. 
`datum_value.c` does `avro_schema_map_values(map_schema)`.
   
   ## Why you might not have noticed
   
   Right now it kinda works anyway, because `avro_array_schema_t` and 
`avro_map_schema_t` have the same layout:
   
   ```c
   struct avro_array_schema_t { struct avro_obj_t obj; avro_schema_t items;  };
   struct avro_map_schema_t   { struct avro_obj_t obj; avro_schema_t values; };
   ```
   
   Same offset for `items` / `values`, so you get the right pointer by accident 
in a normal build. But it's still UB — you're casting a map schema to an array 
schema via `container_of`, and UBSan catches it.
   
   If someone ever adds a field to one struct but not the other, this would 
break silently in prod. Not great.
   
   **Where:** `lang/c/src/generic.c`, `avro_generic_map_class()` (~line 2781 on 
current `main`)
   
   ## How to repro
   
   You don't need malicious binary input or anything fancy. Just build with 
UBSan and create a map schema.
   
   **Build avro with UBSan:**
   
   ```bash
   git clone https://github.com/apache/avro.git
   cd avro/lang/c
   mkdir build && cd build
   cmake .. \
       -DCMAKE_C_COMPILER=clang \
       -DCMAKE_C_FLAGS="-fsanitize=address,undefined 
-fno-sanitize-recover=undefined -g -O1"
   make -j$(nproc) avro-static
   ```
   
   **`repro.c`:**
   
   ```c
   #include <avro.h>
   #include <stdio.h>
   #include <string.h>
   
   int main(void)
   {
       const char *schema_json = "{\"type\": \"map\", \"values\": \"string\"}";
       avro_schema_t schema = NULL;
       avro_value_iface_t *iface = NULL;
       avro_value_t value;
   
       if (avro_schema_from_json_length(schema_json, strlen(schema_json), 
&schema) != 0) {
           fprintf(stderr, "schema parse failed\n");
           return 1;
       }
   
       /* hits avro_generic_map_class() → avro_schema_array_items() */
       iface = avro_generic_class_from_schema(schema);
       if (iface == NULL) {
           fprintf(stderr, "iface is NULL\n");
           avro_schema_decref(schema);
           return 1;
       }
   
       memset(&value, 0, sizeof(value));
       avro_generic_value_new(iface, &value);
   
       avro_value_decref(&value);
       avro_value_iface_decref(iface);
       avro_schema_decref(schema);
       return 0;
   }
   ```
   
   **Run it:**
   
   ```bash
   clang -fsanitize=address,undefined -fno-sanitize-recover=undefined -g -O1 \
       -I../src repro.c -L./src -lavro $(pkg-config --libs jansson) \
       -Wl,-rpath,./src -o repro
   
   ASAN_OPTIONS=detect_leaks=0 UBSAN_OPTIONS=print_stacktrace=1 ./repro
   ```
   
   **What you'll see (UBSan build):**
   
   ```
   lang/c/src/schema.c:462:9: runtime error: member access within null pointer
   of type 'struct avro_array_schema_t'
       #0 avro_schema_array_items
       #1 avro_generic_class_from_schema_memoized
       #2 avro_generic_class_from_schema
       #3 main
   ```
   
   Exact message might vary a bit depending on compiler/opt level, but it's 
always the map class construction path.
   
   **Sanity check:** rebuild without UBSan and run the same program — it 
finishes fine. So this isn't blowing up prod today, it's just wrong code that 
UBSan happens to catch.
   
   ## Impact (being honest about it)
   
   This isn't a security bug as far as I can tell — no memory corruption, no 
crash in normal builds. It's more of a "this is technically UB and will bite 
you later" kind of thing. UBSan/fuzzing builds will abort on it though.
   
   ## Suggested fix
   
   One line:
   
   ```diff
    static avro_generic_value_iface_t *
    avro_generic_map_class(avro_schema_t schema, memoize_state_t *state)
    {
   -    avro_schema_t  child_schema = avro_schema_array_items(schema);
   +    avro_schema_t  child_schema = avro_schema_map_values(schema);
        avro_generic_value_iface_t  *child_giface =
            avro_generic_class_from_schema_memoized(child_schema, state);
   ```
   
   I tried this locally and the repro passes cleanly under UBSan after the 
change.
   
   ## How I found it
   
   Fuzzing `avro_generic_class_from_schema()` / `avro_generic_value_new()` with 
UBSan on. Crashes as soon as the fuzzer picks a map schema — before any binary 
payload gets read.
   
   Happy to open a PR if that's helpful.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to