{"id":112510,"date":"2026-07-08T07:00:00","date_gmt":"2026-07-08T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=112510"},"modified":"2026-07-04T07:13:04","modified_gmt":"2026-07-04T14:13:04","slug":"20260708-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20260708-00\/?p=112510","title":{"rendered":"The other kind of control flow guard check: The combined validate and call"},"content":{"rendered":"<p>Some time ago, I discussed <a title=\"What to do when you have a crash in the runtime control flow guard check\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20251029-00\/?p=111738\"> how to extract the function pointer from the control flow guard check<\/a>. I gave the code for <code>LdrpValidateUserCallTarget<\/code>, but there&#8217;s another version of the function that combines the validation with a call. I assume this version exists because after validating a function pointer, you nearly always call it, so you may as well combine the two operations.<\/p>\n<p>But this does mean that the calling convention has to change, because the registers need to be set up for the final call, meaning that the parameters to the combined validate-and-call cannot overlap with registers used by the calling convention. (<a title=\"Windows stack limit checking retrospective, follow-up\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20260617-00\/?p=112436\">Sound familiar<\/a>?)<\/p>\n<p>Here&#8217;s an x86-64 version.<\/p>\n<pre>    mov     r11, [ntdll!....]\r\n    mov     r10,rax\r\n    shr     r10,9\r\n    mov     r11,qword ptr [r11+r10*8]\r\n    mov     r10,rax\r\n    shr     r10,3\r\n    test    al,0Fh\r\n    jne     @1\r\n    bt      r11,r10\r\n    jae     @2\r\n    jmp     rax\r\n@1: btr     r10,0\r\n    bt      r11,r10\r\n    jae     @3\r\n@2: or      r10,1\r\n    bt      r11,r10\r\n    jae     @3\r\n    jmp     rax\r\n@3: xor     r10d, r10d\r\n    jmp     bad\r\n<\/pre>\n<p>Let&#8217;s put this side-by-side with the validate-only version:<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse;\" border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<th>Validate only<\/th>\n<th>Validate and call<\/th>\n<\/tr>\n<tr>\n<td>\n<pre>    mov     rdx,qword ptr [ntdll!....]\r\n    mov     rax,rcx\r\n    shr     rax,9\r\n    mov     rdx,qword ptr [rdx+rax*8]\r\n    mov     rax,rcx\r\n    shr     rax,3\r\n    test    cl,0Fh\r\n    jne     @1\r\n    bt      rdx,rax\r\n    jae     @2\r\n    ret\r\n@1: btr     rax,0\r\n    bt      rdx,rax\r\n    jae     @3\r\n@2: or      rax,1\r\n    bt      rdx,rax\r\n    jae     @3\r\n    ret\r\n@3: mov     rax,rcx\r\n    xor     r10d,r10d\r\n    jmp     bad\r\n<\/pre>\n<\/td>\n<td>\n<pre>    mov     r11, [ntdll!....]\r\n    mov     r10,rax\r\n    shr     r10,9\r\n    mov     r11,qword ptr [r11+r10*8]\r\n    mov     r10,rax\r\n    shr     r10,3\r\n    test    al,0Fh\r\n    jne     @1\r\n    bt      r11,r10\r\n    jae     @2\r\n    jmp     rax\r\n@1: btr     r10,0\r\n    bt      r11,r10\r\n    jae     @3\r\n@2: or      r10,1\r\n    bt      r11,r10\r\n    jae     @3\r\n    jmp     rax\r\n@3:\r\n    xor     r10d, r10d\r\n    jmp     bad\r\n<\/pre>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>The logic is the same; the functions merely use different registers.<\/p>\n<p>The validate-only version receives the address in <code>rcx<\/code> and uses <code>rax<\/code> and <code>rdx<\/code> as scratch registers. The validate-and-call version receives the address in <code>rax<\/code> and uses <code>r10<\/code> and <code>r11<\/code> as scratch registers. (There&#8217;s also a small change when a bad pointer is detected: The validate-and-call version already has the bad pointer in the <code>rax<\/code> register, so it doesn&#8217;t have to do anything to move it there.)<\/p>\n<p>The validate-and-call version shifts its parameter and scratch registers to those not used by the x86-64 Windows calling convention, so that it can finish with a <code>jmp rax<\/code> to jump to the validated function with all function parameters intact.<\/p>\n<p>For AArch64, the story is similar.<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse;\" border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<th>Validate only<\/th>\n<th>Validate and call<\/th>\n<\/tr>\n<tr>\n<td>\n<pre>    adrp        xip0,ntdll!....\r\n    ldr         xip0,[xip0,#0x598]\r\n\r\n    lsr         xip1,x15,#6\r\n    tst         x15,#0xF\r\n    ldrb        wip1,[xip0,xip1]\r\n    ubfx        xip0,x15,#3,#3\r\n    bne         @2\r\n\r\n    lsr         xip1,xip1,xip0\r\n    tbz         wip1,#0,@3\r\n@1: ret\r\n\r\n@2: and         xip0,xip0,#-2\r\n    lsr         xip1,xip1,xip0\r\n    tbz         wip1,#0,@4\r\n@3: tbnz        wip1,#1,@1\r\n@4: mov         xip0,#0\r\n    b           @5\r\n@5: b           bad\r\n<\/pre>\n<\/td>\n<td>\n<pre>    adrp        xip0,ntdll!....\r\n    ldr         xip0,[xip0,#0x598]\r\n\r\n    lsr         xip1,x9,#6\r\n    tst         x9,#0xF\r\n    ldrb        wip1,[xip0,xip1]\r\n    ubfx        xip0,x9,#3,#3\r\n    bne         @2\r\n\r\n    lsr         xip1,xip1,xip0\r\n    tbz         wip1,#0,@3\r\n@1: br          x9\r\n\r\n@2: and         xip0,xip0,#-2\r\n    lsr         xip1,xip1,xip0\r\n    tbz         wip1,#0,@4\r\n@3: tbnz        wip1,#1,@1\r\n@4: mov         xip0,#1\r\n    mov         x15,x9\r\n    b           bad\r\n<\/pre>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Again, the code sequences are the same; it&#8217;s just the register usage. (And the code sequence when a bad call is detected.) The validate-only version takes the address in <code>x15<\/code>, whereas the validate-and-call version takes the address in <code>x9<\/code>. (Both use <code>xip0<\/code> and <code>xip1<\/code> as scratch registers.) And the validate-and-call version finishes with a <code>b r9<\/code> to jump directly to the validated address instead of returning.<\/p>\n<p>Again, you can extract the bad pointer from the thing that is shifted. For x86-64 validate-and-call, it&#8217;s <code>rax<\/code>, and for Aarch64 validate-and-call, it&#8217;s <code>r9<\/code>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>A two-in-one package.<\/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-112510","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>A two-in-one package.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112510","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=112510"}],"version-history":[{"count":1,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112510\/revisions"}],"predecessor-version":[{"id":112511,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112510\/revisions\/112511"}],"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=112510"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=112510"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=112510"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}