Skip to content

KRN-1117: Add path-compressed trie storage to stackdepot - #2

Draft
caleb-kan wants to merge 130 commits into
linux-6.18.yfrom
caleb/KRN-1117-stackdepot-port
Draft

KRN-1117: Add path-compressed trie storage to stackdepot#2
caleb-kan wants to merge 130 commits into
linux-6.18.yfrom
caleb/KRN-1117-stackdepot-port

Conversation

@caleb-kan

@caleb-kan caleb-kan commented Jun 18, 2026

Copy link
Copy Markdown
Owner

Summary

This ports the KRN-1117 stackdepot redesign to Linux 6.18.

DOG KASAN hosts can exhaust stackdepot pools because stackdepot currently deduplicates only byte-identical full stacks. The RFC and userspace prototype showed that these stacks still share many frame sequences, even when the whole stack is unique.

This adds an opt-in path-compressed trie backend for persistent stack saves. The trie stores shared frame runs once and compresses kernel text frames when the arch helper can reconstruct them safely.

stackdepot.trie_enabled defaults off. The next step is DOG one-off testing and validation.

Design Notes

The legacy hash backend is still used for:

  • STACK_DEPOT_FLAG_GET
  • explicit hash saves
  • overlong stacks
  • page_owner count-helper paths

The trie path is used only for persistent, non-refcounted stack saves.

This keeps refcounted eviction out of scope and preserves existing hash semantics for the callers that still need them.

Review Focus

The parts I would most like reviewed are:

  • trie save/insert routing
  • RCU/COW publication
  • retired trie object reuse
  • side-table handle lifetime
  • KMSAN extra-bit handling
  • no-spin / constrained-context behavior
  • x86-64 and arm64 frame compression
  • caller migration to stack_depot_fetch_into()

Validation

Based on linux-6.18.y at v6.18.35.

Passed:

  • git diff --check greg/linux-6.18.y...HEAD
  • strict checkpatch on the effective diff
  • focused full-file checkpatch for stackdepot files and mm/page_owner.c
  • focused arm64 sparse build for lib/stackdepot.o and lib/tests/stackdepot_kunit.o
  • stackdepot KUnit: 136/136
  • lockdep / PROVE_RCU / raw-lock KUnit: 136/136
  • KCSAN KUnit: 136/136
  • runtime stackdepot.trie_enabled enable/disable smoke
  • trie-enabled Generic KASAN boot smoke
  • sampled drgn trie materialization and frame-compression checks

Rollout

This is still experimental. DOG needs to answer:

  • whether the memory savings hold on real Generic KASAN workload
  • whether stackdepot pool exhaustion is avoided
  • whether KASAN reports still have usable allocation/free stacks
  • whether stack_depot_save() cost is acceptable

Caleb Kan added 30 commits June 1, 2026 21:14
Add stack_depot_fetch_into() so callers can copy a saved stack trace into
caller-owned storage instead of receiving a pointer to stackdepot-owned
memory.

This is a preparatory API for trie-backed stack storage, where a saved
stack may be spread across multiple trie nodes and must be materialized
before use.

Add KUnit coverage for successful copy-out and invalid or too-small
buffer handling.

Signed-off-by: Caleb Kan <ckan@cloudflare.com>
Cover the stackdepot public header and KUnit test under LIBRARY CODE
so get_maintainer.pl reports the library maintainer for the new test
file and checkpatch no longer warns that MAINTAINERS may need
updating.

Signed-off-by: Caleb Kan <ckan@cloudflare.com>
Document the caller-owned copy and handle lifetime contract for
stack_depot_fetch_into() so the helper is explicit about how
trie-backed materialization will be consumed. Cover the
exact-fit, oversized-buffer, and zero-sized-buffer cases in
KUnit to lock the all-or-nothing copy semantics.

Keep the stackdepot KUnit entry ordered with nearby tests and
broaden MAINTAINERS coverage so future stackdepot tests are
routed to the library maintainer.

Signed-off-by: Caleb Kan <ckan@cloudflare.com>
Use stack_depot_fetch_into() when rendering page_owner_stacks so the
diagnostic path no longer depends on stackdepot's flat entries[]
storage. Store the materialized stack in seq_file private data to
avoid a large stack buffer and keep output formatting unchanged.

Clarify the fetch_into() oversized-buffer documentation while adding
the first page_owner caller that relies on caller-owned stack
materialization.

Signed-off-by: Caleb Kan <ckan@cloudflare.com>
Store page_owner stack handles in the show_stacks list instead of
stackdepot internal stack_record pointers. This keeps the debugfs
listing path from depending on stackdepot's flat record storage while
preserving the existing page count accounting for a later helper
conversion.

Materialize stack frames into seq_file private storage with
stack_depot_fetch_into() when printing page_owner_stacks so the output
format stays unchanged and no large stack-local buffer is needed.

Signed-off-by: Caleb Kan <ckan@cloudflare.com>
Add internal stackdepot count helpers and use them from page_owner so
page_owner no longer reads or mutates struct stack_record directly.
This keeps page_owner behind handle-based stackdepot access before the
persistent stack storage layout changes for trie-backed records.

Keep the stackdepot KUnit test built-in-only because it exercises
internal helpers, and add coverage for the saturated-to-counted
transition and count boundary cases.

Signed-off-by: Caleb Kan <ckan@cloudflare.com>
Move the flat stackdepot record and handle bitfield layout out of the
public stackdepot header and into lib/stackdepot.c. Runtime callers now
use handle-based helpers, so the flat storage details no longer need to
be exposed before the persistent storage backend changes.

Keep the page_owner count helpers internal to stackdepot, document their
counted-mode semantics, and extend KUnit coverage for saturated,
counted, and invalid-count cases.

Signed-off-by: Caleb Kan <ckan@cloudflare.com>
Add generic architecture hooks for stackdepot frame compression so future
x86-64 and arm64 implementations can opt in without changing common
stackdepot callers. The generic asm fallback keeps today's behavior by
declining compression and storing raw frames.

Keep stackdepot_fetch_into() safe across the copy by holding the same
notrace RCU read-side section used by stackdepot lookups, and cover the
raw frame fallback plus count-helper edge cases in KUnit.

Signed-off-by: Caleb Kan <ckan@cloudflare.com>
Keep the stackdepot count decrement helper from silently discarding
attempts to subtract more than the current counted value. Preserve
the old refcount diagnostic behavior by warning before returning
false on an underflow attempt.

Signed-off-by: Caleb Kan <ckan@cloudflare.com>
Add the x86-64 stackdepot frame compression hook for frames whose high
32 bits match the kernel-text prefix. This keeps non-x86-64 builds on
the generic raw fallback while allowing the common code to use the
architecture hook added earlier.

Extend stackdepot KUnit coverage for the x86-64 round trip and for
non-compressible direct-map frames.

Signed-off-by: Caleb Kan <ckan@cloudflare.com>
Add the internal frame-run encoding layer used by the trie-backed
stackdepot prototype. Runs are described as either raw frames or
compressed low 32-bit frame values that share one architecture prefix,
with caller-provided scratch storage so the kernel port does not hide
CONFIG_STACKDEPOT_MAX_FRAMES-sized arrays on the stack.

Enable the corresponding arm64 compression hook using the runtime
_text prefix plus adjacent 4 GiB prefixes so KASLR and module windows
round-trip exactly, while non-matching frames keep the raw fallback.
Harden the existing x86 and generic hooks and extend KUnit coverage for
raw, compressed, invalid, and max-frame cases.

Signed-off-by: Caleb Kan <ckan@cloudflare.com>
Keep the page_owner stack display buffer sized to the depth that
page_owner actually saves, and make count_threshold use the same
unsigned int range as the stackdepot page counters.

This keeps the stack-list marker subtraction explicit before threshold
filtering and rejects oversized debugfs thresholds instead of silently
suppressing every stack.

Signed-off-by: Caleb Kan <ckan@cloudflare.com>
Add the private trie-node storage primitive from the userspace
prototype. A node stores one homogeneous raw or compressed frame run
in caller-provided storage and records immutable parent and stack-length
metadata so a leaf parent chain can be materialized into caller-owned
output.

Keep the helper inert for now: stack_depot_save() and the public fetch
path still use existing hash records. KUnit covers node sizing, raw and
compressed round trips, parent-chain materialization, mixed-run
rejection, short storage, and invalid fetch inputs.

Signed-off-by: Caleb Kan <ckan@cloudflare.com>
Add the sorted child-array helper layer from the userspace trie
prototype. Child arrays are built in caller-owned storage, reject
duplicate first-frame keys and in-place replacement, and remain
unpublished so no RCU or COW visibility rules change yet.

Cover initialization, lookup, empty insertion, middle insertion,
unsorted input, duplicate insertion, and in-place update rejection in
stackdepot KUnit. Also document the page_owner stack listing race as
best-effort because counts can change while seq_file output is being
generated.

Signed-off-by: Caleb Kan <ckan@cloudflare.com>
Tighten the private trie helper contracts after adding child arrays.
Reject overlapping frame-run buffers, validate old child-array storage
alignment, and use an explicit ULONG_MAX arm64 prefix bound. Keep the
page_owner count helper limits consistent across set/inc paths and
document best-effort page_owner stack listing semantics.

Extend stackdepot KUnit coverage for arm64 compressed frame-run round
trips, compressed trie nodes without scratch storage, count-boundary
handling, and child-array edge cases. The trie helpers remain inert:
they only build caller-owned unpublished storage and do not change
stack_depot_save() or public fetch routing.

Signed-off-by: Caleb Kan <ckan@cloudflare.com>
Add the private trie node matching primitive from the userspace
prototype. It decodes one raw or compressed node run and returns the
matching prefix length against caller-provided frames without walking
parent or child links.

Extend stackdepot KUnit coverage for raw and compressed exact matches,
partial matches, shorter and longer inputs, first-frame mismatches,
and invalid inputs.

Signed-off-by: Caleb Kan <ckan@cloudflare.com>
Add the private append-chain primitive from the userspace trie
prototype. The helper builds unpublished raw or compressed trie nodes
from caller-owned storage, splitting chains at frame-run boundaries and
linking newly-created nodes with one-child arrays.

Keep the helper inert for now: it does not publish nodes, allocate
memory, or change stack_depot_save() and fetch routing. Cover raw
chains, parent chains, frame-run boundary splits, and invalid storage
inputs in stackdepot KUnit.

Signed-off-by: Caleb Kan <ckan@cloudflare.com>
Add the private append-publication primitive from the userspace trie
prototype. The helper builds a replacement child array around an
unpublished appended chain and installs it into caller-owned root or
parent storage only after validation succeeds.

Cover root publication, parent publication, duplicate rejection, root
replacement, and bad input handling in stackdepot KUnit.

Signed-off-by: Caleb Kan <ckan@cloudflare.com>
Make the private append-publication helper read the current child array
with READ_ONCE() and publish the replacement with release ordering. This
keeps the helper safe for future lockless trie readers.

Signed-off-by: Caleb Kan <ckan@cloudflare.com>
Add the private trie lookup-step classifier from the userspace trie
prototype. The helper reads one root or parent child array with acquire
ordering, compares the matching child node against caller frames, and
reports whether the caller should append, descend, split, promote, or
return an existing leaf.

Keep the helper inert for now: it does not allocate, publish, or change
stack_depot_save() and fetch routing. Cover append, descend, found,
promote, split, and invalid-input classifications in stackdepot KUnit.

Also make the counted-stack increment helper fail closed if the cmpxchg
loser path observes a non-positive count instead of delegating that case
to refcount_add().

Signed-off-by: Caleb Kan <ckan@cloudflare.com>
Harden the private stackdepot trie preparation helpers before the
backend starts routing production saves through them. The lookup step
now keeps parent-chain validation with the multi-step walker that has
the full matched prefix context, and the publication and scratch-buffer
contracts document the required writer serialization and aliasing rules.

Tighten page_owner stack-list accounting so a failed list-node
allocation cannot leave an untracked marker count behind, and keep
diagnostic stack counts from being resurrected after they reach zero.
The arm64 frame compression helper now rejects the synthetic zero
prefix explicitly.

Signed-off-by: Caleb Kan <ckan@cloudflare.com>
Add the private append-only insertion helper from the userspace trie
prototype. The helper handles the case where no existing child starts
with the next input frame: it preflights root or parent context,
storage aliasing, existing-child state, and publication storage before
building the unpublished append chain and publishing the replacement
child array last.

Cover root and parent append insertion, multi-run append insertion,
zero-frame rejection, alias rejection, existing-child rejection, and
failure atomicity in stackdepot KUnit.

Signed-off-by: Caleb Kan <ckan@cloudflare.com>
Extend the private append-insertion helper so it can descend through
fully matched trie nodes before appending the remaining stack suffix.
The helper still rejects found, promote, and split cases so those
operations can be implemented as separate, reviewable steps.

Preflight descend storage against the published child arrays and
subtrees before mutating caller-owned staging slots, preserving the
copy-on-write publication contract. Cover one-level and multi-level
descend insertion plus sibling-alias rejection in stackdepot KUnit.

Signed-off-by: Caleb Kan <ckan@cloudflare.com>
Give stackdepot files an explicit MAINTAINERS section so stackdepot
changes route to the mm tree and linux-kernel list directly instead of
being matched only through the broad library-code entry.

Signed-off-by: Caleb Kan <ckan@cloudflare.com>
Document the stackdepot prep assumptions that came up during review:
arm64 frame compression uses adjacent high-bit buckets around the 2 GB
module relocation window, saturated refcounts must fail positive-count
tests, and page_owner keeps saturated stacks retryable when list marker
tracking cannot be allocated.

Signed-off-by: Caleb Kan <ckan@cloudflare.com>
Keep the trie and frame-run prep interfaces private to stackdepot so
the public header only exposes APIs used outside lib/. The helpers are
still inert, but their contracts now reject aliasing, overlong parent
chains, count overflows, and untracked page_owner handles before later
storage-routing patches can depend on them.

Strengthen KUnit coverage for those edge cases and remove fixed-size
test buffers so the tests scale with the configured stack depth.

Signed-off-by: Caleb Kan <ckan@cloudflare.com>
Teach the private append insertion helper to promote a fully matched,
childless internal trie node into a terminal leaf by publishing a
copy-on-write replacement child array. Keep split and promote-with-
children cases rejected until subtree COW support lands.

Cover root-level promotion, descended promotion, and the childful
rejection path in stackdepot KUnit. Also keep raw node matching on the
direct raw-frame path so mismatch handling avoids the generic decoder.

Signed-off-by: Caleb Kan <ckan@cloudflare.com>
Allow the private trie promotion path to promote internal nodes that
already have children by carrying the child array into the replacement
node and reparenting descendants before publication. This keeps the
copy-on-write shape intact while preparing split insertion to preserve
existing subtrees.

Cover the childful promotion path in stackdepot KUnit and keep the test
node-slot allocator limited to single-run inputs.

Signed-off-by: Caleb Kan <ckan@cloudflare.com>
Add a private helper that initializes a trie node from a contiguous
slice of an existing node's frame run. This gives split insertion a
copy-on-write building block for prefix and old-tail nodes without
decoding or recompressing stored frame payloads.

Cover raw, compressed, parent-chain, and invalid slice cases in
stackdepot KUnit.

Signed-off-by: Caleb Kan <ckan@cloudflare.com>
Add a private helper that builds the child array for a future split
prefix from the displaced old tail and an optional new stack tail. The
helper keeps child ordering centralized and reuses existing child-array
validation so split insertion can publish a fully initialized prefix
subtree later.

Cover one-child, sorted two-child, duplicate-key, short-storage, and
storage-overlap cases in stackdepot KUnit.

Signed-off-by: Caleb Kan <ckan@cloudflare.com>
Caleb Kan added 5 commits June 25, 2026 14:47
Document that trie-backed saves do not fall back to hash storage when
trie insertion cannot proceed. This keeps trie_enabled pool pressure
visible instead of mixing backends in the same run.

Also fix a indentation issue in trie lookup.

Signed-off-by: Caleb Kan <ckan@cloudflare.com>
Document that stack_depot_save() may return trie-backed handles when
trie storage is enabled. Callers that need backend-independent stack
contents must use stack_depot_fetch_into(), stack_depot_print(), or
stack_depot_snprint().

Also tighten the stack_depot_fetch() kernel-doc to state that it only
accepts hash-backed handles, matching the legacy pointer-returning API.

Signed-off-by: Caleb Kan <ckan@cloudflare.com>
Make debugfs persistent counters explicitly hash-backed now that normal
persistent saves can use trie storage. This avoids reading hash-only
record counts as total stackdepot usage during trie validation.

Also make the gdb stackdepot helper reject trie-backed handles instead
of decoding them as hash pool offsets, and fix its pool-index diagnostic.

Signed-off-by: Caleb Kan <ckan@cloudflare.com>
Initialize trie free-list buckets once during trie init instead of
lazily checking and deriving that state from allocation paths. This
removes repeated hot-path checks while keeping trie enablement as the
single publication point for initialized trie state.

Also remove a single-use transaction init wrapper, make side-table ID
commit warn-only on impossible ordering, and drop a useless KUnit
assertion that only checked a local initializer.

Signed-off-by: Caleb Kan <ckan@cloudflare.com>
Type replacement child-array storage, warn when trie pool rollback
cannot unwind, and avoid a redundant parent-chain length walk during
trie lookup.

Also keep arch frame decompression infallible to match the compressor
contract, retry side-table boundary races once, and drop small
page_owner and KUnit dead checks.

Signed-off-by: Caleb Kan <ckan@cloudflare.com>
Comment thread mm/page_owner.c Outdated
Comment thread mm/page_owner.c Outdated
Comment thread mm/page_owner.c Outdated
Comment thread mm/page_owner.c Outdated
Comment thread lib/stackdepot.c
ret = __stack_depot_trie_alloc_txn_reserve(req);
if (ret)
return ret;
storage = req->storage ? *req->storage : NULL;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

is it safe for storage to be NULL here? This seems like overly defensive programming again. What's the invariant we should be checking?

Keep the page_owner changes in this series limited to routing stack saves
through countable stackdepot records. Drop the independent seq_file and
threshold fixes, along with formatting-only churn, so those changes do
not get buried in the trie series.

Signed-off-by: Caleb Kan <ckan@cloudflare.com>
Comment thread lib/stackdepot.c
ret = __stack_depot_trie_lookup_step(root, parent, entries, nr_entries, &lookup);
if (ret)
return ret;
if (lookup.status != STACK_DEPOT_TRIE_LOOKUP_DESCEND)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

instead of using a status flag for this, I would really prefer to see this expressed in code, e.g.

   found = trie_find_child(root, parent, entries[0], &children, &node, &pos, &found);
   if (!found)
      append_here();

   matched = node_match(node, entries, nr_entries);

   if (matched < node->run.nr_entries)
      split_node();

   if (matched < nr_entries) {
      parent = node;
      entries += matched;
      nr_entries -= matched;
      continue;
   }

   if (node->leaf_id)
      found_leaf();
   else
      promote_node();

For me, this is much easier to follow and makes the algorithm crystal clear. This is how things are done in kernel/bpf/lpm_trie.c, for example.

Comment thread lib/stackdepot.c
size_t child_size;
int ret;

if (!child)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

we just looked up the entry before this, how is this condition possible?

Comment thread lib/stackdepot.c

if (!child)
return -EINVAL;
if (child->parent != parent)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

this is an invariant bug. This needs to be WARN_ON() or WARN_ON_ONCE() (if it needs to be ratelimited). This should never happen.

Comment thread lib/stackdepot.c
if (root)
return &root->children;
return &parent->children;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

is it not possible to make this unnecessary by minimising our use of the stack_depot_trie_root type? Beyond some initial checks I would just expend to use root->node and then all this checking of root vs parent goes away?

Comment thread lib/stackdepot.c
return 0;

return ALIGN(size, sizeof(unsigned long));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

i'm not sure why we need all of this validation? Why can't this just be:

   static inline size_t stack_depot_trie_node_size(const struct stack_depot_frame_run *run)
   {
      return ALIGN(struct_size(node, data,
             stack_depot_frame_run_bytes(run)), sizeof(unsigned long));
   }

Comment thread lib/stackdepot.c
unsigned int pos;
bool found;

if (!old_array)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

how is this even possible? we have no old array and no new array?

Comment thread lib/stackdepot.c
return -EINVAL;
if (found || !trie_child_array_can_append(old_array, pos))
return -EINVAL;
{

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

please don't use these kinds of nested blocks. It took me 30 seconds to realise the open brakcet wasn't associated with the if() condition.

If you need variables declarations just stick them at the top of the fuction, or inside a nested block guarded by a condition, but not a naked one like this.

Comment thread lib/stackdepot.c
return -EINVAL;
if (stack_depot_trie_child_lower_bound(old_array, frame, &pos, &found))
return -EINVAL;
if (found || !trie_child_array_can_append(old_array, pos))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

i don't follow why we only check whether we can append way down here inside the append_prepare() function after checking whether we have a new array available. The logic flow seems really weird to reason about.

Either we need to append or we need a new array, why are we worrying about both cases in one code path?

Comment thread lib/stackdepot.c
memset(&txn->pool, 0, sizeof(txn->pool));
}

static int trie_side_publish_locked(const struct stack_depot_trie_leaf_update *updates,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

this is only ever called with the logs already held. Drop the lockdep_assert_held() and hoist this function into trie_side_publish(), please.

Comment thread lib/stackdepot.c
trie_child_array_publish_child((struct stack_depot_trie_child_array *)old_array,
pos, head);
/* Only tail append mutates a live array; COW arrays are unpublished. */
WRITE_ONCE(((struct stack_depot_trie_child_array *)old_array)->nr_children,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

it would really help reviewers to know that this is safe because A) concurrent writers are prohibited by stcak_depot_trie_workspace_lock and B) only readers need to see the updated value.

Comment thread lib/stackdepot.c
}

static int
stack_depot_trie_child_lower_bound(const struct stack_depot_trie_child_array *array,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

stack_depot_trie_slot_binsesarch() or something would be more descriptive here, I think. The salient point is "slot to insert the child" not "lower bound".

@mfleming

Copy link
Copy Markdown

The trie insertion path is currently hard to review because most internal helpers are written defensively, as if they are public APIs receiving hostile inputs. That obscures the actual invariants.

In particular, the path around __stack_depot_trie_insert_plan(), trie_publish_append_prepare(), trie_promote_child(), trie_split_subtree_prepare(), and trie_split_child() repeatedly revalidates facts the writer path should already know:

  • all trie writers are serialized by stack_depot_trie_workspace_lock
  • published trie nodes have valid run metadata
  • child arrays are sorted; only tail append mutates a live array
  • non-tail insert/replace/split uses a private replacement array
  • the planner determines the operation kind and exact slot/storage needs
  • side-table storage is reserved before publish

If those are the intended invariants, I think they should be documented once near the writer lock / child-array definition, and the internal helpers should rely on them. Today the code keeps rediscovering state: e.g. trie_publish_append_prepare() branches on new_storage == NULL, reruns lower_bound(), rechecks appendability, and republishes a side-table update inline; trie_split_subtree_prepare() validates slot counts, topology, split bounds, and side-table publish failure after the planner supposedly made this operation valid; __stack_depot_trie_node_size() revalidates runs loaded from trie-owned nodes.

This makes the code look like it supports many impossible interleavings and partial states. A simpler contract would be: boundary/planning code validates once, then trusted build/publish helpers consume an explicit planned op (append-in-place, append-replace, promote, split) plus exact slots. Internal misuse can be WARN_ON_ONCE() or left to the invariant, not threaded through normal -EINVAL paths.

I’m not asking for a rewrite of the trie algorithm. I’m asking to narrow the helper contracts so invalid states are designed out rather than checked everywhere. That should make the insert path much easier to review and reduce the rollback/error-path surface.

caleb-kan pushed a commit that referenced this pull request Jul 8, 2026
commit 68938cc upstream.

The xe driver keeps track of whether to probe display, and whether
display hardware is there, using xe->info.probe_display. It gets set to
false if there's no display after intel_display_device_probe(). However,
the display may also be disabled via fuses, detected at a later time in
intel_display_device_info_runtime_init().

In this case, the xe driver does for_each_intel_crtc() on uninitialized
mode config in xe_display_flush_cleanup_work(), leading to a NULL
pointer dereference, and generally calls display code with display info
cleared.

Check for intel_display_device_present() after
intel_display_device_info_runtime_init(), and reset
xe->info.probe_display as necessary. Also do unset_display_features()
for completeness, although display runtime init has already done
that. This will need to be unified across all cases later.

Move intel_display_device_info_runtime_init() call slightly earlier,
similar to i915, to avoid a bunch of unnecessary setup for no display
cases.

Note #1: The xe driver has no business doing low level display plumbing
like for_each_intel_crtc() to begin with. It all needs to happen in
display code.

Note #2: The actual bug is present already in commit 44e6949
("drm/xe/display: Implement display support"), but the oops was likely
introduced later at commit ddf6492 ("drm/xe/display: Make display
suspend/resume work on discrete").

Fixes: 44e6949 ("drm/xe/display: Implement display support")
Closes: https://gitlab.freedesktop.org/drm/xe/kernel/-/work_items/7904
Closes: https://gitlab.freedesktop.org/drm/xe/kernel/-/work_items/6150
Cc: stable@vger.kernel.org # v6.8+
Reviewed-by: Suraj Kandpal <suraj.kandpal@intel.com>
Link: https://patch.msgid.link/20260515160920.1082842-1-jani.nikula@intel.com
Signed-off-by: Jani Nikula <jani.nikula@intel.com>
(cherry picked from commit 7c3eb9f)
Signed-off-by: Matthew Brost <matthew.brost@intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
caleb-kan pushed a commit that referenced this pull request Jul 8, 2026
commit cb481e5 upstream.

The length for the internal output buffer is calculated incorrectly, which
can result overflow when a too small buffer is provided.

Fix the bug by allocating internal output with the size of the maximum
length of the cryptographic primitive instead of caller provided size.

Link: https://lore.kernel.org/keyrings/20260531024914.3712130-1-jarkko@kernel.org/
Cc: stable@vger.kernel.org # v4.20+
Fixes: 00d60fd ("KEYS: Provide keyctls to drive the new key type ops for asymmetric keys [ver #2]")
Reported-by: Alessandro Groppo <ale.grpp@gmail.com>
Tested-by: Alessandro Groppo <ale.grpp@gmail.com>
Signed-off-by: Jarkko Sakkinen <jarkko@kernel.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
@caleb-kan
caleb-kan marked this pull request as draft July 8, 2026 12:20
caleb-kan pushed a commit that referenced this pull request Jul 22, 2026
commit 40ef3ed upstream.

The current implementation uses pci_num_vf() while holding the
memory_lock to prevent changing the power state of a PF when
VFs are enabled. This creates a lockdep circular dependency
warning because memory_lock is held during device probing.

[  286.997167] ======================================================
[  287.003363] WARNING: possible circular locking dependency detected
[  287.009562] 7.0.0-dbg-DEV #3 Tainted: G S
[  287.015074] ------------------------------------------------------
[  287.021270] vfio_pci_sriov_/18636 is trying to acquire lock:
[  287.026942] ff45bea2294d4968 (&vdev->memory_lock){+.+.}-{4:4}, at:
vfio_pci_core_runtime_resume+0x1f/0xa0
[  287.036530]
[  287.036530] but task is already holding lock:
[  287.042383] ff45bea3a96b8230 (&new_dev_set->lock){+.+.}-{4:4}, at:
vfio_group_fops_unl_ioctl+0x44d/0x7b0
[  287.051879]
[  287.051879] which lock already depends on the new lock.
[  287.051879]
[  287.060070]
[  287.060070] the existing dependency chain (in reverse order) is:
[  287.067568]
[  287.067568] -> #2 (&new_dev_set->lock){+.+.}-{4:4}:
[  287.073941]        __mutex_lock+0x92/0xb80
[  287.078058]        vfio_assign_device_set+0x66/0x1b0
[  287.083042]        vfio_pci_core_register_device+0xd1/0x2a0
[  287.088638]        vfio_pci_probe+0xd2/0x100
[  287.092933]        local_pci_probe_callback+0x4d/0xa0
[  287.098001]        process_scheduled_works+0x2ca/0x680
[  287.103158]        worker_thread+0x1e8/0x2f0
[  287.107452]        kthread+0x10c/0x140
[  287.111230]        ret_from_fork+0x18e/0x360
[  287.115519]        ret_from_fork_asm+0x1a/0x30
[  287.119983]
[  287.119983] -> #1 ((work_completion)(&arg.work)){+.+.}-{0:0}:
[  287.127219]        __flush_work+0x345/0x490
[  287.131429]        pci_device_probe+0x2e3/0x490
[  287.135979]        really_probe+0x1f9/0x4e0
[  287.140180]        __driver_probe_device+0x77/0x100
[  287.145079]        driver_probe_device+0x1e/0x110
[  287.149803]        __device_attach_driver+0xe3/0x170
[  287.154789]        bus_for_each_drv+0x125/0x150
[  287.159346]        __device_attach+0xca/0x1a0
[  287.163720]        device_initial_probe+0x34/0x50
[  287.168445]        pci_bus_add_device+0x6e/0x90
[  287.172995]        pci_iov_add_virtfn+0x3c9/0x3e0
[  287.177719]        sriov_add_vfs+0x2c/0x60
[  287.181838]        sriov_enable+0x306/0x4a0
[  287.186038]        vfio_pci_core_sriov_configure+0x184/0x220
[  287.191715]        sriov_numvfs_store+0xd9/0x1c0
[  287.196351]        kernfs_fop_write_iter+0x13f/0x1d0
[  287.201338]        vfs_write+0x2be/0x3b0
[  287.205286]        ksys_write+0x73/0x100
[  287.209233]        do_syscall_64+0x14d/0x750
[  287.213529]        entry_SYSCALL_64_after_hwframe+0x77/0x7f
[  287.219120]
[  287.219120] -> #0 (&vdev->memory_lock){+.+.}-{4:4}:
[  287.225491]        __lock_acquire+0x14c6/0x2800
[  287.230048]        lock_acquire+0xd3/0x2f0
[  287.234168]        down_write+0x3a/0xc0
[  287.238019]        vfio_pci_core_runtime_resume+0x1f/0xa0
[  287.243436]        __rpm_callback+0x8c/0x310
[  287.247730]        rpm_resume+0x529/0x6f0
[  287.251765]        __pm_runtime_resume+0x68/0x90
[  287.256402]        vfio_pci_core_enable+0x44/0x310
[  287.261216]        vfio_pci_open_device+0x1c/0x80
[  287.265947]        vfio_df_open+0x10f/0x150
[  287.270148]        vfio_group_fops_unl_ioctl+0x4a4/0x7b0
[  287.275476]        __se_sys_ioctl+0x71/0xc0
[  287.279679]        do_syscall_64+0x14d/0x750
[  287.283975]        entry_SYSCALL_64_after_hwframe+0x77/0x7f
[  287.289559]
[  287.289559] other info that might help us debug this:
[  287.289559]
[  287.297582] Chain exists of:
[  287.297582]   &vdev->memory_lock --> (work_completion)(&arg.work)
--> &new_dev_set->lock
[  287.297582]
[  287.310023]  Possible unsafe locking scenario:
[  287.310023]
[  287.315961]        CPU0                    CPU1
[  287.320510]        ----                    ----
[  287.325059]   lock(&new_dev_set->lock);
[  287.328917]
lock((work_completion)(&arg.work));
[  287.336153]                                lock(&new_dev_set->lock);
[  287.342523]   lock(&vdev->memory_lock);
[  287.346382]
[  287.346382]  *** DEADLOCK ***
[  287.346382]
[  287.352315] 2 locks held by vfio_pci_sriov_/18636:
[  287.357125]  #0: ff45bea208ed3e18 (&group->group_lock){+.+.}-{4:4},
at: vfio_group_fops_unl_ioctl+0x3e3/0x7b0
[  287.367048]  #1: ff45bea3a96b8230 (&new_dev_set->lock){+.+.}-{4:4},
at: vfio_group_fops_unl_ioctl+0x44d/0x7b0
[  287.376976]
[  287.376976] stack backtrace:
[  287.381353] CPU: 191 UID: 0 PID: 18636 Comm: vfio_pci_sriov_
Tainted: G S                  7.0.0-dbg-DEV #3 PREEMPTLAZY
[  287.381355] Tainted: [S]=CPU_OUT_OF_SPEC
[  287.381356] Call Trace:
[  287.381357]  <TASK>
[  287.381358]  dump_stack_lvl+0x54/0x70
[  287.381361]  print_circular_bug+0x2e1/0x300
[  287.381363]  check_noncircular+0xf9/0x120
[  287.381364]  ? __lock_acquire+0x5b4/0x2800
[  287.381366]  __lock_acquire+0x14c6/0x2800
[  287.381368]  ? pci_mmcfg_read+0x4f/0x220
[  287.381370]  ? pci_mmcfg_write+0x57/0x220
[  287.381371]  ? lock_acquire+0xd3/0x2f0
[  287.381373]  ? pci_mmcfg_write+0x57/0x220
[  287.381374]  ? lock_release+0xef/0x360
[  287.381376]  ? vfio_pci_core_runtime_resume+0x1f/0xa0
[  287.381377]  lock_acquire+0xd3/0x2f0
[  287.381378]  ? vfio_pci_core_runtime_resume+0x1f/0xa0
[  287.381379]  ? lock_is_held_type+0x76/0x100
[  287.381382]  down_write+0x3a/0xc0
[  287.381382]  ? vfio_pci_core_runtime_resume+0x1f/0xa0
[  287.381383]  vfio_pci_core_runtime_resume+0x1f/0xa0
[  287.381384]  ? __pfx_pci_pm_runtime_resume+0x10/0x10
[  287.381385]  __rpm_callback+0x8c/0x310
[  287.381386]  ? ktime_get_mono_fast_ns+0x3d/0xb0
[  287.381389]  ? __pfx_pci_pm_runtime_resume+0x10/0x10
[  287.381390]  rpm_resume+0x529/0x6f0
[  287.381392]  ? lock_is_held_type+0x76/0x100
[  287.381394]  __pm_runtime_resume+0x68/0x90
[  287.381396]  vfio_pci_core_enable+0x44/0x310
[  287.381398]  vfio_pci_open_device+0x1c/0x80
[  287.381399]  vfio_df_open+0x10f/0x150
[  287.381401]  vfio_group_fops_unl_ioctl+0x4a4/0x7b0
[  287.381402]  __se_sys_ioctl+0x71/0xc0
[  287.381404]  do_syscall_64+0x14d/0x750
[  287.381405]  ? entry_SYSCALL_64_after_hwframe+0x77/0x7f
[  287.381406]  ? trace_irq_disable+0x25/0xd0
[  287.381409]  entry_SYSCALL_64_after_hwframe+0x77/0x7f

Introduce a private flag 'sriov_active' in the vfio_pci_core_device
struct. This  allows the driver to track the SR-IOV power state requirement
without  relying on pci_num_vf() while holding the memory_lock. The lock is
now  only held to set the flag and ensure the device is in D0, after which
pci_enable_sriov() can be called without the lock.

Fixes: f4162eb ("vfio/pci: Change the PF power state to D0 before enabling VFs")
Cc: stable@vger.kernel.org
Suggested-by: Jason Gunthorpe <jgg@ziepe.ca>
Suggested-by: Alex Williamson <alex@shazbot.org>
Signed-off-by: Raghavendra Rao Ananta <rananta@google.com>
Link: https://lore.kernel.org/r/20260514173449.3282188-1-rananta@google.com
[promote bitfield to plain bool to avoid storage-unit races]
Signed-off-by: Alex Williamson <alex@shazbot.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
caleb-kan pushed a commit that referenced this pull request Jul 22, 2026
commit fa09f08 upstream.

The etb10 miscdevice uses drvdata->reading as a shared exclusivity gate
for userspace buffer access. etb_open() claims that gate with
local_cmpxchg(), and etb_release() clears it with local_set().

That gate is shared per-device state rather than CPU-local state. A
running system can reach it whenever /dev/<etb> is opened, closed, and
reopened by different tasks while the device remains registered, so the
same drvdata->reading variable may be claimed on one CPU and later
cleared on another.

This code used to use atomic_t for the same gate, but commit
27b10da ("coresight: etb10: moving to local atomic operations")
changed it to local_t even though the access pattern remained cross-task
and cross-CPU. Restore atomic_t together with atomic_cmpxchg() and
atomic_set() so the exclusivity gate again uses a primitive intended
for shared state.

The issue was found on Linux v6.18.21 by our static analysis tool while
scanning surviving local_t-on-shared-state sites, and then manually
reviewed against the live etb10 file-op path.

It was runtime-validated with a reproducible QEMU no-device KCSAN PoC
that kept the same report-local contract:

  1. use one shared struct etb_drvdata carrier and its
     drvdata->reading gate;
  2. call etb_open() and etb_release() sequentially on that gate to
     confirm the original claim/clear path;
  3. bind the open side to CPU0 and the release side to CPU1 for the
     same gate to show cross-CPU ownership;
  4. run bound workers that repeatedly race etb_open() and
     etb_release() on the same gate until KCSAN reports a target hit.

The harness recorded:

  L1 passed open=1 release=1
  reading_after_open=1 reading_after_release=0
  L2 passed open_cpu=0 release_cpu=1
  cross_cpu_release=1 reading_after=0 open_ret=0

Representative KCSAN excerpt from the no-device validation run:

  BUG: KCSAN: data-race in etb_open.constprop.0.isra.0 [vuln_msv]

  write to 0xffffffffc0003810 of 4 bytes by task 216 on cpu 1:
   etb_open.constprop.0.isra.0+0x38/0x80 [vuln_msv]
   l3_worker_thread_fn+0x4f/0xf0 [vuln_msv]
   kthread+0x17e/0x1c0
   ret_from_fork+0x22/0x30

  read to 0xffffffffc0003810 of 4 bytes by task 215 on cpu 0:
   etb_open.constprop.0.isra.0+0x18/0x80 [vuln_msv]
   l3_worker_thread_fn+0x4f/0xf0 [vuln_msv]
   kthread+0x17e/0x1c0
   ret_from_fork+0x22/0x30

  value changed: 0x00000000 -> 0x00000001

  Reported by Kernel Concurrency Sanitizer on:
  CPU: 0 PID: 215 Comm: etb10_l3_a Tainted: G           O       6.1.66 #2

This no-device harness is not a real ETB10 hardware end-to-end run, but
it preserves the same shared drvdata->reading gate and the same
etb_open()/etb_release() claim/clear contract. No real ETB10 hardware
was available for runtime testing.

Build-tested with:
  make olddefconfig
  make -j"$(nproc)" drivers/hwtracing/coresight/coresight-etb10.o

Fixes: 27b10da ("coresight: etb10: moving to local atomic operations")
Cc: stable@vger.kernel.org
Signed-off-by: Runyu Xiao <runyu.xiao@seu.edu.cn>
Reviewed-by: James Clark <james.clark@linaro.org>
Signed-off-by: Suzuki K Poulose <suzuki.poulose@arm.com>
Link: https://lore.kernel.org/r/20260528165201.319452-1-runyu.xiao@seu.edu.cn
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
caleb-kan pushed a commit that referenced this pull request Jul 22, 2026
…softlockup

commit 66366d2 upstream.

We hit a real softlockup in an internal stress test environment.  The
workload was LTP memory/swap stress on a large arm64 machine, with 320
CPUs, about 1TB memory and an 8.6GB swap device.  The system was under
heavy load and the swap device had a large number of full clusters.  The
softlockup was triggered during a stress test after about 3 days.

So, add periodic cond_resched() calls during large full_clusters
reclaim operations to prevent softlockup issues.

Detailed call trace as follow:

PID: 3817773  TASK: ffff0883bb28b780  CPU: 48   COMMAND: "kworker/48:7"
   #0 [ffff800080183d10] __crash_kexec at ffffa4c1361e5de4
   #1 [ffff800080183d90] panic at ffffa4c1360d5e9c
   #2 [ffff800080183e20] watchdog_timer_fn at ffffa4c136231fa8
   ...
  #16 [ffff8000c4ad3cb0] swap_cache_del_folio at ffffa4c1363e1614
  #17 [ffff8000c4ad3ce0] __try_to_reclaim_swap at ffffa4c1363e4bfc
  #18 [ffff8000c4ad3d40] swap_reclaim_full_clusters at ffffa4c1363e5474
  #19 [ffff8000c4ad3da0] swap_reclaim_work at ffffa4c1363e550c
  #20 [ffff8000c4ad3dc0] process_one_work at ffffa4c136102edc
  #21 [ffff8000c4ad3e10] worker_thread at ffffa4c136103398
  #22 [ffff8000c4ad3e70] kthread at ffffa4c13610d95c

Link: https://lore.kernel.org/20260506130919.2298807-1-kerayhuang@tencent.com
Fixes: 5168a68 ("mm, swap: avoid over reclaim of full clusters")
Signed-off-by: Zijiang Huang <kerayhuang@tencent.com>
Reviewed-by: Kairui Song <kasong@tencent.com>
Reviewed-by: Hao Peng <flyingpeng@tencent.com>
Reviewed-by: albinwyang <albinwyang@tencent.com>
Reviewed-by: Baoquan He <baoquan.he@linux.dev>
Acked-by: Chris Li <chrisl@kernel.org>
Cc: Barry Song <baohua@kernel.org>
Cc: Kairui Song <kasong@tencent.com>
Cc: Kemeng Shi <shikemeng@huaweicloud.com>
Cc: Nhat Pham <nphamcs@gmail.com>
Cc: Youngjun Park <youngjun.park@lge.com>
Cc: <stable@vger.kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
caleb-kan pushed a commit that referenced this pull request Jul 22, 2026
commit b9399d2 upstream.

EHCI and FOTG210 isochronous submits build an ehci_iso_sched before
linking the URB to the endpoint queue, and keep the staged schedule in
urb->hcpriv until iso_stream_schedule() and the link helpers consume it.
If the controller is no longer accessible, or usb_hcd_link_urb_to_ep()
fails, submit jumps to done_not_linked before that handoff happens and
leaks the staged schedule still attached to urb->hcpriv.

Free the staged schedule from done_not_linked when submit fails before
the URB is linked and clear urb->hcpriv after the free.

The bug was first flagged by an experimental analysis tool we are
developing for kernel memory-management bugs while analyzing
v6.13-rc1. The tool is still under development and is not yet publicly
available. Manual inspection confirms that the bug is still
present in v7.1.1.

An x86_64 allyesconfig build showed no new warnings. As we do not have an
EHCI host controller with a USB isochronous device to test with, no
runtime testing was able to be performed.

Fixes: 8de9840 ("[PATCH] USB: Fix USB suspend/resume crasher (#2)")
Fixes: e9df41c ("USB: make HCDs responsible for managing endpoint queues")
Fixes: 7d50195 ("usb: host: Faraday fotg210-hcd driver")
Cc: stable <stable@kernel.org>
Signed-off-by: Dawei Feng <dawei.feng@seu.edu.cn>
Reviewed-by: Alan Stern <stern@rowland.harvard.edu>
Link: https://patch.msgid.link/20260630071419.349161-1-dawei.feng@seu.edu.cn
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
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.

3 participants