lightningd: fix false positive in leak detection.

We ask each channeld to report its leaks, and keep a refcount of how
many are outstanding.  When the channeld replies, or dies, we decrement
the count in a destructor.

But if the last channeld we're waiting for dies, we can call the
destructor in an unexpected place, and thus run leak detection when
we were partway through some operation, which gives a false positive.
Here's a backtrace showing it:

```
0x5f93405897e3 send_backtrace
	common/daemon.c:33
0x5f93405381cf finish_report
	lightningd/memdump.c:139
0x5f93405382a0 leak_detect_req_done
	lightningd/memdump.c:172
0x5f9340705c41 notify
	ccan/ccan/tal/tal.c:243
0x5f9340705ca5 del_tree
	ccan/ccan/tal/tal.c:437
0x5f9340705ce8 del_tree
	ccan/ccan/tal/tal.c:447
0x5f93407061f3 tal_free
	ccan/ccan/tal/tal.c:532
0x5f9340563f5a subd_release_channel
	lightningd/subd.c:924
0x5f934050fb91 channel_set_owner
	lightningd/channel.c:31
0x5f9340511b84 channel_err
	lightningd/channel.c:1081
0x5f93405121a3 channel_fail_transient
	lightningd/channel.c:1095
0x5f934054e547 peer_channels_cleanup
	lightningd/peer_control.c:187
0x5f9340550411 peer_connected
	lightningd/peer_control.c:1689
0x5f9340525101 connectd_msg
	lightningd/connect_control.c:629
```

Instead, do the lightningd detection up-front, where it's in a
clean environment.

Reported-by: Shahana Farooqui
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
Rusty Russell 2024-08-03 22:02:29 +09:30 committed by Vincenzo Palazzo
parent 0b4b4c5e03
commit 2e06d814e5

View File

@ -96,9 +96,6 @@ static void memleak_log(struct logger *log, const char *fmt, ...)
static void finish_report(const struct leak_detect *leaks)
{
struct htable *memtable;
struct command *cmd;
struct lightningd *ld;
struct json_stream *response;
/* If it timed out, we free ourselved and exit! */
@ -107,34 +104,7 @@ static void finish_report(const struct leak_detect *leaks)
return;
}
/* Convenience variables */
cmd = leaks->cmd;
ld = cmd->ld;
/* Enter everything, except this cmd and its jcon */
memtable = memleak_start(cmd);
/* This command is not a leak! */
memleak_ptr(memtable, cmd);
memleak_ignore_children(memtable, cmd);
/* First delete known false positives. */
memleak_scan_htable(memtable, &ld->topology->txwatches->raw);
memleak_scan_htable(memtable, &ld->topology->txowatches->raw);
memleak_scan_htable(memtable, &ld->topology->outgoing_txs->raw);
memleak_scan_htable(memtable, &ld->htlcs_in->raw);
memleak_scan_htable(memtable, &ld->htlcs_out->raw);
memleak_scan_htable(memtable, &ld->htlc_sets->raw);
memleak_scan_htable(memtable, &ld->peers->raw);
memleak_scan_htable(memtable, &ld->peers_by_dbid->raw);
/* Now delete ld and those which it has pointers to. */
memleak_scan_obj(memtable, ld);
if (dump_memleak(memtable, memleak_log, ld->log))
tal_arr_expand(&leaks->leakers, "lightningd");
response = json_stream_success(cmd);
response = json_stream_success(leaks->cmd);
json_array_start(response, "leaks");
for (size_t num_leakers = 0;
num_leakers < tal_count(leaks->leakers);
@ -146,7 +116,7 @@ static void finish_report(const struct leak_detect *leaks)
json_array_end(response);
/* Command is now done. */
was_pending(command_success(cmd, response));
was_pending(command_success(leaks->cmd, response));
}
static void leak_detect_timeout(struct leak_detect *leak_detect)
@ -210,6 +180,34 @@ static void connect_dev_memleak_done(struct subd *connectd,
report_subd_memleak(leaks, connectd);
}
static bool lightningd_check_leaks(struct command *cmd)
{
struct lightningd *ld = cmd->ld;
struct htable *memtable;
/* Enter everything, except this cmd and its jcon */
memtable = memleak_start(cmd);
/* This command is not a leak! */
memleak_ptr(memtable, cmd);
memleak_ignore_children(memtable, cmd);
/* First delete known false positives. */
memleak_scan_htable(memtable, &ld->topology->txwatches->raw);
memleak_scan_htable(memtable, &ld->topology->txowatches->raw);
memleak_scan_htable(memtable, &ld->topology->outgoing_txs->raw);
memleak_scan_htable(memtable, &ld->htlcs_in->raw);
memleak_scan_htable(memtable, &ld->htlcs_out->raw);
memleak_scan_htable(memtable, &ld->htlc_sets->raw);
memleak_scan_htable(memtable, &ld->peers->raw);
memleak_scan_htable(memtable, &ld->peers_by_dbid->raw);
/* Now delete ld and those which it has pointers to. */
memleak_scan_obj(memtable, ld);
return dump_memleak(memtable, memleak_log, ld->log);
}
static struct command_result *json_memleak(struct command *cmd,
const char *buffer,
const jsmntok_t *obj UNNEEDED,
@ -236,6 +234,10 @@ static struct command_result *json_memleak(struct command *cmd,
leaks->num_outstanding_requests = 0;
leaks->leakers = tal_arr(leaks, const char *, 0);
/* Check for our own leaks. */
if (lightningd_check_leaks(cmd))
tal_arr_expand(&leaks->leakers, "lightningd");
/* hsmd is sync, so do that first. */
msg = hsm_sync_req(tmpctx, cmd->ld, take(towire_hsmd_dev_memleak(NULL)));
if (!fromwire_hsmd_dev_memleak_reply(msg, &found_leak))