xref: /linux/tools/testing/selftests/mm/ksft_kmemleak_dedup.sh (revision 3d5e48944e824bddc20d7b874e784f7b279636fe)
1*cfaef29cSBreno Leitao#!/bin/bash
2*cfaef29cSBreno Leitao# SPDX-License-Identifier: GPL-2.0
3*cfaef29cSBreno Leitao#
4*cfaef29cSBreno Leitao# Regression test for kmemleak's per-scan verbose dedup.
5*cfaef29cSBreno Leitao#
6*cfaef29cSBreno Leitao# Loads samples/kmemleak's helper module to generate orphan allocations
7*cfaef29cSBreno Leitao# (some of which share an allocation backtrace), runs a few kmemleak
8*cfaef29cSBreno Leitao# scans with verbose printing enabled, and verifies that no two
9*cfaef29cSBreno Leitao# "unreferenced object" reports within a single scan share the same
10*cfaef29cSBreno Leitao# backtrace - which would mean dedup failed to collapse them.
11*cfaef29cSBreno Leitao#
12*cfaef29cSBreno Leitao# This test is intentionally permissive: the kmemleak-test module's
13*cfaef29cSBreno Leitao# leaks frequently get reported across many separate scans (per-CPU
14*cfaef29cSBreno Leitao# chunk reuse, slab freelist pointers, kernel stack residue), so dedup
15*cfaef29cSBreno Leitao# may never have anything to fold within one scan. That is not a
16*cfaef29cSBreno Leitao# regression. The test only fails when it actually catches dedup not
17*cfaef29cSBreno Leitao# happening on input that should have triggered it - i.e. two reports
18*cfaef29cSBreno Leitao# with identical backtraces in the same scan.
19*cfaef29cSBreno Leitao#
20*cfaef29cSBreno Leitao# Author: Breno Leitao <leitao@debian.org>
21*cfaef29cSBreno Leitao
22*cfaef29cSBreno Leitaoksft_skip=4
23*cfaef29cSBreno LeitaoKMEMLEAK=/sys/kernel/debug/kmemleak
24*cfaef29cSBreno LeitaoVERBOSE_PARAM=/sys/module/kmemleak/parameters/verbose
25*cfaef29cSBreno LeitaoMODULE=kmemleak-test
26*cfaef29cSBreno Leitao
27*cfaef29cSBreno Leitaoskip() {
28*cfaef29cSBreno Leitao	echo "SKIP: $*"
29*cfaef29cSBreno Leitao	exit $ksft_skip
30*cfaef29cSBreno Leitao}
31*cfaef29cSBreno Leitao
32*cfaef29cSBreno Leitaofail() {
33*cfaef29cSBreno Leitao	echo "FAIL: $*"
34*cfaef29cSBreno Leitao	exit 1
35*cfaef29cSBreno Leitao}
36*cfaef29cSBreno Leitao
37*cfaef29cSBreno Leitaopass() {
38*cfaef29cSBreno Leitao	echo "PASS: $*"
39*cfaef29cSBreno Leitao	exit 0
40*cfaef29cSBreno Leitao}
41*cfaef29cSBreno Leitao
42*cfaef29cSBreno Leitao[ "$(id -u)" -eq 0 ] || skip "must run as root"
43*cfaef29cSBreno Leitao[ -r "$KMEMLEAK" ] || skip "no kmemleak debugfs (CONFIG_DEBUG_KMEMLEAK)"
44*cfaef29cSBreno Leitao[ -w "$VERBOSE_PARAM" ] || skip "kmemleak verbose param missing"
45*cfaef29cSBreno Leitaomodinfo "$MODULE" >/dev/null 2>&1 ||
46*cfaef29cSBreno Leitao	skip "$MODULE not built (CONFIG_SAMPLE_KMEMLEAK)"
47*cfaef29cSBreno Leitao
48*cfaef29cSBreno Leitao# The verdict depends entirely on dmesg contents, so a silently-empty
49*cfaef29cSBreno Leitao# dmesg (dmesg_restrict=1 with CAP_SYSLOG dropped, restricted container,
50*cfaef29cSBreno Leitao# etc.) would let the script report PASS without parsing anything. Probe
51*cfaef29cSBreno Leitao# both read and clear up front and skip cleanly if either is denied.
52*cfaef29cSBreno Leitaodmesg >/dev/null 2>&1 ||
53*cfaef29cSBreno Leitao	skip "cannot read dmesg (need CAP_SYSLOG or dmesg_restrict=0)"
54*cfaef29cSBreno Leitaodmesg -C >/dev/null 2>&1 ||
55*cfaef29cSBreno Leitao	skip "cannot clear dmesg (need CAP_SYSLOG or dmesg_restrict=0)"
56*cfaef29cSBreno Leitao
57*cfaef29cSBreno Leitao# kmemleak can be present but disabled at runtime (boot arg kmemleak=off,
58*cfaef29cSBreno Leitao# or it self-disabled after an internal error). In that state writes other
59*cfaef29cSBreno Leitao# than "clear" return EPERM, so probe once and skip if so.
60*cfaef29cSBreno Leitaoif ! echo scan > "$KMEMLEAK" 2>/dev/null; then
61*cfaef29cSBreno Leitao	skip "kmemleak is disabled (check dmesg or kmemleak= boot arg)"
62*cfaef29cSBreno Leitaofi
63*cfaef29cSBreno Leitao
64*cfaef29cSBreno Leitaoprev_verbose=$(cat "$VERBOSE_PARAM")
65*cfaef29cSBreno Leitao# shellcheck disable=SC2317  # invoked indirectly via trap
66*cfaef29cSBreno Leitaocleanup() {
67*cfaef29cSBreno Leitao	echo "$prev_verbose" > "$VERBOSE_PARAM" 2>/dev/null
68*cfaef29cSBreno Leitao	rmmod "$MODULE" 2>/dev/null
69*cfaef29cSBreno Leitao	# Drain the leak set we generated. Subsequent selftests (e.g.
70*cfaef29cSBreno Leitao	# tools/testing/selftests/net/netfilter/nft_interface_stress.sh)
71*cfaef29cSBreno Leitao	# fail on any non-empty kmemleak report, so leaving the helper
72*cfaef29cSBreno Leitao	# module's intentional leaks behind would poison the rest of a
73*cfaef29cSBreno Leitao	# kselftest run.
74*cfaef29cSBreno Leitao	#
75*cfaef29cSBreno Leitao	# Caveat: kmemleak_clear() only greys objects that have already
76*cfaef29cSBreno Leitao	# been reported (OBJECT_REPORTED && unreferenced_object()). Helper
77*cfaef29cSBreno Leitao	# allocations that stayed "still referenced" throughout the test
78*cfaef29cSBreno Leitao	# (stale pointers in per-CPU chunks, slab freelists, kernel stacks)
79*cfaef29cSBreno Leitao	# were never reported and are therefore not greyed by this clear -
80*cfaef29cSBreno Leitao	# they remain tracked and a later scan can still surface them. Such
81*cfaef29cSBreno Leitao	# leftovers are inherent to the kmemleak-test sample module and are
82*cfaef29cSBreno Leitao	# not specific to this test; consumers that fail on any kmemleak
83*cfaef29cSBreno Leitao	# output (rather than on the test-specific backtraces) need to be
84*cfaef29cSBreno Leitao	# robust to that, or this test should be excluded from the run.
85*cfaef29cSBreno Leitao	echo clear > "$KMEMLEAK" 2>/dev/null
86*cfaef29cSBreno Leitao}
87*cfaef29cSBreno Leitaotrap cleanup EXIT
88*cfaef29cSBreno Leitao
89*cfaef29cSBreno Leitaoecho 1 > "$VERBOSE_PARAM"
90*cfaef29cSBreno Leitao
91*cfaef29cSBreno Leitao# Drain the existing leak set so the next scan only reports our objects.
92*cfaef29cSBreno Leitaoecho clear > "$KMEMLEAK"
93*cfaef29cSBreno Leitao
94*cfaef29cSBreno Leitao# Re-clear dmesg now (the up-front probe also cleared it, but anything
95*cfaef29cSBreno Leitao# logged between then and here - module unload chatter, the probe scan,
96*cfaef29cSBreno Leitao# the verbose-param write - would otherwise pollute the parse window).
97*cfaef29cSBreno Leitaodmesg -C >/dev/null
98*cfaef29cSBreno Leitao
99*cfaef29cSBreno Leitao# If the module was left loaded by a previous aborted run, modprobe would
100*cfaef29cSBreno Leitao# be a no-op and the init function would not run, so no new leaks would be
101*cfaef29cSBreno Leitao# generated. Force a clean state first.
102*cfaef29cSBreno Leitaormmod "$MODULE" 2>/dev/null
103*cfaef29cSBreno Leitaomodprobe "$MODULE" || skip "failed to load $MODULE"
104*cfaef29cSBreno Leitao# Removing the module orphans the list elements without freeing them.
105*cfaef29cSBreno Leitaormmod "$MODULE"    || skip "failed to unload $MODULE"
106*cfaef29cSBreno Leitao
107*cfaef29cSBreno Leitao# Run a handful of scans so kmemleak has the chance to age and report
108*cfaef29cSBreno Leitao# the orphans. We do not require any particular number to be reported:
109*cfaef29cSBreno Leitao# the regression check below operates on whatever lands in dmesg.
110*cfaef29cSBreno Leitao#
111*cfaef29cSBreno Leitao# Note: with CONFIG_DEBUG_KMEMLEAK_AUTO_SCAN=y the kernel's own scan
112*cfaef29cSBreno Leitao# thread can report and mark these orphans (OBJECT_REPORTED) before our
113*cfaef29cSBreno Leitao# manual scans run, after which our scans will see nothing. The
114*cfaef29cSBreno Leitao# lower-bound check below catches the case where that happens and the
115*cfaef29cSBreno Leitao# manual scans also produce nothing.
116*cfaef29cSBreno LeitaoSCAN_COUNT=4
117*cfaef29cSBreno LeitaoSCAN_SLEEP=6
118*cfaef29cSBreno Leitaofor _ in $(seq 1 "$SCAN_COUNT"); do
119*cfaef29cSBreno Leitao	echo scan > "$KMEMLEAK"
120*cfaef29cSBreno Leitao	sleep "$SCAN_SLEEP"
121*cfaef29cSBreno Leitaodone
122*cfaef29cSBreno Leitao
123*cfaef29cSBreno Leitao# Strip the leading "[   nnn.nnnnnn] " dmesg timestamp prefix. Without
124*cfaef29cSBreno Leitao# this, two identical stack frames printed from two reports in the same
125*cfaef29cSBreno Leitao# scan would produce different per-frame strings (different timestamps)
126*cfaef29cSBreno Leitao# and the duplicate-backtrace check below would not match them, silently
127*cfaef29cSBreno Leitao# passing a real dedup regression. Doing the strip here makes the rest
128*cfaef29cSBreno Leitao# of the parser timestamp-agnostic regardless of what dmesg defaults to.
129*cfaef29cSBreno Leitaolog=$(dmesg | sed 's/^\[[^]]*\] //')
130*cfaef29cSBreno Leitao
131*cfaef29cSBreno Leitao# After running the workload (modprobe + scans), dmesg should contain at
132*cfaef29cSBreno Leitao# least the helper module's pr_info lines and our manual-scan output. An
133*cfaef29cSBreno Leitao# empty capture here means dmesg succeeded earlier but is now denying us
134*cfaef29cSBreno Leitao# the buffer (race with dmesg_restrict toggling, etc.); refuse to give a
135*cfaef29cSBreno Leitao# verdict on no evidence.
136*cfaef29cSBreno Leitao[ -n "$log" ] || skip "dmesg returned empty after running workload"
137*cfaef29cSBreno Leitao
138*cfaef29cSBreno Leitao# Lower bound: if kmemleak's own per-scan tally counted leaks but the
139*cfaef29cSBreno Leitao# verbose path emitted no "unreferenced object" line, the verbose printer
140*cfaef29cSBreno Leitao# itself is regressed - fail rather than silently passing on no input.
141*cfaef29cSBreno Leitaonew_leaks=$(echo "$log" |
142*cfaef29cSBreno Leitao	sed -n 's/.*kmemleak: \([0-9]\+\) new suspected.*/\1/p' |
143*cfaef29cSBreno Leitao	awk '{s+=$1} END{print s+0}')
144*cfaef29cSBreno Leitaoprinted=$(echo "$log" | grep -c 'kmemleak: unreferenced object')
145*cfaef29cSBreno Leitaoif [ "$new_leaks" -gt 0 ] && [ "$printed" -eq 0 ]; then
146*cfaef29cSBreno Leitao	fail "verbose path broken: $new_leaks leaks counted, 0 printed in $SCAN_COUNT scans"
147*cfaef29cSBreno Leitaofi
148*cfaef29cSBreno Leitao
149*cfaef29cSBreno Leitao# Walk the log: split into per-scan chunks at "N new suspected memory
150*cfaef29cSBreno Leitao# leaks" boundaries; within each chunk, capture each "unreferenced
151*cfaef29cSBreno Leitao# object" report's backtrace and check that no backtrace is reported
152*cfaef29cSBreno Leitao# more than once. A duplicate within a single scan means dedup failed
153*cfaef29cSBreno Leitao# to collapse two leaks that share an allocation site.
154*cfaef29cSBreno Leitaoviolations=$(echo "$log" | awk '
155*cfaef29cSBreno Leitao	function flush_block() {
156*cfaef29cSBreno Leitao		if (in_block) {
157*cfaef29cSBreno Leitao			# Skip empty backtraces: leaks with trace_handle == 0
158*cfaef29cSBreno Leitao			# (early-boot allocations or stack_depot_save() failures
159*cfaef29cSBreno Leitao			# under memory pressure) are intentionally not deduped,
160*cfaef29cSBreno Leitao			# so multiple such reports in one scan are expected and
161*cfaef29cSBreno Leitao			# must not be flagged as a regression.
162*cfaef29cSBreno Leitao			if (bt != "")
163*cfaef29cSBreno Leitao				seen[bt]++
164*cfaef29cSBreno Leitao			in_block = 0
165*cfaef29cSBreno Leitao			collecting = 0
166*cfaef29cSBreno Leitao			bt = ""
167*cfaef29cSBreno Leitao		}
168*cfaef29cSBreno Leitao	}
169*cfaef29cSBreno Leitao	function check_and_reset(   b) {
170*cfaef29cSBreno Leitao		for (b in seen)
171*cfaef29cSBreno Leitao			if (seen[b] > 1)
172*cfaef29cSBreno Leitao				printf("backtrace seen %d times in one scan:\n%s\n",
173*cfaef29cSBreno Leitao				       seen[b], b)
174*cfaef29cSBreno Leitao		delete seen
175*cfaef29cSBreno Leitao	}
176*cfaef29cSBreno Leitao	# Scan boundary: the per-scan summary line.
177*cfaef29cSBreno Leitao	/kmemleak: [0-9]+ new suspected memory leaks/ {
178*cfaef29cSBreno Leitao		flush_block()
179*cfaef29cSBreno Leitao		check_and_reset()
180*cfaef29cSBreno Leitao		next
181*cfaef29cSBreno Leitao	}
182*cfaef29cSBreno Leitao	# Start of a new "unreferenced object" report.
183*cfaef29cSBreno Leitao	/kmemleak: unreferenced object/ {
184*cfaef29cSBreno Leitao		flush_block()
185*cfaef29cSBreno Leitao		in_block = 1
186*cfaef29cSBreno Leitao		next
187*cfaef29cSBreno Leitao	}
188*cfaef29cSBreno Leitao	# Inside a report, the "backtrace (crc ...):" line switches us to
189*cfaef29cSBreno Leitao	# backtrace-collecting mode.
190*cfaef29cSBreno Leitao	in_block && /kmemleak:[[:space:]]+backtrace \(crc/ {
191*cfaef29cSBreno Leitao		collecting = 1
192*cfaef29cSBreno Leitao		next
193*cfaef29cSBreno Leitao	}
194*cfaef29cSBreno Leitao	# Once collecting, capture only deeply-indented "kmemleak: " lines
195*cfaef29cSBreno Leitao	# (stack frames have 4+ spaces of indentation under "kmemleak: ";
196*cfaef29cSBreno Leitao	# headers and the "... and N more" tail line have less). This stops
197*cfaef29cSBreno Leitao	# unrelated kmemleak warns landing between reports from being lumped
198*cfaef29cSBreno Leitao	# into the backtrace key, which would mask a genuine duplicate.
199*cfaef29cSBreno Leitao	in_block && collecting && /kmemleak:[[:space:]]{4,}/ {
200*cfaef29cSBreno Leitao		bt = bt $0 "\n"
201*cfaef29cSBreno Leitao		next
202*cfaef29cSBreno Leitao	}
203*cfaef29cSBreno Leitao	END {
204*cfaef29cSBreno Leitao		flush_block()
205*cfaef29cSBreno Leitao		check_and_reset()
206*cfaef29cSBreno Leitao	}
207*cfaef29cSBreno Leitao')
208*cfaef29cSBreno Leitao
209*cfaef29cSBreno Leitaoif [ -n "$violations" ]; then
210*cfaef29cSBreno Leitao	echo "$violations"
211*cfaef29cSBreno Leitao	fail "kmemleak dedup regression: same backtrace reported more than once in a single scan"
212*cfaef29cSBreno Leitaofi
213*cfaef29cSBreno Leitao
214*cfaef29cSBreno Leitao# Count the dedup summary lines so the report distinguishes "dedup
215*cfaef29cSBreno Leitao# actually fired" from "no same-backtrace leaks turned up to dedup".
216*cfaef29cSBreno Leitaodedup_lines=$(echo "$log" | grep -c 'more object(s) with the same backtrace')
217*cfaef29cSBreno Leitao
218*cfaef29cSBreno Leitaoif [ "$dedup_lines" -gt 0 ]; then
219*cfaef29cSBreno Leitao	pass "no dedup violations across $SCAN_COUNT scans; dedup fired ($dedup_lines summary line(s) observed)"
220*cfaef29cSBreno Leitaoelse
221*cfaef29cSBreno Leitao	pass "no dedup violations across $SCAN_COUNT scans; dedup had nothing to collapse"
222*cfaef29cSBreno Leitaofi
223