Skip to content

[pfcwdorch]: Fix null-deref crash when non-port GLOBAL key is deleted#4760

Merged
dgsudharsan merged 4 commits into
sonic-net:masterfrom
nexthop-ai:fix-null-deref-crash-when-global-key-is
Jul 23, 2026
Merged

[pfcwdorch]: Fix null-deref crash when non-port GLOBAL key is deleted#4760
dgsudharsan merged 4 commits into
sonic-net:masterfrom
nexthop-ai:fix-null-deref-crash-when-global-key-is

Conversation

@pinky-nexthop

Copy link
Copy Markdown
Contributor

Why I did it

Symptoms

An orchagent core (SIGSEGV, segfault at address 0) was observed after the following sequence of config events:

  • Deleted BIG_RED_SWITCH from PFC_WD|GLOBAL
  • Added DEVICE_NEIGHBOR config for all ports
  • Ran config qos reload

Based on the timestamps, the segfault happened during or immediately after config qos reload.

Root cause

PfcWdOrch::deleteEntry ignores the return value of gPortsOrch->getPort(). When a non-port key like GLOBAL is passed (e.g. when the last field is removed from PFC_WD|GLOBAL, causing Redis to emit a DEL("GLOBAL") event), port is left default-constructed and m_queue_ids is an empty vector. stopWdOnPortunregisterFromWdDb then dereferences port.m_queue_ids[i] on a null backing pointer, causing a segfault at address 0.

Reproduced by:

redis-cli -n 4 HDEL "PFC_WD|GLOBAL" BIG_RED_SWITCH

when BIG_RED_SWITCH is the only field in that key.

How I did it

Check the return value of gPortsOrch->getPort() in deleteEntry and return early if the port is not present (as is the case for PFC_WD|GLOBAL).

How to verify it

sudo pfcwd big_red_switch enable
redis-cli -n 4 hdel 'PFC_WD|GLOBAL' 'BIG_RED_SWITCH'

orchagent no longer crashes. A unit test covering this case is added in tests/test_pfcwd.py.

@mssonicbld

Copy link
Copy Markdown
Collaborator

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

Signed-off-by: purush-nexthop <purush@nexthop.ai>
@pinky-nexthop
pinky-nexthop force-pushed the fix-null-deref-crash-when-global-key-is branch from 6791ee8 to 48d10a6 Compare July 17, 2026 05:58
@mssonicbld

Copy link
Copy Markdown
Collaborator

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

@pinky-nexthop

Copy link
Copy Markdown
Contributor Author

@vmittal-msft kindly review.

@mssonicbld

Copy link
Copy Markdown
Collaborator

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

@pinky-nexthop

Copy link
Copy Markdown
Contributor Author

/azpw retry

@mssonicbld

Copy link
Copy Markdown
Collaborator

Retrying failed(or canceled) jobs...

@mssonicbld

Copy link
Copy Markdown
Collaborator

Retrying failed(or canceled) stages in build 1167507:

✅Stage TestAsan:

  • Job vstest: retried.

✅Stage Test:

  • Job vstest: retried.
  • Job vstest: retried.

kperumalbfn
kperumalbfn previously approved these changes Jul 17, 2026
@mssonicbld

Copy link
Copy Markdown
Collaborator

Hi @sonic-net/sonic-swss-maintainer, this approved PR has workflow run(s) waiting for approval. Please help review. Thanks!

---Powered by SONiC BuildBot

@pinky-nexthop

Copy link
Copy Markdown
Contributor Author

/azpw retry

@mssonicbld

Copy link
Copy Markdown
Collaborator

Retrying failed(or canceled) jobs...

@mssonicbld

Copy link
Copy Markdown
Collaborator

Retrying failed(or canceled) stages in build 1167507:

✅Stage TestAsan:

  • Job vstest: retried.

✅Stage Test:

  • Job vstest: retried.
  • Job vstest: retried.

@mssonicbld

Copy link
Copy Markdown
Collaborator

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
@mssonicbld

Copy link
Copy Markdown
Collaborator

Hi @sonic-net/sonic-swss-maintainer, this approved PR has workflow run(s) waiting for approval. Please help review. Thanks!

---Powered by SONiC BuildBot

@pinky-nexthop

Copy link
Copy Markdown
Contributor Author

/azpw run

@mssonicbld

Copy link
Copy Markdown
Collaborator

⚠️ Notice: /azpw run only runs failed jobs now. If you want to trigger a whole pipline run, please rebase your branch or close and reopen the PR.
💡 Tip: You can also use /azpw retry to retry failed jobs directly.

Retrying failed(or canceled) jobs...

@mssonicbld

Copy link
Copy Markdown
Collaborator

Retrying failed(or canceled) stages in build 1167507:

✅Stage TestAsan:

  • Job vstest: retried.

✅Stage Test:

  • Job vstest: retried.
  • Job vstest: retried.

@mssonicbld

Copy link
Copy Markdown
Collaborator

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

@pinky-nexthop
pinky-nexthop force-pushed the fix-null-deref-crash-when-global-key-is branch from 5fd99b6 to 0709583 Compare July 22, 2026 02:56
@mssonicbld

Copy link
Copy Markdown
Collaborator

/azp run

@azure-pipelines

Copy link
Copy Markdown
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>
@pinky-nexthop
pinky-nexthop force-pushed the fix-null-deref-crash-when-global-key-is branch from 0709583 to 49efa30 Compare July 22, 2026 06:47
@mssonicbld

Copy link
Copy Markdown
Collaborator

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

@pinky-nexthop

Copy link
Copy Markdown
Contributor Author

/azpw retry

@mssonicbld

Copy link
Copy Markdown
Collaborator

Retrying failed(or canceled) jobs...

@mssonicbld

Copy link
Copy Markdown
Collaborator

Retrying failed(or canceled) stages in build 1171644:

✅Stage BuildAsan:

  • Job amd64: retried.

@vmittal-msft
vmittal-msft self-requested a review July 22, 2026 16:58

@vmittal-msft vmittal-msft left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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. stopWdOnPortunregisterFromWdDb 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.

Comment thread orchagent/pfcwdorch.cpp
Comment thread orchagent/pfcwdorch.cpp
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>
@mssonicbld

Copy link
Copy Markdown
Collaborator

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

@pinky-nexthop

Copy link
Copy Markdown
Contributor Author

/azpw retry

@mssonicbld

Copy link
Copy Markdown
Collaborator

Retrying failed(or canceled) jobs...

@mssonicbld

Copy link
Copy Markdown
Collaborator

Retrying failed(or canceled) stages in build 1172589:

✅Stage BuildAsan:

  • Job amd64: retried.

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>
@mssonicbld

Copy link
Copy Markdown
Collaborator

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

@vmittal-msft vmittal-msft left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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. 👍

@kperumalbfn

Copy link
Copy Markdown
Contributor

@dgsudharsan Could you please merge this.

@dgsudharsan
dgsudharsan merged commit a709754 into sonic-net:master Jul 23, 2026
19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants