Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,10 @@ See archives under `docs/activity/` for older logs.
* Sweep for any lingering `fprintf` debug traces before shipping.
* Run CodeRabbit worksheet checker again after touching docs to ensure no new “Decision:” gaps.
- Today’s continuation: pulled the trigger on that list—extracted `gm_edge_resolve_blob_identity` into `core/src/edge/internal/blob_identity.c`, introduced `gm_bytes_to_hex`/`gm_oid_to_hex`, and refactored cache rebuild/query to drop `git_oid_fmt/tostr`; `make ci-local` is green and `tools/review/check_worksheets.py docs/code-reviews/PR171/*.md` still flags the known undecided sections (see console), so next slot should be worksheet triage + CodeRabbit replies.
- Draft Punks spin-out begun: workflows (`auto-seed-review`, `apply-feedback`, `review-artifact-cleanup`, `coderabbit-status`, `seed-review`) and review scripts copied into `/Users/james/git/draft-punks/`; README drafted in character for future extraction of the review tooling.
- PR #172 merged; local `main` fast-forwarded to `069ff59`. Seeded worksheet artifacts under `docs/code-reviews/PR172/` deleted from the repo (these are post-merge and live only in history now).
- On branch `feat/edge-port-followup`, swapped remaining edge/journal code to rely on `gm_oid_to_hex` instead of `git_oid_tostr`, removed lingering `<git2/oid.h>` from cache/edge modules, and cleaned the tree in preparation for further edge migration.
- Next up: continue the edge/augment migration by auditing `core/src/hooks/augment.c`, `core/src/domain/cache/edge_map.c`, and `core/src/journal/writer.c` for lingering libgit2 helpers (e.g., `git_oid_cmp`, `git_oid_is_zero`, `git_oid_fromraw`) and replace them with gm-level utilities/ports as needed.
- Follow-up (same day): cleaned up CodeRabbit’s fake-port feedback—fake commit/update paths now surface `gm_strcpy_safe` truncations, the shared OID helpers carry project-compliant naming/includes, and the deleted worksheet directory is ready for a fresh seed if we decide to log post-merge notes.
- Quick-start when you return: rerun `make ci-local` if you touch the cache/journal surface, then tackle the shared blob helper → oid hex helper chain; once that’s in place, prep the PR summary referencing the fast-forward protections.

Expand Down
38 changes: 38 additions & 0 deletions core/include/gitmind/util/oid.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#ifndef GITMIND_UTIL_OID_H
#define GITMIND_UTIL_OID_H

#include <stdbool.h>

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.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify umbrella exposure in include/gitmind.h.

Public headers must be umbrella‑safe and reachable via the umbrella. Please confirm this header is re‑exported.


🏁 Script executed:

#!/bin/bash
# Expect: include/gitmind.h exists and references util/oid.h
fd -a gitmind.h include | xargs -I{} rg -n 'gitmind/util/oid\.h' {}
# Also check for duplicates/different paths:
rg -nP --glob 'include/**' '\butil/oid\.h\b'

Length of output: 0


🏁 Script executed:

#!/usr/bin/env bash
# Locate all gitmind.h files and check for util/oid.h references
find . -type f -name gitmind.h | while read file; do
  echo "---- $file ----"
  grep -Hn "util/oid\.h" "$file" || echo "no reference"
done

Length of output: 42


Include util/oid.h in the umbrella header
Add the following to include/gitmind.h so that core/include/gitmind/util/oid.h is re-exported by the umbrella API:

--- a/include/gitmind.h
+++ b/include/gitmind.h
@@ /* somewhere among other includes */
+#include <gitmind/util/oid.h>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#include <stdbool.h>
++ b/include/gitmind.h
@@ /* somewhere among other umbrella-header includes */
#include <gitmind/other_header.h>
#include <gitmind/util/oid.h>
#include <gitmind/yet_another_header.h>
🤖 Prompt for AI Agents
In core/include/gitmind/util/oid.h at line 7, the util/oid header is not being
re-exported by the umbrella API; open include/gitmind.h and add an #include
"gitmind/util/oid.h" (placed with the other util/includes or alphabetical group)
so that core/include/gitmind/util/oid.h is exposed via the umbrella header;
update any include guards or ordering if necessary to preserve compile order.

#include <stddef.h>
#include <stdint.h>

Expand Down Expand Up @@ -36,6 +37,43 @@ GM_NODISCARD int gm_bytes_to_hex(const uint8_t *bytes, size_t len,
*/
GM_NODISCARD int gm_oid_to_hex(const gm_oid_t *oid, char *out, size_t out_size);

/**
* Test whether two OIDs refer to the same object.
*
* @param lhs Left-hand OID operand
* @param rhs Right-hand OID operand
* @return true when both pointers are non-null and contain identical raw bytes
*/
GM_NODISCARD bool gm_oid_equal(const gm_oid_t *lhs, const gm_oid_t *rhs);

/**
* Determine whether an OID is the all-zero sentinel.
*
* @param oid OID to inspect; NULL is treated as invalid / not zero
* @return true when every byte is zero; false otherwise (including NULL)
*/
GM_NODISCARD bool gm_oid_is_zero(const gm_oid_t *oid);

/**
* Populate an OID from raw bytes.
*
* @param oid Destination OID (must not be NULL)
* @param raw Source byte buffer
* @param raw_len Number of bytes provided in `raw`
* @return GM_OK on success, GM_ERR_INVALID_ARGUMENT when parameters are invalid
*/
GM_NODISCARD int gm_oid_from_raw(gm_oid_t *oid, const uint8_t *raw,
size_t raw_len);

/**
* Parse a hexadecimal string into an OID.
*
* @param oid Destination OID (cleared on failure)
* @param hex Null-terminated hex string (must be GM_OID_HEX_CHARS long)
* @return GM_OK on success, GM_ERR_INVALID_ARGUMENT otherwise
*/
GM_NODISCARD int gm_oid_from_hex(gm_oid_t *oid, const char *hex);

#ifdef __cplusplus
}
#endif
Expand Down
3 changes: 2 additions & 1 deletion core/src/adapters/git/libgit2_repository_port.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
#include "gitmind/security/memory.h"
#include "gitmind/security/string.h"
#include "gitmind/types.h"
#include "gitmind/util/oid.h"

struct gm_libgit2_repository_port_state {
git_repository *repo;
Expand Down Expand Up @@ -923,7 +924,7 @@ static gm_result_void_t reference_update_impl(
GM_ERROR(GM_ERR_UNKNOWN, "reference %s missing target", spec->ref_name));
}

if (!git_oid_equal(current_target, spec->target_oid)) {
if (!gm_oid_equal(current_target, spec->target_oid)) {
int descendant = git_graph_descendant_of(state->repo, spec->target_oid,
current_target);
if (descendant < 0) {
Expand Down
61 changes: 11 additions & 50 deletions core/src/cache/query.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,49 +28,6 @@
#define CACHE_PATH_BUFFER_SIZE 128 /* Buffer for cache paths */
#define INITIAL_EDGE_CAPACITY 100 /* Initial allocation for edge arrays */

static int gm_hex_to_raw(const char *hex, uint8_t *out_raw, size_t raw_size);

static int gm_hex_value(char c) {
if (c >= '0' && c <= '9') {
return c - '0';
}
c = (char)(c | 0x20); /* lowercase */
if (c >= 'a' && c <= 'f') {
return c - 'a' + 10;
}
return -1;
}

static int gm_hex_to_raw(const char *hex, uint8_t *out_raw, size_t raw_size) {
if (hex == NULL || out_raw == NULL) {
return GM_ERR_INVALID_ARGUMENT;
}
size_t len = strlen(hex);
if (len != raw_size * 2) {
return GM_ERR_INVALID_ARGUMENT;
}
for (size_t i = 0; i < raw_size; ++i) {
int hi = gm_hex_value(hex[i * 2]);
int lo = gm_hex_value(hex[i * 2 + 1]);
if (hi < 0 || lo < 0) {
return GM_ERR_INVALID_ARGUMENT;
}
out_raw[i] = (uint8_t)((hi << 4) | lo);
}
return GM_OK;
}

static int gm_hex_to_oid(const char *hex, gm_oid_t *oid) {
if (oid == NULL) {
return GM_ERR_INVALID_ARGUMENT;
}
return gm_hex_to_raw(hex, oid->id, GM_OID_RAWSZ);
}

static bool gm_oid_equal(const gm_oid_t *a, const gm_oid_t *b) {
return a != NULL && b != NULL && memcmp(a->id, b->id, GM_OID_RAWSZ) == 0;
}

/* Get SHA prefix for sharding */
static void get_sha_prefix(const uint8_t *sha, char *prefix, int bits) {
int chars = (bits + 3) / BITS_PER_HEX_CHAR; /* Round up to hex chars */
Expand Down Expand Up @@ -203,13 +160,19 @@ bool gm_cache_is_stale(gm_context_t *ctx, const char *branch) {
}

gm_cache_meta_t meta;
gm_memset_safe(&meta, sizeof(meta), 0, sizeof(meta));
if (gm_cache_load_meta(ctx, branch, &meta) != GM_OK) {
return true;
}

/* Check age */
time_t now = time(NULL);
if ((uint64_t)now - meta.journal_tip_time > CACHE_MAX_AGE_SECONDS) {
if (now <= 0) {
return true;
}
uint64_t now_u = (uint64_t)now;
if (now_u > meta.journal_tip_time &&
(now_u - meta.journal_tip_time) > CACHE_MAX_AGE_SECONDS) {
return true;
}

Expand All @@ -229,15 +192,13 @@ bool gm_cache_is_stale(gm_context_t *ctx, const char *branch) {
return true;
}

bool have_binary = (memcmp(&meta.journal_tip_oid_bin, &(const gm_oid_t){0},
sizeof(gm_oid_t)) != 0);
if (have_binary) {
if (!gm_oid_is_zero(&meta.journal_tip_oid_bin)) {
return !gm_oid_equal(&current_tip.oid, &meta.journal_tip_oid_bin);
}

if (meta.journal_tip_oid[0] != '\0') {
gm_oid_t cached_tip;
if (gm_hex_to_oid(meta.journal_tip_oid, &cached_tip) != GM_OK) {
if (gm_oid_from_hex(&cached_tip, meta.journal_tip_oid) != GM_OK) {
return true;
}
return !gm_oid_equal(&current_tip.oid, &cached_tip);
Expand Down Expand Up @@ -343,7 +304,7 @@ static int try_cache_query(gm_context_t *ctx, const char *branch,
return rc;
}

if (memcmp(&meta.cache_tip_oid, &(const gm_oid_t){0}, sizeof(gm_oid_t)) == 0) {
if (gm_oid_is_zero(&meta.cache_tip_oid)) {
return GM_ERR_NOT_FOUND;
}

Expand Down Expand Up @@ -470,7 +431,7 @@ int gm_cache_stats(gm_context_t *ctx, const char *branch,
}

if (cache_size_bytes) {
if (memcmp(&meta.cache_tip_oid, &(const gm_oid_t){0}, sizeof(gm_oid_t)) != 0) {
if (!gm_oid_is_zero(&meta.cache_tip_oid)) {
rc = unwrap_result_code(gm_git_repository_port_commit_tree_size(
&ctx->git_repo_port, &meta.cache_tip_oid, cache_size_bytes));
if (rc != GM_OK) {
Expand Down
10 changes: 7 additions & 3 deletions core/src/domain/cache/edge_map.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@

#include "gitmind/cache/internal/edge_map.h"

#include <git2/oid.h>

#include <stdint.h>
#include <stdlib.h>

#include "gitmind/error.h"
#include "gitmind/util/memory.h"
#include "gitmind/util/oid.h"

struct gm_edge_map_entry {
gm_oid_t oid;
Expand Down Expand Up @@ -83,6 +82,11 @@ gm_result_void_t gm_edge_map_create(size_t bucket_count, gm_edge_map_t **out_map
GM_ERROR(GM_ERR_INVALID_ARGUMENT, "edge map output pointer required"));
}

if (bucket_count == 0U) {
return gm_err_void(GM_ERROR(GM_ERR_INVALID_ARGUMENT,
"edge map bucket count must be > 0"));
}

gm_edge_map_t *map = calloc(1, sizeof(gm_edge_map_t));
if (map == NULL) {
return gm_err_void(GM_ERROR(GM_ERR_OUT_OF_MEMORY, "edge map alloc failed"));
Expand Down Expand Up @@ -127,7 +131,7 @@ gm_result_void_t gm_edge_map_add(gm_edge_map_t *map, const gm_oid_t *oid,
size_t bucket_index = gm_edge_map_hash(oid, map->bucket_count);
struct gm_edge_map_entry *entry = map->buckets[bucket_index];
while (entry != NULL) {
if (git_oid_cmp(&entry->oid, oid) == 0) {
if (gm_oid_equal(&entry->oid, oid)) {
gm_bitmap_add(entry->bitmap, edge_id);
return gm_ok_void();
}
Expand Down
31 changes: 25 additions & 6 deletions core/src/edge/attributed.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "gitmind/types.h"
#include "gitmind/attribution.h"
#include "gitmind/util/memory.h"
#include "gitmind/util/oid.h"
#include "gitmind/edge/internal/blob_identity.h"
#include <stdint.h>
#include "gitmind/security/memory.h"
Expand Down Expand Up @@ -237,11 +238,25 @@ static int decode_attr_ex_impl(const uint8_t *buffer, size_t len,
gm_result_void_t rr = gm_cbor_read_text(buffer, &offset, len, out.ulid, GM_ULID_SIZE + 1);
if (!rr.ok) return GM_ERR_INVALID_FORMAT; break; }
case GM_CBOR_KEY_SRC_OID: {
uint8_t raw[GM_OID_RAWSZ]; gm_result_void_t rr = gm_cbor_read_bytes(buffer, &offset, len, raw, GM_OID_RAWSZ);
if (!rr.ok) return GM_ERR_INVALID_FORMAT; git_oid_fromraw(&out.src_oid, raw); break; }
uint8_t raw[GM_OID_RAWSZ];
gm_result_void_t rr =
gm_cbor_read_bytes(buffer, &offset, len, raw, GM_OID_RAWSZ);
if (!rr.ok) return GM_ERR_INVALID_FORMAT;
if (gm_oid_from_raw(&out.src_oid, raw, GM_OID_RAWSZ) != GM_OK) {
return GM_ERR_INVALID_FORMAT;
}
break;
}
case GM_CBOR_KEY_TGT_OID: {
uint8_t raw[GM_OID_RAWSZ]; gm_result_void_t rr = gm_cbor_read_bytes(buffer, &offset, len, raw, GM_OID_RAWSZ);
if (!rr.ok) return GM_ERR_INVALID_FORMAT; git_oid_fromraw(&out.tgt_oid, raw); break; }
uint8_t raw[GM_OID_RAWSZ];
gm_result_void_t rr =
gm_cbor_read_bytes(buffer, &offset, len, raw, GM_OID_RAWSZ);
if (!rr.ok) return GM_ERR_INVALID_FORMAT;
if (gm_oid_from_raw(&out.tgt_oid, raw, GM_OID_RAWSZ) != GM_OK) {
return GM_ERR_INVALID_FORMAT;
}
break;
}
case GM_CBOR_KEY_SOURCE_TYPE: {
gm_result_uint64_t rr = gm_cbor_read_uint(buffer, &offset, len);
if (!rr.ok) return GM_ERR_INVALID_FORMAT; out.attribution.source_type = (gm_source_type_t)rr.u.val; break; }
Expand All @@ -262,8 +277,12 @@ static int decode_attr_ex_impl(const uint8_t *buffer, size_t len,
}
}

if (git_oid_is_zero(&out.src_oid)) git_oid_fromraw(&out.src_oid, out.src_sha);
if (git_oid_is_zero(&out.tgt_oid)) git_oid_fromraw(&out.tgt_oid, out.tgt_sha);
if (gm_oid_is_zero(&out.src_oid)) {
(void)gm_oid_from_raw(&out.src_oid, out.src_sha, GM_OID_RAWSZ);
}
if (gm_oid_is_zero(&out.tgt_oid)) {
(void)gm_oid_from_raw(&out.tgt_oid, out.tgt_sha, GM_OID_RAWSZ);
}
Comment on lines +280 to +285

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.

⚠️ Potential issue | 🔴 Critical

Fix OID backfill length mismatch (potential OOB read)

You pass GM_OID_RAWSZ while sourcing from out.src_sha/out.tgt_sha (GM_SHA1_SIZE). That can read past the SHA buffer or fail. Zero‑pad into a GM_OID_RAWSZ scratch first.

-    if (gm_oid_is_zero(&out.src_oid)) {
-        (void)gm_oid_from_raw(&out.src_oid, out.src_sha, GM_OID_RAWSZ);
-    }
-    if (gm_oid_is_zero(&out.tgt_oid)) {
-        (void)gm_oid_from_raw(&out.tgt_oid, out.tgt_sha, GM_OID_RAWSZ);
-    }
+    if (gm_oid_is_zero(&out.src_oid)) {
+        uint8_t tmp[GM_OID_RAWSZ];
+        gm_memset_safe(tmp, sizeof tmp, 0, sizeof tmp);
+        gm_memcpy_span(tmp, sizeof tmp, out.src_sha, GM_SHA1_SIZE);
+        (void)gm_oid_from_raw(&out.src_oid, tmp, GM_OID_RAWSZ);
+    }
+    if (gm_oid_is_zero(&out.tgt_oid)) {
+        uint8_t tmp[GM_OID_RAWSZ];
+        gm_memset_safe(tmp, sizeof tmp, 0, sizeof tmp);
+        gm_memcpy_span(tmp, sizeof tmp, out.tgt_sha, GM_SHA1_SIZE);
+        (void)gm_oid_from_raw(&out.tgt_oid, tmp, GM_OID_RAWSZ);
+    }

As per coding guidelines (zero outputs on error paths; use gm_memcpy_span). Based on learnings.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (gm_oid_is_zero(&out.src_oid)) {
(void)gm_oid_from_raw(&out.src_oid, out.src_sha, GM_OID_RAWSZ);
}
if (gm_oid_is_zero(&out.tgt_oid)) {
(void)gm_oid_from_raw(&out.tgt_oid, out.tgt_sha, GM_OID_RAWSZ);
}
if (gm_oid_is_zero(&out.src_oid)) {
uint8_t tmp[GM_OID_RAWSZ];
gm_memset_safe(tmp, sizeof tmp, 0, sizeof tmp);
gm_memcpy_span(tmp, sizeof tmp, out.src_sha, GM_SHA1_SIZE);
(void)gm_oid_from_raw(&out.src_oid, tmp, GM_OID_RAWSZ);
}
if (gm_oid_is_zero(&out.tgt_oid)) {
uint8_t tmp[GM_OID_RAWSZ];
gm_memset_safe(tmp, sizeof tmp, 0, sizeof tmp);
gm_memcpy_span(tmp, sizeof tmp, out.tgt_sha, GM_SHA1_SIZE);
(void)gm_oid_from_raw(&out.tgt_oid, tmp, GM_OID_RAWSZ);
}


*e = out; *consumed = offset; return GM_OK;
}
Expand Down
31 changes: 19 additions & 12 deletions core/src/edge/edge.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include "gitmind/security/string.h"
#include "gitmind/security/memory.h"
#include "gitmind/util/memory.h"
#include "gitmind/util/oid.h"
#include "gitmind/cbor/keys.h"
#include <stdint.h>
#include <string.h>
Expand Down Expand Up @@ -114,8 +115,8 @@ bool gm_edge_equal(const gm_edge_t *edge_a, const gm_edge_t *edge_b) {
}

/* Source: if both OIDs present, they must match; otherwise fallback to legacy SHA */
if (!git_oid_is_zero(&edge_a->src_oid) && !git_oid_is_zero(&edge_b->src_oid)) {
if (git_oid_cmp(&edge_a->src_oid, &edge_b->src_oid) != 0) {
if (!gm_oid_is_zero(&edge_a->src_oid) && !gm_oid_is_zero(&edge_b->src_oid)) {
if (!gm_oid_equal(&edge_a->src_oid, &edge_b->src_oid)) {
return false;
}
} else {
Expand All @@ -125,8 +126,8 @@ bool gm_edge_equal(const gm_edge_t *edge_a, const gm_edge_t *edge_b) {
}

/* Target: same rule as Source */
if (!git_oid_is_zero(&edge_a->tgt_oid) && !git_oid_is_zero(&edge_b->tgt_oid)) {
if (git_oid_cmp(&edge_a->tgt_oid, &edge_b->tgt_oid) != 0) {
if (!gm_oid_is_zero(&edge_a->tgt_oid) && !gm_oid_is_zero(&edge_b->tgt_oid)) {
if (!gm_oid_equal(&edge_a->tgt_oid, &edge_b->tgt_oid)) {
return false;
}
} else {
Expand Down Expand Up @@ -318,7 +319,7 @@ gm_result_void_t gm_edge_encode_cbor(const gm_edge_t *edge, uint8_t *buffer,
uint8_t src_oid_bytes[GM_OID_RAWSZ] = {0};
uint8_t tgt_oid_bytes[GM_OID_RAWSZ] = {0};

if (!git_oid_is_zero(&edge->src_oid)) {
if (!gm_oid_is_zero(&edge->src_oid)) {
(void)gm_memcpy_span(src_oid_bytes, sizeof(src_oid_bytes), edge->src_oid.id,
GM_OID_RAWSZ);
} else {
Expand All @@ -331,7 +332,7 @@ gm_result_void_t gm_edge_encode_cbor(const gm_edge_t *edge, uint8_t *buffer,
}
}

if (!git_oid_is_zero(&edge->tgt_oid)) {
if (!gm_oid_is_zero(&edge->tgt_oid)) {
(void)gm_memcpy_span(tgt_oid_bytes, sizeof(tgt_oid_bytes), edge->tgt_oid.id,
GM_OID_RAWSZ);
} else {
Expand Down Expand Up @@ -436,14 +437,20 @@ static gm_result_void_t decode_cbor_field(const uint8_t *buffer, size_t *offset,
uint8_t raw[GM_OID_RAWSZ];
gm_result_void_t r = gm_cbor_read_bytes(buffer, offset, len, raw, GM_OID_RAWSZ);
if (!r.ok) return r;
git_oid_fromraw(&edge->src_oid, raw);
if (gm_oid_from_raw(&edge->src_oid, raw, GM_OID_RAWSZ) != GM_OK) {
return gm_err_void(
GM_ERROR(GM_ERR_INVALID_FORMAT, "Invalid source OID payload"));
}
return gm_ok_void();
}
case GM_CBOR_KEY_TGT_OID: {
uint8_t raw[GM_OID_RAWSZ];
gm_result_void_t r = gm_cbor_read_bytes(buffer, offset, len, raw, GM_OID_RAWSZ);
if (!r.ok) return r;
git_oid_fromraw(&edge->tgt_oid, raw);
if (gm_oid_from_raw(&edge->tgt_oid, raw, GM_OID_RAWSZ) != GM_OK) {
return gm_err_void(
GM_ERROR(GM_ERR_INVALID_FORMAT, "Invalid target OID payload"));
}
return gm_ok_void();
}
default:
Expand Down Expand Up @@ -482,11 +489,11 @@ static int gm_edge_decode_cbor_ex_impl(const uint8_t *buffer, size_t len, gm_edg
}

/* Backfill OIDs from legacy SHA if missing */
if (git_oid_is_zero(&edge.src_oid)) {
git_oid_fromraw(&edge.src_oid, edge.src_sha);
if (gm_oid_is_zero(&edge.src_oid)) {
(void)gm_oid_from_raw(&edge.src_oid, edge.src_sha, GM_OID_RAWSZ);
}
if (git_oid_is_zero(&edge.tgt_oid)) {
git_oid_fromraw(&edge.tgt_oid, edge.tgt_sha);
if (gm_oid_is_zero(&edge.tgt_oid)) {
(void)gm_oid_from_raw(&edge.tgt_oid, edge.tgt_sha, GM_OID_RAWSZ);
}
Comment on lines +492 to 497

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.

⚠️ Potential issue | 🔴 Critical

Fix OID backfill length mismatch (potential OOB read)

As in attributed.c, passing GM_OID_RAWSZ while sourcing from GM_SHA1_SIZE buffers risks OOB. Zero‑pad into a GM_OID_RAWSZ scratch first.

-    if (gm_oid_is_zero(&edge.src_oid)) {
-        (void)gm_oid_from_raw(&edge.src_oid, edge.src_sha, GM_OID_RAWSZ);
-    }
-    if (gm_oid_is_zero(&edge.tgt_oid)) {
-        (void)gm_oid_from_raw(&edge.tgt_oid, edge.tgt_sha, GM_OID_RAWSZ);
-    }
+    if (gm_oid_is_zero(&edge.src_oid)) {
+        uint8_t tmp[GM_OID_RAWSZ];
+        gm_memset_safe(tmp, sizeof tmp, 0, sizeof tmp);
+        gm_memcpy_span(tmp, sizeof tmp, edge.src_sha, GM_SHA1_SIZE);
+        (void)gm_oid_from_raw(&edge.src_oid, tmp, GM_OID_RAWSZ);
+    }
+    if (gm_oid_is_zero(&edge.tgt_oid)) {
+        uint8_t tmp[GM_OID_RAWSZ];
+        gm_memset_safe(tmp, sizeof tmp, 0, sizeof tmp);
+        gm_memcpy_span(tmp, sizeof tmp, edge.tgt_sha, GM_SHA1_SIZE);
+        (void)gm_oid_from_raw(&edge.tgt_oid, tmp, GM_OID_RAWSZ);
+    }

As per coding guidelines (safe memory ops; zero on error).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (gm_oid_is_zero(&edge.src_oid)) {
(void)gm_oid_from_raw(&edge.src_oid, edge.src_sha, GM_OID_RAWSZ);
}
if (git_oid_is_zero(&edge.tgt_oid)) {
git_oid_fromraw(&edge.tgt_oid, edge.tgt_sha);
if (gm_oid_is_zero(&edge.tgt_oid)) {
(void)gm_oid_from_raw(&edge.tgt_oid, edge.tgt_sha, GM_OID_RAWSZ);
}
if (gm_oid_is_zero(&edge.src_oid)) {
uint8_t tmp[GM_OID_RAWSZ];
gm_memset_safe(tmp, sizeof tmp, 0, sizeof tmp);
gm_memcpy_span(tmp, sizeof tmp, edge.src_sha, GM_SHA1_SIZE);
(void)gm_oid_from_raw(&edge.src_oid, tmp, GM_OID_RAWSZ);
}
if (gm_oid_is_zero(&edge.tgt_oid)) {
uint8_t tmp[GM_OID_RAWSZ];
gm_memset_safe(tmp, sizeof tmp, 0, sizeof tmp);
gm_memcpy_span(tmp, sizeof tmp, edge.tgt_sha, GM_SHA1_SIZE);
(void)gm_oid_from_raw(&edge.tgt_oid, tmp, GM_OID_RAWSZ);
}
🤖 Prompt for AI Agents
In core/src/edge/edge.c around lines 492 to 497, the code calls gm_oid_from_raw
using edge.src_sha/edge.tgt_sha buffers sized GM_SHA1_SIZE but passes
GM_OID_RAWSZ which can read past the source buffer; to fix, allocate a
GM_OID_RAWSZ-byte scratch array, zero it, copy GM_SHA1_SIZE bytes from the SHA
buffer into the scratch, then call gm_oid_from_raw with the scratch and
GM_OID_RAWSZ; ensure the scratch is zeroed before use and that any failure from
gm_oid_from_raw leaves the oid zeroed (follow existing safe-memory/error-zeroing
patterns).


*edge_out = edge;
Expand Down
Loading
Loading