address uaf in portfolio command watchlist
All checks were successful
Generic zig build / build (push) Successful in 5m45s
Generic zig build / publish-macos (push) Successful in 12s
Generic zig build / deploy (push) Successful in 2m33s

This commit is contained in:
Emil Lerch 2026-07-06 13:12:10 -07:00
parent 473a94976d
commit 9aaa374f12
Signed by: lobo
GPG key ID: A7B62D657EF764F8

View file

@ -181,8 +181,23 @@ pub fn run(ctx: *framework.RunCtx, parsed: ParsedArgs) !void {
}
}.f);
// Separate watchlist file (backward compat). Loaded here at the run
// scope - NOT inside the collection block below - so its symbol
// strings outlive the `display` call at the end of `run`.
// `watch_list` (and `watch_prices`' keys) borrow these slices;
// freeing them before display - the previous behavior, where the
// load + `defer freeWatchlist` lived inside the collection block -
// rendered the file's watch symbols as freed-memory garbage on the
// CLI. The TUI is unaffected: it keeps watchlist symbols in its
// long-lived arena.
const wl_syms: ?[][]const u8 = if (watchlist_path) |wl_path|
cli.loadWatchlist(io, allocator, wl_path)
else
null;
defer cli.freeWatchlist(allocator, wl_syms);
// Collect watch symbols and their prices for display.
// Includes watch lots from portfolio + symbols from separate watchlist file.
// Includes watch lots from portfolio + symbols from the watchlist file.
var watch_list: std.ArrayList([]const u8) = .empty;
defer watch_list.deinit(allocator);
var watch_prices = std.StringHashMap(f64).init(allocator);
@ -208,18 +223,14 @@ pub fn run(ctx: *framework.RunCtx, parsed: ParsedArgs) !void {
}
}
// Separate watchlist file (backward compat)
if (watchlist_path) |wl_path| {
const wl_syms = cli.loadWatchlist(io, allocator, wl_path);
defer cli.freeWatchlist(allocator, wl_syms);
if (wl_syms) |syms_list| {
for (syms_list) |sym| {
if (watch_seen.contains(sym)) continue;
try watch_seen.put(sym, {});
try watch_list.append(allocator, sym);
if (svc.getCachedLastClose(sym)) |close| {
try watch_prices.put(sym, close);
}
// Symbols from the separate watchlist file (loaded above).
if (wl_syms) |syms_list| {
for (syms_list) |sym| {
if (watch_seen.contains(sym)) continue;
try watch_seen.put(sym, {});
try watch_list.append(allocator, sym);
if (svc.getCachedLastClose(sym)) |close| {
try watch_prices.put(sym, close);
}
}
}