{"id":112677,"date":"2026-09-07T07:00:00","date_gmt":"2026-09-07T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=112677"},"modified":"2026-09-08T08:09:16","modified_gmt":"2026-09-08T15:09:16","slug":"20260907-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20260907-00\/?p=112677","title":{"rendered":"Why don&#8217;t we allow stacks to be sparse, instead of forcing them to be contiguous?"},"content":{"rendered":"<p>When I discussed <a title=\"Why don't we just make the entire stack out of guard pages?\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20260713-00\/?p=112528\"> why we don&#8217;t just make the entire stack out of guard pages<\/a>, <a href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20260713-00\/?p=112528&amp;commentid=144516#comment-144516\"> commenter BCS wondered<\/a>, &#8220;Why require that the stack use contiguously mapped pages? What would break if only touched pages got mapped in? That could actually be a good thing for example with a function that wanted to <code>alloca<\/code> 512MB on the stack but only read\/writes a few pages.&#8221;<\/p>\n<p>So the question is asking why the stack must be contiguous. Why not let it be sparse and fault in only the pages that are touched?<\/p>\n<p>The first issue is that the stack check code would have to include an explicit check against the stack limit, instead of just walking down the stack a page at a time. This explicit check is needed to avoid security vulnerabilities if somebody manages to <code>alloca<\/code> a buffer so large that it goes past the end of the stack reservation entirely. If you go a single page at a time, you will eventually hit the no-access page that marks the end of the stack. But if you can leap over multiple pages at a time without touching them, you might leap so far past the end of the stack that you land somewhere else and start corrupting that other memory because you&#8217;re using it as a stack. In linux circles, this vulnerability is nicknamed &#8220;<a href=\"https:\/\/www.qualys.com\/2017\/06\/19\/stack-clash\/stack-clash.txt\">Stack Clash<\/a>&#8220;\u00b9 and goes more formally by &#8220;<a href=\"https:\/\/lwn.net\/Articles\/725832\/\">stack guard-page hopping<\/a>.&#8221;\u00b2<\/p>\n<p>After fixing that issue, you have another problem: How would you report a failure to commit a page in the middle of the stack?<\/p>\n<pre>void dosomething()\r\n{\r\n    void* buffer = NULL;\r\n    __try {\r\n        buffer = alloca(65536);\r\n    } __except (GetExceptionCode() == STATUS_STACK_OVERFLOW) {\r\n        if (!_resetstkoflw()) __fastfail(FAST_FAIL_FATAL_APP_EXIT);\r\n    }\r\n\r\n    if (buffer != NULL) {\r\n        \u27e6 use the buffer \u27e7\r\n    }\r\n}\r\n<\/pre>\n<p>If you allowed sparse stacks, then the memory for the <code>buffer<\/code> would not actually be committed until the code used it. But the point the code uses the buffer is <i>outside<\/i> the exception handler for the failed <code>alloca()<\/code>. The code assumes, not unreasonably, that if <code>alloca<\/code> succeeds, then the memory is indeed allocated.<\/p>\n<p>I guess you could fix this by committing the memory without making it present. That would mean making a call to <code>Virtual\u00adAlloc<\/code> to expand the stack rather than just accessing the memory. Not only would this make the stack expansion code more complicated, particularly since <a title=\"Windows stack limit checking retrospective, follow-up\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20260617-00\/?p=112436\"> you have to preserve all the registers that might possibly be used by any calling convention<\/a>, but you also have to make sure that the <code>Virtual\u00adAlloc<\/code> function itself doesn&#8217;t allocate too much stack!<\/p>\n<p>Now, you can still tweak the x86-32 stack prober to avoid <a href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20260311-00\/?p=112134&amp;commentid=143917#comment-143917\"> pete.d<\/a>&#8216;s problem, where a large stack frame is made completely present, with the resulting page-ins creating noticeable performance issues. The x86-32 prober could short-circuit the stack probe (<a title=\"Windows stack limit checking retrospective: arm64, also known as AArch64\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20260320-00\/?p=112154\">like the MIPS and other processors listed in the table on this page<\/a>) so that the page-ins occur only when the stack is actually expanding.<\/p>\n<p>\u00b9 Bonus reading about Stack Clash:<\/p>\n<ul>\n<li><a href=\"https:\/\/developers.redhat.com\/blog\/2017\/09\/25\/stack-clash-mitigation-gcc-background\"> Stack Clash Mitigation in GCC &#8212; Background<\/a><\/li>\n<li><a href=\"https:\/\/developers.redhat.com\/blog\/2019\/04\/30\/stack-clash-mitigation-in-gcc-why-fstack-check-is-not-the-answer\"> Stack Clash mitigation in GCC: Why -fstack-check is not the answer<\/a><\/li>\n<li><a href=\"https:\/\/developers.redhat.com\/blog\/2020\/05\/22\/stack-clash-mitigation-in-gcc-part-3\"> Stack clash mitigation in GCC, Part 3<\/a><\/li>\n<\/ul>\n<p>\u00b2 Some systems mitigate stack guard-page hopping by creating a really large no-access region beyond the end of the stack. However, this isn&#8217;t a fix; just a mitigation. It just makes people have to leap further to clear the no-access region. If you already have this vulnerability, it&#8217;s probably because an attacker can control the size of the allocation, in which case you didn&#8217;t really slow them down by much; they just have to put a bigger number in their attack payload.<\/p>\n<p>Other systems address this more thoroughly by (surprise) probing each page of the stack in sequence.<\/p>\n<p>Stack Clash continues to be a problem even though gcc had a solution in 2020. <a href=\"https:\/\/app.opencve.io\/cve\/CVE-2026-77658\"> Here&#8217;s CVE-2026-77658 from just a few days ago<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Imagining how to report a memory allocation failure for a sparse page.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-112677","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Imagining how to report a memory allocation failure for a sparse page.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112677","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=112677"}],"version-history":[{"count":1,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112677\/revisions"}],"predecessor-version":[{"id":112678,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112677\/revisions\/112678"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=112677"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=112677"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=112677"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}