[pfcwdorch]: Fix null-deref crash when non-port GLOBAL key is deleted#4760
Conversation
|
/azp run |
|
Azure Pipelines: There may be pipelines that require an authorized user to comment /azp run to run. |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
Signed-off-by: purush-nexthop <purush@nexthop.ai>
6791ee8 to
48d10a6
Compare
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
|
@vmittal-msft kindly review. |
|
Hi, there are workflow run(s) waiting for approval, you may be first-time contributor. I will notify maintainers to help approve once PR is approved. Thanks! ---Powered by SONiC BuildBot
|
|
/azpw retry |
|
Retrying failed(or canceled) jobs... |
|
Retrying failed(or canceled) stages in build 1167507: ✅Stage TestAsan:
✅Stage Test:
|
|
Hi @sonic-net/sonic-swss-maintainer, this approved PR has workflow run(s) waiting for approval. Please help review. Thanks! ---Powered by SONiC BuildBot
|
|
/azpw retry |
|
Retrying failed(or canceled) jobs... |
|
Retrying failed(or canceled) stages in build 1167507: ✅Stage TestAsan:
✅Stage Test:
|
|
Hi @sonic-net/sonic-swss-maintainer, this approved PR has workflow run(s) waiting for approval. Please help review. Thanks! ---Powered by SONiC BuildBot
|
1 similar comment
|
Hi @sonic-net/sonic-swss-maintainer, this approved PR has workflow run(s) waiting for approval. Please help review. Thanks! ---Powered by SONiC BuildBot
|
|
/azpw run |
|
Retrying failed(or canceled) jobs... |
|
Retrying failed(or canceled) stages in build 1167507: ✅Stage TestAsan:
✅Stage Test:
|
|
/azp run |
|
Azure Pipelines: There may be pipelines that require an authorized user to comment /azp run to run. |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
5fd99b6 to
0709583
Compare
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
test_pfcwd_global_hdel_last_field_no_crash exercised PFC_WD|GLOBAL delete/hdel paths but its post-conditions were contaminated by the BIG_RED_SWITCH field it used to build a single-field GLOBAL entry: - BIG_RED_SWITCH:enable puts pfcwdorch into big-red-switch mode, which force-drops monitored queues and bypasses normal storm detection. Deleting the field does not transition BRS off, so the queue stayed in force-drop mode and verify_pfcwd_state saw PFC_WD_STATUS=operational instead of stormed. Explicitly disable BIG_RED_SWITCH and reset GLOBAL to a clean POLL_INTERVAL before verifying storm detection. - The BRS enable/disable cycle also bumps the cumulative DEADLOCK_DETECTED/RESTORED counters, so verify_pfcwd_counters saw 2/2 instead of the expected 1/0. Reset the counters before the storm so the single storm yields detected=1, restored=0. Signed-off-by: Pinky Agrawal <pinky@nexthop.ai>
0709583 to
49efa30
Compare
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
|
/azpw retry |
|
Retrying failed(or canceled) jobs... |
|
Retrying failed(or canceled) stages in build 1171644: ✅Stage BuildAsan:
|
vmittal-msft
left a comment
There was a problem hiding this comment.
Reviewed the fix and tests. Root cause is confirmed and the fix is correct, minimal, and safe — recommending approval, with two minor/optional suggestions inline.
Root cause (verified): deleteEntry() ignored getPort()'s return, leaving a default-constructed Port whose m_queue_ids is an empty vector. stopWdOnPort → unregisterFromWdDb then indexes port.m_queue_ids[i] for i in 0..PFC_WD_TC_MAX(8), dereferencing a null vector backing pointer → SIGSEGV at address 0. Worth calling out the asymmetry that made this reachable: createEntry is overridden in PfcWdSwOrch to special-case GLOBAL, but deleteEntry is not — so DEL("GLOBAL") falls through to the port-only path.
Fix: Correct. task_invalid_entry is handled by doTask (entry erased, no retry loop), and it mirrors the guard/message already used in PfcWdSwOrch::doTask's APPL_DB path.
Tests: Both are well-constructed, clearly commented, and vendor-agnostic. test_pfcwd_global_key_del_no_crash covers the direct deleteEntry("GLOBAL") path; test_pfcwd_global_hdel_last_field_no_crash reproduces the real-world last-field hdel → Redis auto-del trigger (valid, since the CONFIG_DB consumer is a keyspace-driven SubscriberStateTable and DVS runs with notify-keyspace-events AKE). Test vstest and TestAsan vstest both pass, so the null-deref would be caught under ASan.
Address review: GLOBAL is a valid config key, not a port, so deleting PFC_WD|GLOBAL should not be logged as an "Invalid port interface" error. Short-circuit the GLOBAL key to task_success before the port lookup, mirroring the existing createEntry override. Genuinely invalid port keys still return task_invalid_entry with the error. Update the test docstring to match. Signed-off-by: Pinky Agrawal <pinky@nexthop.ai>
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
|
/azpw retry |
|
Retrying failed(or canceled) jobs... |
|
Retrying failed(or canceled) stages in build 1172589: ✅Stage BuildAsan:
|
Add test_pfcwd_del_unknown_port_no_crash: deleting a PFC_WD entry for a non-existent port drives the getPort() failure path (task_invalid_entry), which the GLOBAL short-circuit no longer reaches. Restores diff coverage on deleteEntry to 100%. Signed-off-by: Pinky Agrawal <pinky@nexthop.ai>
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
vmittal-msft
left a comment
There was a problem hiding this comment.
Re-reviewed after the latest changes (aba9491). Both of my earlier comments are addressed — approving.
1. Misleading "Invalid port interface" ERROR on routine GLOBAL removal (my minor/optional suggestion): Resolved exactly as suggested. deleteEntry now short-circuits GLOBAL before the port lookup, mirroring the existing key == PFC_WD_GLOBAL special-case in createEntry:
if (name == PFC_WD_GLOBAL)
{
return task_process_status::task_success;
}The SWSS_LOG_ERROR("Invalid port interface ...") now only fires for genuinely invalid ports, and the added comment clearly explains the rationale.
2. GLOBAL-delete no-op behavioral note: Confirmed intended, with the state-teardown behavior tracked as follow-up in #4785.
Bonus: The new test_pfcwd_del_unknown_port_no_crash nicely covers the invalid-port guard — verified it's meaningful, since base createEntry also rejects Ethernet9999 with task_invalid_entry and deleteEntry's guard handles the delete, so orchagent survives both paths.
CI: All checks green, including Test vstest and TestAsan vstest — the ASan run would catch this null-deref, so the fix is verified clean.
The fix remains correct, minimal, consistent with createEntry, and vendor-agnostic. LGTM. 👍
|
@dgsudharsan Could you please merge this. |
Why I did it
Symptoms
An orchagent core (
SIGSEGV, segfault at address 0) was observed after the following sequence of config events:BIG_RED_SWITCHfromPFC_WD|GLOBALDEVICE_NEIGHBORconfig for all portsconfig qos reloadBased on the timestamps, the segfault happened during or immediately after
config qos reload.Root cause
PfcWdOrch::deleteEntryignores the return value ofgPortsOrch->getPort(). When a non-port key likeGLOBALis passed (e.g. when the last field is removed fromPFC_WD|GLOBAL, causing Redis to emit aDEL("GLOBAL")event),portis left default-constructed andm_queue_idsis an empty vector.stopWdOnPort→unregisterFromWdDbthen dereferencesport.m_queue_ids[i]on a null backing pointer, causing a segfault at address 0.Reproduced by:
when
BIG_RED_SWITCHis the only field in that key.How I did it
Check the return value of
gPortsOrch->getPort()indeleteEntryand return early if the port is not present (as is the case forPFC_WD|GLOBAL).How to verify it
orchagent no longer crashes. A unit test covering this case is added in
tests/test_pfcwd.py.