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