Skip to content

Commit 37fee8f

Browse files
committed
rework merged-record provenance: uncapped + method bitmask
Size provenance to MAX_COMPONENTS so a record holds every contributor — overflow is impossible by construction, retiring the 8-entry cap, its warning, and the saturation flag that truncated attribution on leaky systems. Replace the per-contributor methods[] array (only [0] was ever read) with a method_set bitmask; result_method now returns the strongest method, consistent with the resolved confidence, not the earliest contributor's. JSON and verbose text surface the method diversity; the default Leaks line clamps to the first few contributors + "+N more" (verbose lists all).
1 parent 423fa61 commit 37fee8f

9 files changed

Lines changed: 210 additions & 99 deletions

File tree

src/include/kasld/constraint.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@
2424

2525
#include <stdint.h>
2626

27-
/* Cap on lineage entries per constraint (matches MAX_PROVENANCE in spirit). */
27+
/* Cap on lineage entries per constraint: an engine-side termination bound on
28+
* per-constraint provenance depth. Independent of the display-side
29+
* MAX_PROVENANCE (which now equals MAX_COMPONENTS). */
2830
#ifndef MAX_LINEAGE
2931
#define MAX_LINEAGE 8
3032
#endif

src/include/kasld/internal.h

Lines changed: 74 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,10 @@
2828
#define MAX_RESULTS 4096
2929
#define NAME_LEN 48 /* specific instance: kernel symbol, ACPI ID, BDF, ... */
3030
#define ORIGIN_LEN 64 /* component name (orchestrator-filled) */
31-
#define METHOD_LEN 16 /* method: meta value */
32-
#define MAX_PROVENANCE 8 /* cap on merged-record contributors */
31+
/* A merged record can be corroborated by at most every component, so size its
32+
* provenance to the structural maximum — overflow is impossible by
33+
* construction. */
34+
#define MAX_PROVENANCE MAX_COMPONENTS
3335
/* Captured-stdout lines are kept only for --verbose / --json output. The log
3436
* is grown geometrically on demand from this initial capacity; there is no
3537
* hard cap (the previous fixed cap of 64 silently truncated noisy components).
@@ -119,6 +121,72 @@ enum kasld_set_bits {
119121
};
120122
#endif
121123

124+
/* Analytical method a component claims for a leak (the `method:` meta value).
125+
* Closed set, ordered weakest->strongest so the strongest member of a set is
126+
* the highest bit. A merged record stores the union of its contributors'
127+
* methods as a bitmask (method_set); this is the only place the names live. */
128+
enum kasld_method {
129+
KM_DETECTION = 0,
130+
KM_BRUTE,
131+
KM_TIMING,
132+
KM_HEURISTIC,
133+
KM_INFERRED,
134+
KM_DERIVED,
135+
KM_PARSED,
136+
KM_COUNT
137+
};
138+
139+
static inline const char *kasld_method_name(enum kasld_method m) {
140+
switch (m) {
141+
case KM_DETECTION:
142+
return "detection";
143+
case KM_BRUTE:
144+
return "brute";
145+
case KM_TIMING:
146+
return "timing";
147+
case KM_HEURISTIC:
148+
return "heuristic";
149+
case KM_INFERRED:
150+
return "inferred";
151+
case KM_DERIVED:
152+
return "derived";
153+
case KM_PARSED:
154+
return "parsed";
155+
case KM_COUNT:
156+
break;
157+
}
158+
return "unknown";
159+
}
160+
161+
/* Strongest method present in a set, as a display string ("unknown" if empty).
162+
*/
163+
static inline const char *kasld_method_set_strongest(uint16_t set) {
164+
for (int m = KM_COUNT - 1; m >= 0; m--)
165+
if (set & (1u << m))
166+
return kasld_method_name((enum kasld_method)m);
167+
return "unknown";
168+
}
169+
170+
/* Format a method set strongest-first as "parsed+timing" into buf ("unknown" if
171+
* empty). Surfaces method diversity where a single line has room (verbose). */
172+
static inline void kasld_method_set_str(uint16_t set, char *buf, size_t sz) {
173+
if (sz == 0)
174+
return;
175+
buf[0] = '\0';
176+
size_t o = 0;
177+
for (int m = KM_COUNT - 1; m >= 0; m--) {
178+
if (!(set & (1u << m)))
179+
continue;
180+
int w = snprintf(buf + o, sz - o, "%s%s", o ? "+" : "",
181+
kasld_method_name((enum kasld_method)m));
182+
if (w < 0 || (size_t)w >= sz - o)
183+
break;
184+
o += (size_t)w;
185+
}
186+
if (o == 0)
187+
snprintf(buf, sz, "unknown");
188+
}
189+
122190
struct result {
123191
enum kasld_addr_type type;
124192
enum kasld_region region;
@@ -132,9 +200,11 @@ struct result {
132200
enum kasld_position pos;
133201
enum kasld_confidence conf;
134202

135-
/* Provenance — earliest contributor at index 0. */
203+
/* Provenance: the components that corroborate this record (origins[0] is the
204+
* earliest contributor) and method_set, the union of their methods. Sized to
205+
* the structural max (MAX_COMPONENTS) so it can never overflow. */
136206
char origins[MAX_PROVENANCE][ORIGIN_LEN];
137-
char methods[MAX_PROVENANCE][METHOD_LEN];
207+
uint16_t method_set; /* bitmask over enum kasld_method */
138208
uint8_t provenance_count;
139209
};
140210

src/include/kasld/render_internal.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@
2020
*/
2121
const char *human_size(unsigned long bytes, char *buf, size_t bufsz);
2222

23-
/* Result-model helpers (mirror anchor_addr(). methods[0] is the canonical
24-
* single-value method; origins are iterated as r->origins[0..provenance_count]
25-
* at the consuming renderer). */
23+
/* Result-model helpers (mirror anchor_addr(). result_method returns the
24+
* strongest method in the record's method_set; origins are iterated as
25+
* r->origins[0..provenance_count] at the consuming renderer). */
2626
const char *result_method(const struct result *r);
2727
const char *result_section(const struct result *r);
2828
int in_bounds(const struct result *r);

src/orchestrator.c

Lines changed: 35 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -164,8 +164,6 @@ int num_comp_logs;
164164
*/
165165
enum orchestrator_saturation {
166166
ORCH_SAT_RESULTS_FULL = 1u << 0, /* MAX_RESULTS hit; drops new records */
167-
ORCH_SAT_PROVENANCE_FULL =
168-
1u << 1, /* MAX_PROVENANCE hit; drops later contributors */
169167
ORCH_SAT_COMPONENT_LINES_DROPPED =
170168
1u << 2, /* alloc failure during verbose-line capture */
171169
};
@@ -560,6 +558,30 @@ static enum kasld_confidence conf_from_wire(const char *s) {
560558
return CONF_UNKNOWN;
561559
}
562560

561+
/* Map a method: meta value to its bit in struct result.method_set (0 if the
562+
* value is empty or unrecognised). Mirrors the closed set in enum kasld_method.
563+
*/
564+
static uint16_t method_bit(const char *s) {
565+
int m = -1;
566+
if (!s || !*s)
567+
return 0;
568+
if (strcmp(s, "parsed") == 0)
569+
m = KM_PARSED;
570+
else if (strcmp(s, "derived") == 0)
571+
m = KM_DERIVED;
572+
else if (strcmp(s, "inferred") == 0)
573+
m = KM_INFERRED;
574+
else if (strcmp(s, "heuristic") == 0)
575+
m = KM_HEURISTIC;
576+
else if (strcmp(s, "timing") == 0)
577+
m = KM_TIMING;
578+
else if (strcmp(s, "brute") == 0)
579+
m = KM_BRUTE;
580+
else if (strcmp(s, "detection") == 0)
581+
m = KM_DETECTION;
582+
return m < 0 ? 0u : (uint16_t)(1u << m);
583+
}
584+
563585
/* Power-of-two test, allowing v=0 to mean "no constraint" but the caller
564586
* gates on v != 0 separately. */
565587
static int is_pow2(unsigned long v) { return v && !(v & (v - 1)); }
@@ -879,11 +901,7 @@ static int capture_result(const char *line, const char *method,
879901
memcpy(r->origins[0], origin, ol);
880902
r->origins[0][ol] = '\0';
881903
}
882-
if (method && *method) {
883-
size_t ml = strnlen(method, METHOD_LEN - 1);
884-
memcpy(r->methods[0], method, ml);
885-
r->methods[0][ml] = '\0';
886-
}
904+
r->method_set = method_bit(method);
887905
r->provenance_count = 1;
888906
return 1;
889907
}
@@ -1710,22 +1728,14 @@ static int provenance_has(const struct result *r, const char *s) {
17101728
return 0;
17111729
}
17121730

1713-
static void provenance_add(struct result *r, const char *origin,
1714-
const char *method) {
1731+
static void provenance_add(struct result *r, const char *origin) {
17151732
if (origin && *origin && provenance_has(r, origin))
17161733
return;
1717-
if (r->provenance_count >= MAX_PROVENANCE) {
1718-
orchestrator_saturation |= ORCH_SAT_PROVENANCE_FULL;
1719-
static int warned;
1720-
if (!warned && !quiet) {
1721-
fprintf(stderr,
1722-
"warning: merged record provenance capped at MAX_PROVENANCE=%d; "
1723-
"later contributors dropped\n",
1724-
MAX_PROVENANCE);
1725-
warned = 1;
1726-
}
1734+
/* Distinct origins <= number of components <= MAX_PROVENANCE, so this guard
1735+
* never binds; it only keeps the write in-bounds if that invariant changes.
1736+
*/
1737+
if (r->provenance_count >= MAX_PROVENANCE)
17271738
return;
1728-
}
17291739
int slot = r->provenance_count++;
17301740
if (origin && *origin) {
17311741
size_t ol = strnlen(origin, ORIGIN_LEN - 1);
@@ -1734,13 +1744,6 @@ static void provenance_add(struct result *r, const char *origin,
17341744
} else {
17351745
r->origins[slot][0] = '\0';
17361746
}
1737-
if (method && *method) {
1738-
size_t ml = strnlen(method, METHOD_LEN - 1);
1739-
memcpy(r->methods[slot], method, ml);
1740-
r->methods[slot][ml] = '\0';
1741-
} else {
1742-
r->methods[slot][0] = '\0';
1743-
}
17441747
}
17451748

17461749
static void merge_into(struct result *a, const struct result *b,
@@ -1782,8 +1785,9 @@ static void merge_into(struct result *a, const struct result *b,
17821785
}
17831786
if (conf_weight(b->conf) > conf_weight(a->conf))
17841787
a->conf = b->conf;
1788+
a->method_set |= b->method_set;
17851789
for (int i = 0; i < b->provenance_count; i++)
1786-
provenance_add(a, b->origins[i], b->methods[i]);
1790+
provenance_add(a, b->origins[i]);
17871791
}
17881792

17891793
static int merge_consistent(const struct result *a) {
@@ -2454,9 +2458,9 @@ static void engine_report_saturation(const struct engine *e) {
24542458
ESTIMATE_MAX_CONFLICTS);
24552459
}
24562460

2457-
/* Sibling reporter for orchestrator-side caps (results[], merged-record
2458-
* provenance, per-component verbose-line capture). Same diagnostic shape
2459-
* as engine_report_saturation; surfaces under --verbose. */
2461+
/* Sibling reporter for orchestrator-side caps (results[], per-component
2462+
* verbose-line capture). Same diagnostic shape as engine_report_saturation;
2463+
* surfaces under --verbose. */
24602464
static void orchestrator_report_saturation(void) {
24612465
if (!orchestrator_saturation)
24622466
return;
@@ -2465,12 +2469,6 @@ static void orchestrator_report_saturation(void) {
24652469
"[orchestrator] saturation: MAX_RESULTS (%d) reached; "
24662470
"further leak/scalar observations were dropped at capture\n",
24672471
MAX_RESULTS);
2468-
if (orchestrator_saturation & ORCH_SAT_PROVENANCE_FULL)
2469-
fprintf(stderr,
2470-
"[orchestrator] saturation: MAX_PROVENANCE (%d) reached on at "
2471-
"least one merged record; additional contributors were not "
2472-
"recorded (the record's resolved value is unaffected)\n",
2473-
MAX_PROVENANCE);
24742472
if (orchestrator_saturation & ORCH_SAT_COMPONENT_LINES_DROPPED)
24752473
fprintf(stderr,
24762474
"[orchestrator] saturation: allocation failure while capturing "

src/render.c

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,16 +49,17 @@ const char *human_size(unsigned long bytes, char *buf, size_t bufsz) {
4949
* Result-model helpers
5050
*
5151
* Mirror the orchestrator's anchor_addr() and the result_in_bounds()
52-
* convention. methods[0]/origins[0] are the earliest contributor for a
53-
* merged record; for the renderer this is the canonical display value.
52+
* convention. A merged record carries method_set (the union of its
53+
* contributors' methods); the single display value is the strongest method
54+
* present, which stays consistent with the record's resolved confidence.
5455
* -------------------------------------------------------------------------
5556
*/
5657
/* anchor_addr() is defined as a static inline in kasld/internal.h. */
5758

5859
const char *result_method(const struct result *r) {
59-
if (!r || r->provenance_count == 0 || r->methods[0][0] == '\0')
60+
if (!r)
6061
return "unknown";
61-
return r->methods[0];
62+
return kasld_method_set_strongest(r->method_set);
6263
}
6364

6465
const char *result_section(const struct result *r) {

src/render/json.c

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,20 @@ static void render_json_group(enum kasld_addr_type gt, const char *gs) {
130130
}
131131
printf("],\n");
132132
printf(" \"method\": ");
133-
json_print_escaped(result_method(r));
133+
json_print_escaped(
134+
result_method(r)); /* single strongest, for compatibility */
134135
printf(",\n");
136+
printf(" \"methods\": [");
137+
{
138+
int firstm = 1;
139+
for (int m = 0; m < KM_COUNT; m++)
140+
if (r->method_set & (1u << m)) {
141+
printf(firstm ? "" : ", ");
142+
json_print_escaped(kasld_method_name((enum kasld_method)m));
143+
firstm = 0;
144+
}
145+
}
146+
printf("],\n");
135147
printf(" \"valid\": %s\n", in_bounds(r) ? "true" : "false");
136148
printf(" }");
137149
}

src/render/text.c

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -298,21 +298,25 @@ static void print_group(enum kasld_addr_type type, const char *section,
298298

299299
if (!in_bounds(r)) {
300300
if (verbose) {
301+
char mbuf[64];
302+
kasld_method_set_str(r->method_set, mbuf, sizeof mbuf);
301303
printf(" %s0x%016lx%s %s %s(", c(C_RED), a, c(C_RESET), rn, c(C_DIM));
302304
for (int j = 0; j < r->provenance_count; j++)
303305
printf("%s%s", j ? ", " : "", r->origins[j]);
304-
printf(", %s, stale)%s\n", result_method(r), c(C_RESET));
306+
printf(", %s, stale)%s\n", mbuf, c(C_RESET));
305307
} else
306308
printf(" %s0x%016lx%s %s %s(stale)%s\n", c(C_RED), a, c(C_RESET), rn,
307309
c(C_DIM), c(C_RESET));
308310
continue;
309311
}
310312

311313
if (verbose) {
314+
char mbuf[64];
315+
kasld_method_set_str(r->method_set, mbuf, sizeof mbuf);
312316
printf(" %s0x%016lx%s %s %s(", c(C_GREEN), a, c(C_RESET), rn, c(C_DIM));
313317
for (int j = 0; j < r->provenance_count; j++)
314318
printf("%s%s", j ? ", " : "", r->origins[j]);
315-
printf(", %s)%s\n", result_method(r), c(C_RESET));
319+
printf(", %s)%s\n", mbuf, c(C_RESET));
316320
} else
317321
printf(" %s0x%016lx%s %s\n", c(C_GREEN), a, c(C_RESET), rn);
318322

@@ -1158,9 +1162,11 @@ static int readout_print_leaks(void) {
11581162
* same address tagged under different symbol names (e.g. _stext from
11591163
* proc_kallsyms vs an unnamed text leak) lands in separate merged records.
11601164
* Aggregate provenance across all in-bounds records of this (type, region),
1161-
* de-duplicated, so the bracket lists the full set of contributors. */
1162-
char seen[24][ORIGIN_LEN];
1163-
int ns = 0, more = 0;
1165+
* de-duplicated. seen[] is sized to the structural max so the distinct
1166+
* count is exact; the line shows the first few and folds the rest into
1167+
* "+N more" (verbose lists them all). */
1168+
char seen[MAX_COMPONENTS][ORIGIN_LEN];
1169+
int ns = 0;
11641170
for (int j = 0; j < num_results; j++) {
11651171
const struct result *r = &results[j];
11661172
if (r->type != found[i].r->type || r->region != found[i].r->region ||
@@ -1173,25 +1179,23 @@ static int readout_print_leaks(void) {
11731179
dup = 1;
11741180
break;
11751181
}
1176-
if (dup)
1177-
continue;
1178-
if (ns < (int)(sizeof(seen) / sizeof(seen[0])))
1182+
if (!dup && ns < (int)(sizeof(seen) / sizeof(seen[0])))
11791183
snprintf(seen[ns++], ORIGIN_LEN, "%s", r->origins[p]);
1180-
else
1181-
more++;
11821184
}
11831185
}
11841186
if (ns == 0) {
11851187
printf(" %-19s %s0x%016lx%s\n", found[i].label, c(C_GREEN),
11861188
found[i].addr, c(C_RESET));
11871189
continue;
11881190
}
1191+
/* Names that fit a default ~80-col line; the rest fold into "+N more". */
1192+
const int shown = ns < 3 ? ns : 3;
11891193
printf(" %-19s %s0x%016lx%s %s(", found[i].label, c(C_GREEN),
11901194
found[i].addr, c(C_RESET), c(C_DIM));
1191-
for (int idx = 0; idx < ns; idx++)
1195+
for (int idx = 0; idx < shown; idx++)
11921196
printf("%s%s", idx ? ", " : "", seen[idx]);
1193-
if (more)
1194-
printf(", +%d more", more);
1197+
if (ns > shown)
1198+
printf(", +%d more", ns - shown);
11951199
printf(")%s\n", c(C_RESET));
11961200
}
11971201
return nf;

0 commit comments

Comments
 (0)