{
  "schema_version": "r2b.case-study.v1",
  "case_id": "openwrt-ubusd-release-delta",
  "generated_at": "2026-08-31T00:00:00Z",
  "dataset": {
    "vendor": "OpenWrt",
    "device": "Banana Pi BPI-R3 Mini",
    "architecture": "AArch64",
    "releases": [
      {
        "version": "24.10.3",
        "sha256": "6f7dea199d326ca6efcc3c2a11932070edf79fa6d0984e2a4d77cef8c4cbb8a4",
        "source": "https://downloads.openwrt.org/releases/24.10.3/targets/mediatek/filogic/"
      },
      {
        "version": "24.10.4",
        "sha256": "8d632977278bfe58b53e20218dc72571b20b8da589ad28184642a5c3be37263b",
        "source": "https://downloads.openwrt.org/releases/24.10.4/targets/mediatek/filogic/"
      }
    ]
  },
  "inventory": {
    "regular_elfs_per_release": 176,
    "common_paths": 109,
    "unchanged_common_paths": 106,
    "changed_common_paths": [
      "bin/busybox",
      "sbin/ubusd",
      "usr/sbin/odhcpd"
    ],
    "versioned_paths_added": 67,
    "versioned_paths_removed": 67,
    "notes": "Most added and removed paths are kernel modules under 6.6.104 versus 6.6.110 or versioned library filenames. They were not counted as comparable paths."
  },
  "subject": {
    "path": "sbin/ubusd",
    "size_bytes": 65706,
    "old_sha256": "8fd16a06d3b529ee4899bf73c6ac283e8b030972205a8356da4fc2fc98dae5f3",
    "new_sha256": "d279564b22768de6e9d8efe083784c4547c3aeadf6ffd1905356a0e0df1f2765",
    "old_function_count_radare2": 63,
    "new_function_count_radare2": 64
  },
  "controls": [
    {
      "path": "usr/sbin/uhttpd",
      "sha256": "4c2dd929a4d837b1e951d69add3456826644a896d6903244947fe7f931d480e2",
      "result": "same SHA-256 in both releases"
    }
  ],
  "brief": {
    "record_id": "8fd16a06d3b529ee4899bf73c6ac283e8b030972205a8356da4fc2fc98dae5f3",
    "ranked_regions": [
      "imports:network",
      "entry:entry0",
      "imports:memory"
    ],
    "memory_imports": [
      "memcpy",
      "sprintf",
      "strcpy"
    ],
    "claim": "The import region is a caller lead, not proof of memory corruption."
  },
  "verify": {
    "import": "strcpy",
    "status": "dynamic",
    "old_call_sites": [
      "0x00004b50",
      "0x00004b6c",
      "0x00004ba8"
    ],
    "selected_old_function": "0x00104a3c",
    "selected_new_function": "0x001049a0"
  },
  "targeted_decompile": {
    "old_evidence": [
      "sVar8 = strlen(__s);",
      "cVar1 = __s[lVar11 + -1];",
      "if ((*__s == '\\0') || (acl_check(...) == 0))",
      "strcpy(destination, __s);"
    ],
    "new_evidence": [
      "sVar8 = strlen(__s);",
      "if (0 < iVar3) {",
      "cVar1 = __s[lVar11 + -1];",
      "if (acl_check(...) == 0)",
      "strcpy(destination, __s);"
    ],
    "interpretation": "The fixed binary rejects an empty pattern before indexing the last byte. Source is used to name the function and confirm intent."
  },
  "full_analysis": {
    "model_calls": 0,
    "target_executed": false,
    "tools": {
      "radare2": {
        "status": "completed",
        "functions": 63
      },
      "ghidra": {
        "status": "completed",
        "functions": 130
      },
      "angr": {
        "status": "completed",
        "stored_cfg_nodes": 300,
        "stored_cfg_edges": 381
      },
      "capstone": {
        "status": "partial",
        "reason": "instruction-only output; no functions or CFG"
      },
      "dwarf": {
        "status": "completed",
        "debug_info": false
      }
    }
  },
  "review_width": {
    "mode": "rules",
    "model_calls": 0,
    "marginal_regions": {
      "1": [
        "imports:network",
        "entry:entry0"
      ],
      "2": [],
      "3": [
        "imports:memory"
      ]
    }
  },
  "ground_truth": {
    "advisory": "https://github.com/openwrt/openwrt/security/advisories/GHSA-cp32-65v4-cp73",
    "fix_commit": "https://github.com/openwrt/ubus/commit/d31effb4277bd557f5ccf16d909422718c1e49d0",
    "function": "ubusd_alloc_event_pattern",
    "source_change": "Reject len <= 0 before reading pattern[len - 1]."
  },
  "bundle": {
    "schema_version": "r2b.bundle.v1",
    "sha256": "90544b29015b3fddfefadb00fb3b332dee59e8718681c1896d8c1c894201cbaf",
    "target_bytes_included": false,
    "members": [
      "analysis.json",
      "briefing.json",
      "provenance.json",
      "review.json",
      "tools.json"
    ]
  },
  "kali_pi_replay": {
    "recorded_at": "2026-09-02",
    "host": {
      "uname": "Linux kali-raspberry-pi5 6.1.92-v8+ aarch64",
      "os": "Kali GNU/Linux Rolling 2026.3",
      "cpus": 4,
      "ram_gib": 8,
      "r2b_version": "0.1.0",
      "python": "3.11.13"
    },
    "tools": {
      "radare2": "radare2 6.0.5 0 @ linux-arm-64",
      "binwalk3": "binwalk 3.1.0",
      "unblob": "26.6.4",
      "bwrap": "bubblewrap 0.11.2",
      "unsquashfs": "unsquashfs version 4.7.5 (2026/03/01)",
      "sasquatch": "unsquashfs version 4.5.1 (2022/03/17)",
      "file": "file-5.47"
    },
    "extract": {
      "allow_unsafe_fallback": false,
      "sandbox": "bwrap",
      "squashfs_offset": "0x56c000",
      "unsquashfs_returncode": 2,
      "device_nodes": "unsquashfs could not create /dev/console without superuser; regular files recovered",
      "product_brief_extract": "r2b brief --extract --quick skipped radare2 on the ITB wrapper and ranked SquashFS at 0x56c000",
      "default_max_files": 200,
      "inventory_source": "unsquashfs under r2b.extract.sandbox with max_files=20000"
    },
    "inventory": {
      "regular_elfs_per_release": 176,
      "regular_files_per_release": 1201,
      "common_paths": 109,
      "unchanged_common_paths": 106,
      "changed_common_paths": [
        {
          "path": "bin/busybox",
          "old_sha256": "9f8f1d0cfc748450d132dcd11fdfd2b1ffe4aea70d6fee5932c2c6dce85836b4",
          "new_sha256": "2032e4d04e90242dd1f3484d017628a1e82830dfa757ed7963ab90e29e53fa6c"
        },
        {
          "path": "sbin/ubusd",
          "old_sha256": "8fd16a06d3b529ee4899bf73c6ac283e8b030972205a8356da4fc2fc98dae5f3",
          "new_sha256": "d279564b22768de6e9d8efe083784c4547c3aeadf6ffd1905356a0e0df1f2765"
        },
        {
          "path": "usr/sbin/odhcpd",
          "old_sha256": "9c5ec590de45569ef3cd75a5f09d8a04103824ee9c8c507f36f4fcf0e3b2e13a",
          "new_sha256": "b0f7fc5f63bccd661f9f500f163b466f89e255890bbebd423876198d83271a25"
        }
      ],
      "versioned_paths_added": 67,
      "versioned_paths_removed": 67,
      "ubusd_hashes_match_original": true,
      "uhttpd_unchanged": true
    },
    "brief_quick": {
      "old_function_count": 70,
      "new_function_count": 77,
      "old_import_count": 71,
      "new_import_count": 79,
      "ranked_regions": [
        "imports:network",
        "entry:entry",
        "imports:memory"
      ],
      "note": "Quick-scan function counts are not the original deep radare2 counts (63 / 64)."
    },
    "verify": {
      "import": "strcpy",
      "old_status": "dynamic",
      "old_call_sites": [
        "0x00004b50",
        "0x00004b6c",
        "0x00004ba8"
      ],
      "old_callers_match_original": true,
      "new_status": "dynamic",
      "new_call_sites": [
        "0x00004ab0",
        "0x00004ad0",
        "0x00004b0c"
      ],
      "selected_old_function": "0x00104a3c",
      "selected_new_function": "0x001049a0",
      "memcpy_old_sites": 6,
      "memcpy_new_sites": 5,
      "decompile_queued": false,
      "target_executed": false
    },
    "follow_up_recorded_at": "2026-09-02",
    "changed_siblings": {
      "busybox": {
        "path": "bin/busybox",
        "old_sha256": "9f8f1d0cfc748450d132dcd11fdfd2b1ffe4aea70d6fee5932c2c6dce85836b4",
        "new_sha256": "2032e4d04e90242dd1f3484d017628a1e82830dfa757ed7963ab90e29e53fa6c",
        "size_bytes": 458773,
        "differing_bytes": 8,
        "first_diff_offset": "0x5bcf0",
        "banner": {
          "old": "BusyBox v1.36.1 (2025-09-19 21:19:38 UTC)",
          "new": "BusyBox v1.36.1 (2025-10-19 16:37:45 UTC)"
        },
        "brief_quick": {
          "old_function_count": 301,
          "new_function_count": 301,
          "old_import_count": 299,
          "new_import_count": 299,
          "ranked_regions": [
            "imports:process",
            "imports:network",
            "entry:entry",
            "imports:runtime",
            "imports:memory",
            "imports:control"
          ],
          "process_imports": [
            "execl",
            "execlp",
            "execv",
            "execve",
            "execvp",
            "fork",
            "popen",
            "system"
          ],
          "memory_imports": [
            "memcpy",
            "memmove",
            "realpath",
            "scanf",
            "sprintf",
            "sscanf",
            "strcat",
            "strcpy"
          ],
          "entry": "0x406500"
        },
        "verify": {
          "imports": [
            "execl",
            "execlp",
            "execv",
            "execve",
            "execvp",
            "popen",
            "system",
            "strcpy",
            "memcpy"
          ],
          "verdicts_identical_across_releases": true,
          "execl": {
            "status": "dynamic",
            "sites": 1
          },
          "execlp": {
            "status": "dynamic",
            "sites": 3
          },
          "execv": {
            "status": "dynamic",
            "sites": 2
          },
          "execve": {
            "status": "dynamic",
            "sites": 1
          },
          "execvp": {
            "status": "mixed",
            "sites": 3,
            "constant_site": {
              "address": "0x00434ce8",
              "function": "00434cb8",
              "argument": "stty"
            }
          },
          "popen": {
            "status": "dynamic",
            "sites": 2
          },
          "system": {
            "status": "dynamic",
            "sites": 2
          },
          "strcpy": {
            "status": "dynamic",
            "sites": 81
          },
          "memcpy": {
            "status": "dynamic",
            "sites": 58
          }
        },
        "lead": "rebuild_noise",
        "followed_to_patched_routine": false,
        "decompile_queued": false,
        "target_executed": false,
        "note": "SHA-256 changed; size, ranked regions, and verify maps did not. The eight differing bytes are the embedded BusyBox build timestamp."
      },
      "odhcpd": {
        "path": "usr/sbin/odhcpd",
        "old_sha256": "9c5ec590de45569ef3cd75a5f09d8a04103824ee9c8c507f36f4fcf0e3b2e13a",
        "new_sha256": "b0f7fc5f63bccd661f9f500f163b466f89e255890bbebd423876198d83271a25",
        "old_size_bytes": 132025,
        "new_size_bytes": 132113,
        "brief_quick": {
          "old_function_count": 157,
          "new_function_count": 179,
          "old_import_count": 159,
          "new_import_count": 182,
          "ranked_regions": [
            "imports:process",
            "imports:network",
            "entry:entry",
            "imports:memory",
            "imports:control"
          ],
          "old_memory_imports": [
            "memcpy",
            "memmove",
            "sscanf",
            "strcpy"
          ],
          "new_memory_imports": [
            "memcpy",
            "realpath",
            "sscanf",
            "strcpy"
          ],
          "old_entry": "0x40ac",
          "new_entry": "0x4ac4"
        },
        "verify": {
          "imports": [
            "execv",
            "strcpy",
            "memcpy"
          ],
          "old": {
            "execv": {
              "status": "dynamic",
              "sites": 1,
              "call_sites": [
                {
                  "function": "0000d8bc",
                  "address": "0x0000db7c",
                  "argument": "<dynamic>"
                }
              ]
            },
            "strcpy": {
              "status": "dynamic",
              "sites": 3,
              "call_sites": [
                {
                  "function": "00005b1c",
                  "address": "0x00005bf8",
                  "argument": "<unresolved>"
                },
                {
                  "function": "00005b1c",
                  "address": "0x00006b14",
                  "argument": "<unresolved>"
                },
                {
                  "function": "00005b1c",
                  "address": "0x00006c1c",
                  "argument": "<unresolved>"
                }
              ]
            },
            "memcpy": {
              "status": "dynamic",
              "sites": 18
            }
          },
          "new": {
            "execv": {
              "status": "dynamic",
              "sites": 1,
              "call_sites": [
                {
                  "function": "0000d730",
                  "address": "0x0000d9f0",
                  "argument": "<dynamic>"
                }
              ]
            },
            "strcpy": {
              "status": "dynamic",
              "sites": 1,
              "call_sites": [
                {
                  "function": "0000726c",
                  "address": "0x0000734c",
                  "argument": "<unresolved>"
                }
              ]
            },
            "memcpy": {
              "status": "dynamic",
              "sites": 19
            }
          }
        },
        "lead": "import_map_changed",
        "followed_to_patched_routine": false,
        "decompile_queued": false,
        "target_executed": false,
        "note": "Not rebuild noise at the brief/verify layer: size, function count, memory imports, and strcpy site count all moved. execv stayed one dynamic caller. No decompile was queued, so this was not followed to a patched routine."
      }
    },
    "insights": {
      "schema_version": "r2b.insights.v1",
      "tag": "ubusd",
      "ready": true,
      "skill_ready": false,
      "sibling_count": 2,
      "family": {
        "subject_class": "linux_elf",
        "id": "linux_elf"
      },
      "record_ids": [
        "8fd16a06d3b529ee4899bf73c6ac283e8b030972205a8356da4fc2fc98dae5f3",
        "d279564b22768de6e9d8efe083784c4547c3aeadf6ffd1905356a0e0df1f2765"
      ],
      "exact_sha_collapse": false,
      "identity_pattern": false,
      "recurring_imports": [
        "avl_find",
        "avl_strcmp",
        "blob_buf_init",
        "blob_nest_end",
        "blob_parse_untrusted",
        "blobmsg_add_field",
        "blobmsg_add_json_from_file",
        "blobmsg_open_nested"
      ],
      "pattern_kinds": {
        "import": 8
      },
      "note": "Two tagged records, two SHA-256 values. Insights did not collapse them. Recurring patterns are libubox/libblobmsg symbols at 2/2, not strcpy/memcpy (those are dropped as ubiquitous libc)."
    }
  },
  "limits": [
    "r2b did not discover or prove CVE-2025-62526 by itself.",
    "Exact hashing supplied the release delta; r2b supplied the saved binary triage, caller leads, targeted decompilation, tool status, and handoff.",
    "The v1 .r2br is a single-subject bundle. This case manifest links the two release images and the selected subject.",
    "Device-node creation fails without superuser on Linux as well as macOS; regular files still inventory.",
    "The other two changed ELFs were briefed and verified on the Kali replay. busybox is rebuild noise (eight-byte banner timestamp). odhcpd import maps changed; that lead was not followed to a patched routine.",
    "Tagged insights on ubusd 24.10.3 vs 24.10.4 did not collapse exact SHA-256; the hashes differ."
  ]
}
