mirror of
https://github.com/jmcorgan/fips.git
synced 2026-07-30 19:46:15 +00:00
Teach the trailing-log gate to see command-substitution call sites
Found while fixing count_log_pattern: the gate reported that function clean even though it ends in echo and both callers consume its status. Its call-site patterns only matched direct forms -- if fn, while fn, fn ||, fn && -- so a status consumed through command substitution was invisible, because the line begins with the variable rather than the function name. That is not an exotic form. It is how a shell function returns a value, and it is precisely the shape of the swallowed-failure family this gate exists to catch, so the gate was blind to a large part of its own stated class. Three patterns added for the assignment, if-guarded and test-expression forms. The extension finds four real instances, all value-returning helpers whose trailing echo made their exit status unconditionally zero, and each now carries an explicit return 0. Also corrects the finding message, which described the trailing command as a log call; for these it is the return mechanism, and the hazard is that it fixes the status either way. Validated by breaking what it guards: a probe reintroducing the defect shape behind a command substitution is reported and exits 1, where before the extension it would have passed.
This commit is contained in:
@@ -119,15 +119,39 @@ assertions:
|
|||||||
min_nodes_ce_received: 1
|
min_nodes_ce_received: 1
|
||||||
|
|
||||||
# The fourth criterion, "kernel_drop_events > 0 on at least one node", is
|
# The fourth criterion, "kernel_drop_events > 0 on at least one node", is
|
||||||
# NOT asserted, because the implementation does not meet it: across all 182
|
# NOT asserted, because this scenario has never met it: across all 182
|
||||||
# archived runs of this scenario, including the six that meet the other
|
# archived runs, including the six that meet the other three, no node has
|
||||||
# three, no node has ever reported a non-zero kernel_drop_events. Asserting
|
# reported a non-zero kernel_drop_events. Asserting it would red the scenario
|
||||||
# it would red the scenario permanently, and asserting a weakened version
|
# permanently, and asserting a weakened version would assert nothing.
|
||||||
# would assert nothing. It stands here as an unmet criterion: either the
|
#
|
||||||
# 4 KB recv_buf_size no longer provokes SO_RXQ_OVFL under this traffic, or
|
# CORRECTED 2026-07-23. An earlier version of this note concluded that the
|
||||||
# the counter does not reach the snapshot. Until that is settled it is a
|
# transport drop-detection path "is exercised by nothing". That is wrong, and
|
||||||
# coverage gap, because the transport drop-detection path this scenario
|
# the error was one of scope: it looked only inside this scenario. Widening
|
||||||
# names as signal 2 is exercised by nothing.
|
# the scan to every scenario's congestion snapshots -- 2317 files, 11778 node
|
||||||
|
# records -- finds 51 records reporting kernel_drop_events > 0 across six
|
||||||
|
# other scenarios (depth-vs-cost, bottleneck-parent, tcp-mesh, cost-avoidance,
|
||||||
|
# maelstrom-sparse, mixed-technology), with raw drop counts up to 41165. The
|
||||||
|
# path is live and plumbed through to the snapshot. What is dead is this
|
||||||
|
# scenario's ability to provoke it.
|
||||||
|
#
|
||||||
|
# The inversion is the lead: every scenario that DOES overflow runs with the
|
||||||
|
# default 2 MB recv_buf_size, while this one, the only scenario that shrinks
|
||||||
|
# it to 4 KB, never does. SO_RXQ_OVFL counts datagrams arriving at a full
|
||||||
|
# queue, and the 1 Mbps cap below sets the arrival rate to 125 kB/s per link.
|
||||||
|
# Linux doubles a requested SO_RCVBUF, so the queue is 8192 bytes and filling
|
||||||
|
# it takes ~65 ms of reader stall -- ~22 ms even at the busiest node's 3 Mbps
|
||||||
|
# aggregate. Traffic volume cannot cause the overflow because the volume is
|
||||||
|
# capped below the rate the buffer drains. The ingress policer works against
|
||||||
|
# the same signal, discarding excess in tc before the socket ever sees it.
|
||||||
|
#
|
||||||
|
# So signal 3 appears to suppress signal 2, and the two may not belong in one
|
||||||
|
# scenario. Tracked with the experiment that would settle it (raise or drop
|
||||||
|
# the bandwidth cap for one run and watch the counter) in ISSUE-2026-0084.
|
||||||
|
#
|
||||||
|
# Note also that the header above says "small recv_buf_size (8KB)" while
|
||||||
|
# fips_overrides sets 4096. The 8 KB figure matches what Linux allocates after
|
||||||
|
# doubling, so the comment may be describing the effective queue rather than
|
||||||
|
# the setting; it reads as the setting and should say which it means.
|
||||||
|
|
||||||
logging:
|
logging:
|
||||||
rust_log: "info"
|
rust_log: "info"
|
||||||
|
|||||||
@@ -133,6 +133,15 @@ def status_tested(name: str, all_text: str, defining_file: Path) -> list[str]:
|
|||||||
(rf"^\s*{re.escape(name)}\s+[^\n|&]*&&", "<fn> &&"),
|
(rf"^\s*{re.escape(name)}\s+[^\n|&]*&&", "<fn> &&"),
|
||||||
(rf"^\s*{re.escape(name)}\s*&&", "<fn> &&"),
|
(rf"^\s*{re.escape(name)}\s*&&", "<fn> &&"),
|
||||||
(rf"\bif\s+\S*\s*{re.escape(name)}\b.*;\s*then", "if ... <fn> ; then"),
|
(rf"\bif\s+\S*\s*{re.escape(name)}\b.*;\s*then", "if ... <fn> ; then"),
|
||||||
|
# Command substitution. Found 2026-07-23 while fixing a function this
|
||||||
|
# check reported clean: `total=$(count_log_pattern "$p") || { ... }`
|
||||||
|
# consumes the status, but the line begins with the variable, so none
|
||||||
|
# of the patterns above match it. That is not an exotic form — it is
|
||||||
|
# how a shell function returns a *value*, and it is the shape of the
|
||||||
|
# `|| true`-swallowed-failure family this check exists to catch.
|
||||||
|
(rf"=\$\(\s*{re.escape(name)}\b", "<var>=$(<fn>)"),
|
||||||
|
(rf"\bif\s+!?\s*\S*=?\$\(\s*{re.escape(name)}\b", "if <var>=$(<fn>)"),
|
||||||
|
(rf"\[\s+\"?\$\(\s*{re.escape(name)}\b", "[ $(<fn>) ]"),
|
||||||
]
|
]
|
||||||
hits = []
|
hits = []
|
||||||
for pat, label in patterns:
|
for pat, label in patterns:
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ ping_path() {
|
|||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
echo "${rtt//\// } ${loss:-N/A}"
|
echo "${rtt//\// } ${loss:-N/A}"
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
# ── Path definitions ───────────────────────────────────────────────────────
|
# ── Path definitions ───────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ docker_host_name() {
|
|||||||
local host
|
local host
|
||||||
host=$(get_node_attr "$topology_file" "$node_id" "docker_host")
|
host=$(get_node_attr "$topology_file" "$node_id" "docker_host")
|
||||||
echo "${host:-node-$node_id}"
|
echo "${host:-node-$node_id}"
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
# Get peers list from topology
|
# Get peers list from topology
|
||||||
@@ -118,6 +119,7 @@ get_default_transport() {
|
|||||||
local topology_file="$1"
|
local topology_file="$1"
|
||||||
local transport=$(grep "^default_transport:" "$topology_file" | head -1 | sed 's/.*: *\([a-z]*\).*/\1/')
|
local transport=$(grep "^default_transport:" "$topology_file" | head -1 | sed 's/.*: *\([a-z]*\).*/\1/')
|
||||||
echo "${transport:-udp}"
|
echo "${transport:-udp}"
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
# Get the port for a given transport type
|
# Get the port for a given transport type
|
||||||
|
|||||||
@@ -159,6 +159,7 @@ build_netem_params() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo "$params"
|
echo "$params"
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check if a container is running
|
# Check if a container is running
|
||||||
|
|||||||
Reference in New Issue
Block a user