Skip to content

fix: add ShowCard nesting depth limit to prevent stack overflow#9371

Open
karan68 wants to merge 1 commit intomicrosoft:mainfrom
karan68:fix/showcard-nesting-depth-limit
Open

fix: add ShowCard nesting depth limit to prevent stack overflow#9371
karan68 wants to merge 1 commit intomicrosoft:mainfrom
karan68:fix/showcard-nesting-depth-limit

Conversation

@karan68
Copy link
Copy Markdown

@karan68 karan68 commented May 8, 2026

Summary
Adds a maximum nesting depth limit (5 levels) for Action.ShowCard in the shared C++ object model parser. This prevents deeply nested ShowCard payloads from causing a stack overflow crash in downstream renderers.

Problem
ShowCardActionParser::Deserialize calls AdaptiveCard::Deserialize recursively with zero depth tracking. A malicious or malformed card payload with deeply nested Action.ShowCard actions creates an unbounded recursive parse tree. When this tree is then rendered by the XAML renderer (BuildXamlTreeFromAdaptiveCard → BuildActions → BuildInlineShowCard → BuildShowCard → recurse), the deep recursion overflows the stack, crashing the host process.

Verified locally: A card with 325 levels of ShowCard nesting parses successfully into a fully-recursive tree of 325 AdaptiveCard objects — the parser provides no resistance whatsoever. The only incidental barrier is jsoncpp's stackLimit (1000 JSON nesting levels), which is not an AdaptiveCards security control.

This is relevant to all renderers that use the shared C++ model (UWP, WinUI3, Android, iOS), since the unbounded tree is constructed at parse time before any renderer-specific code runs.

Changes
4 files changed, 146 insertions:

File Change
ParseContext.h Added c_maxShowCardDepth (5), depth counter, and CanIncrementShowCardDepth() / IncrementShowCardDepth() / DecrementShowCardDepth() methods
ParseContext.cpp Implemented the three depth tracking methods
ShowCardAction.cpp Guarded the recursive AdaptiveCard::Deserialize call in ShowCardActionParser::Deserialize when depth exceeds the limit, sets an empty inner card and emits a CustomWarning
AdaptiveCardsSharedModelUnitTest/ObjectModelTest.cpp Added 4 test methods: within-limit, at-exact-limit, exceeds-limit, and deep-nesting (200 levels)

How it works

// Before parsing a ShowCard's inner card:
if (!context.CanIncrementShowCardDepth())    // depth >= 5?
{
    // Emit warning, set empty card, return early no recursion
    context.warnings.push_back(...);
    showCardAction->SetCard(std::make_shared<AdaptiveCard>());
    return showCardAction;
}

context.IncrementShowCardDepth();            // depth 0→1, 1→2, etc.
auto parseResult = AdaptiveCard::Deserialize(json.get(propertyName, ...), "", context);
context.DecrementShowCardDepth();            // restore on return

Behavior

Input depth Before this fix After this fix
3 Parses to depth 3 Parses to depth 3 (unchanged)
5 Parses to depth 5 Parses to depth 5 (unchanged)
6 Parses to depth 6 Capped at depth 5 + warning
10 Parses to depth 10 Capped at depth 5 + warning
200 Parses to depth 200 → renderer crash Capped at depth 5 + warning, no crash

No breaking changes. Real-world cards never nest ShowCards deeper than 2–3 levels. The limit of 5 is generous.

Testing
Verified with a standalone C++ test binary that builds against the ObjectModel library:

TEST 1: Depth 3 (within limit) parses fully         — PASS
TEST 2: Depth 5 (at limit) parses fully              — PASS
TEST 3: Depth 10 (exceeds limit) is capped           — PASS
TEST 4: Depth 200 (previously dangerous) is capped   — PASS
TEST 5: Depth 6 (one above limit) is capped          — PASS
TEST 6: Card without ShowCards is unaffected           — PASS

4 unit tests also added to ObjectModelTest.cpp in the existing CppUnitTest project.

ShowCardActionParser::Deserialize calls AdaptiveCard::Deserialize
recursively with no depth guard, allowing a malicious card with
deeply nested ShowCards to cause a stack overflow crash in the
XAML renderer (BuildXamlTreeFromAdaptiveCard -> BuildActions ->
BuildInlineShowCard -> BuildShowCard -> recurse).

This adds a depth counter to ParseContext that caps ShowCard
nesting at 5 levels. ShowCards beyond the limit get an empty
inner card and a parse warning is emitted.

Verified: depth 200 previously created a fully-recursive tree
of 200 AdaptiveCard objects; now capped at 5 with warning.

Security: Fixes unbounded recursion that can crash WidgetBoard.exe
Related: microsoft#9343
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.

2 participants