{"id":8333,"date":"2012-02-10T07:00:00","date_gmt":"2012-02-10T07:00:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/2012\/02\/10\/fancy-use-of-exception-handling-in-formatmessage-leads-to-repeated-discovery-of-security-flaw\/"},"modified":"2012-02-10T07:00:00","modified_gmt":"2012-02-10T07:00:00","slug":"fancy-use-of-exception-handling-in-formatmessage-leads-to-repeated-discovery-of-security-flaw","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20120210-00\/?p=8333","title":{"rendered":"Fancy use of exception handling in FormatMessage leads to repeated &#034;discovery&#034; of security flaw"},"content":{"rendered":"<p>\nEvery so often, somebody &#8220;discovers&#8221; an alleged\nsecurity vulnerability in the <code>Format&shy;Message<\/code> function.\nYou can try it yourself:\n<\/p>\n<pre>\n#include &lt;windows.h&gt;\n#include &lt;stdio.h&gt;\nchar buf[2048];\nchar extralong[128*1024];\nint __cdecl main(int argc, char **argv)\n{\n memset(extralong, 'x', 128 * 1024 - 1);\n DWORD_PTR args[] = { (DWORD_PTR)extralong };\n FormatMessage(FORMAT_MESSAGE_FROM_STRING |\n               FORMAT_MESSAGE_ARGUMENT_ARRAY, \"%1\", 0, 0,\n               buf, 2048, (va_list*)args);\n return 0;\n}\n<\/pre>\n<p>\nIf you run this program under the debugger and you tell it to break\non all exceptions,\nthen you will find that it breaks on an access violation\ntrying to write to an invalid address.\n<\/p>\n<pre>\neax=00060078 ebx=fffe0001 ecx=0006fa34 edx=00781000 esi=0006fa08 edi=01004330\neip=77f5b279 esp=0006f5ac ebp=0006fa1c iopl=0         nv up ei pl nz na pe cy\ncs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00010203\nntdll!fputwc+0x14:\n77f5b279 668902           mov     [edx],ax              ds:0023:00781000=????\n<\/pre>\n<p>\nDid you just find a buffer overflow security vulnerability?\n<\/p>\n<p>\nThe <code>FormatMessage<\/code> function was part of the original\nWin32 interface,\nback in the days when you had lots of address space\n(two whole <i>gigabytes<\/i>) but not a lot of RAM (12 megabytes,\nor 16 if you were running Server).\nThe implementation of <code>FormatMessage<\/code> reflects\nthis historical reality by\n<a HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2003\/10\/10\/55256.aspx\">\nworking hard to conserve RAM<\/a>\nbut not worrying too much about conserving address space.\nAnd it takes advantage of this fancy new <i>structured\nexception handling<\/i> feature.\n<\/p>\n<p>\nThe <code>FormatMessage<\/code> uses the\n<i>reserve a bunch of address space but commit pages only as they\nare necessary<\/i> pattern, illustrated in MSDN\nunder the topic\n<a HREF=\"http:\/\/msdn.microsoft.com\/library\/aa366803.aspx\">\nReserving and Committing Memory<\/a>.\nExcept that the sample code on that page contains serious errors.\nFor example, if the sample code encounters an exception other than\n<code>STATUS_ACCESS_VIOLATION<\/code>, it still &#8220;handles&#8221;\nit by doing nothing and returning\n<code>EXCEPTION_EXECUTE_HANDLER<\/code>.\nIt fails to handle random access to the buffer\nor access violations caused by DEP.\nThough in the very specific sample, it mostly works since the\nprotected region does only one thing, so there aren&#8217;t many\nopportunities for the other types of exceptions to occur.\n(Though if you&#8217;re really unlucky, you might get an\n<code>STATUS_IN_PAGE_ERROR<\/code>.)\nBut enough complaining about that sample.\n<\/p>\n<p>\nThe <code>FormatMessage<\/code>\nfunction reserves\n64<a HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2009\/06\/11\/9725386.aspx\">KB<\/a>\nof address space, commits the first page,\nand then calls an internal helper function whose job it is\nto generate the output,\npassing the start of the 64KB block of address space as the\nstarting address and telling it to give up when it reaches 64KB.\nSomething like this:\n<\/p>\n<pre>\nstruct DEMANDBUFFER\n{\n  void *Base;\n  SIZE_T Length;\n};\nint\nPageFaultExceptionFilter(DEMANDBUFFER *Buffer,\n                         EXCEPTION_RECORD ExceptionRecord)\n{\n  int Result;\n  <a HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2012\/02\/09\/10265660.aspx\">DWORD dwLastError = GetLastError();<\/a>\n  \/\/ The only exception we handle is a continuable read\/write\n  \/\/ access violation inside our demand-commit buffer.\n  if (ExceptionRecord-&gt;ExceptionFlags &amp; EXCEPTION_NONCONTINUABLE)\n    Result = EXCEPTION_CONTINUE_SEARCH;\n  else if (ExceptionRecord-&gt;ExceptionCode != EXCEPTION_ACCESS_VIOLATION)\n    Result = EXCEPTION_CONTINUE_SEARCH;\n  else if (ExceptionRecord-&gt;NumberParameters &lt; 2)\n    Result = EXCEPTION_CONTINUE_SEARCH;\n  else if (ExceptionRecord-&gt;ExceptionInformation[0] &amp;\n      ~(EXCEPTION_READ_FAULT | EXCEPTION_WRITE_FAULT))\n    Result = EXCEPTION_CONTINUE_SEARCH;\n  else if (ExceptionRecord-&gt;ExceptionInformation[1] -\n      (ULONG_PTR)Buffer-&gt;Base &gt;= Buffer-&gt;Length)\n    Result = EXCEPTION_CONTINUE_SEARCH;\n  else {\n    \/\/ If the memory is already committed, then committing memory won't help!\n    \/\/ (The problem is something like writing to a read-only page.)\n    void *ExceptionAddress = (void*)ExceptionInformation[1];\n    MEMORY_BASIC_INFORMATION Information;\n    if (VirtualQuery(ExceptionAddress, &amp;Information,\n                     sizeof(Information)) != sizeof(Information))\n      Result = EXCEPTION_CONTINUE_SEARCH;\n    else if (Information.State != MEM_RESERVE)\n      Result = EXCEPTION_CONTINUE_SEARCH;\n    \/\/ Okay, handle the exception by committing the page.\n    \/\/ Exercise: What happens if the faulting memory access\n    \/\/ spans two pages?\n    else if (!VirtualAlloc(ExceptionAddress, 1, MEM_COMMIT, PAGE_READWRITE))\n      Result = EXCEPTION_CONTINUE_SEARCH;\n    \/\/ We successfully committed the memory - retry the operation\n    else Result = EXCEPTION_CONTINUE_EXECUTION;\n  }\n  <a HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2011\/04\/29\/10159322.aspx\">RestoreLastError<\/a>(dwLastError);\n  return Result;\n}\nDWORD FormatMessage(...)\n{\n  DWORD Result = 0;\n  DWORD Error;\n  DEMANDBUFFER Buffer;\n  Error = InitializeDemandBuffer(&amp;Buffer, FORMATMESSAGE_MAXIMUM_OUTPUT);\n  if (Error == ERROR_SUCCESS) {\n    __try {\n     Error = FormatMessageIntoBuffer(&amp;Result,\n                                     Buffer.Base, Buffer.Length, ...);\n    } __except (PageFaultExceptionFilter(&amp;Buffer,\n                   GetExceptionInformation()-&gt;ExceptionRecord)) {\n     \/\/ never reached - we never handle the exception\n    }\n  }\n  if (Error == ERROR_SUCCESS) {\n   Error = CopyResultsOutOfBuffer(...);\n  }\n  DeleteDemandBuffer(&amp;Buffer);\n  if (Result == 0) {\n    SetLastError(Error);\n  }\n  return Result;\n}\n<\/pre>\n<p>\nThe <code>FormatMessageIntoBuffer<\/code> function takes an output\nbuffer and a buffer size, and it writes the result to the output buffer,\nstopping when the buffer is full.\nThe <code>DEMANDBUFFER<\/code> structure and the\n<code>PageFaultExceptionHandler<\/code>\nwork together to create the output buffer on demand\nas the <code>FormatMessageIntoBuffer<\/code> function does its work.\n<\/p>\n<p>\nTo make discussion easier, let&#8217;s say that the\n<code>FormatMessage<\/code> function merely took printf-style arguments\nand supported only\n<code>FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ALLOCATE_BUFFER<\/code>.\n<\/p>\n<pre>\nDWORD FormatMessageFromStringPrintfAllocateBuffer(\n    PWSTR *ResultBuffer,\n    PCWSTR FormatString,\n    ...)\n{\n  DWORD Result = 0;\n  DWORD ResultString = NULL;\n  DWORD Error;\n  DEMANDBUFFER Buffer;\n  va_list ap;\n  va_start(ap, FormatString);\n  Error = InitializeDemandBuffer(&amp;Buffer, FORMATMESSAGE_MAXIMUM_OUTPUT);\n  if (Error == ERROR_SUCCESS) {\n    __try {\n     SIZE_T MaxChars = Buffer.Length \/ sizeof(WCHAR);\n     int i = _vsnwprintf((WCHAR*)Buffer.Base, MaxChars,\n                         FormatString, ap);\n     if (i &lt; 0 || i &gt;= MaxChars) Error = ERROR_MORE_DATA;\n     else Result = i;\n    } __except (PageFaultExceptionFilter(&amp;Buffer,\n                   GetExceptionInformation()-&gt;ExceptionRecord)) {\n     \/\/ never reached - we never handle the exception\n    }\n  }\n  if (Error == ERROR_SUCCESS) {\n   \/\/ Exercise: Why don't we need to worry about integer overflow?\n   DWORD BytesNeeded = sizeof(WCHAR) * (Result + 1);\n   ResultString = (PWSTR)LocalAlloc(LMEM_FIXED, BytesNeeded);\n   if (ResultBuffer) {\n    \/\/ Exercise: Why CopyMemory and not StringCchCopy?\n    CopyMemory(ResultString, Buffer.Base, BytesNeeded);\n   } else Error = ERROR_NOT_ENOUGH_MEMORY;\n  }\n  DeleteDemandBuffer(&amp;Buffer);\n  if (Result == 0) {\n    SetLastError(Error);\n  }\n  *ResultBuffer = ResultString;\n  va_end(ap);\n  return Result;\n}\n<\/pre>\n<p>\nLet&#8217;s run this function in our head to see what happens if\nsomebody triggers the alleged buffer overflow by calling\n<\/p>\n<pre>\nPWSTR ResultString;\nDWORD Result = FormatMessageFromStringPrintfAllocateBuffer(\n                   &amp;ResultString, L\"%s\", VeryLongString);\n<\/pre>\n<p>\nAfter setting up the demand buffer, we call\n<code>_vsnwprintf<\/code>\nto format the output into the demand buffer,\nbut telling it not to go past the buffer&#8217;s total length.\nThe <code>_vsnwprintf<\/code> function parses the format\nstring and sees that it needs to copy <code>VeryLongString<\/code>\nto the output buffer.\nLet&#8217;s say that the <code>DEMANDBUFFER<\/code> was allocated at\naddress <code>0x00780000<\/code> on a system with 4KB pages.\nAt the start of the copy, the address space looks like this:\n<\/p>\n<table STYLE=\"border-collapse: collapse;text-align: center\">\n<tr>\n<td COLSPAN=\"16\" ALIGN=\"center\" STYLE=\"border-top: solid .75pt black;border-left: solid .75pt black;border-right: solid .75pt black\">64KB<\/td>\n<td><\/td>\n<\/tr>\n<tr>\n<td BGCOLOR=\"#FFFF80\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>0000<\/code><\/font><br \/>C<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>1000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>2000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>3000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>4000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>5000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>6000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>7000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>8000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>9000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>A000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>B000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>C000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>D000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>E000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>F000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0079<br \/>0000<\/code><\/font><br \/>X<\/td>\n<\/tr>\n<tr>\n<td COLSPAN=\"16\" ALIGN=\"left\">^ output pointer<\/td>\n<\/tr>\n<\/table>\n<p>\n&#8220;C&#8221; stands for a committed page, &#8220;R&#8221; stands for a reserved page,\nand &#8220;X&#8221; stands for a page that, if accessed, would be a buffer overflow.\nWe start copying <code>VeryLongString<\/code> into the output buffer.\nAfter copying 2048 characters, we fill the first committed page;\ncopying character 2049 raises a page fault exception.\n<\/p>\n<table STYLE=\"border-collapse: collapse;text-align: center\">\n<tr>\n<td COLSPAN=\"16\" ALIGN=\"center\" STYLE=\"border-top: solid .75pt black;border-left: solid .75pt black;border-right: solid .75pt black\">64KB<\/td>\n<td><\/td>\n<\/tr>\n<tr>\n<td BGCOLOR=\"#FFFF80\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>0000<\/code><\/font><br \/>C<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>1000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>2000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>3000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>4000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>5000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>6000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>7000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>8000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>9000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>A000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>B000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>C000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>D000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>E000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>F000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0079<br \/>0000<\/code><\/font><br \/>X<\/td>\n<\/tr>\n<tr>\n<td><\/td>\n<td COLSPAN=\"15\" ALIGN=\"left\">^ output pointer<\/td>\n<\/tr>\n<\/table>\n<p>\nThis is the point at which over-eager people observe the first-chance\nexception, capture the register dump above,\nand begin writing up their security vulnerability report,\ncackling with glee.\n(Observe that in the register dump,\nthe address we are writing to is of the form <code>0x####1000<\/code>.)\n<\/p>\n<p>\nAs with all first-chance exceptions,\nit goes down the exception chain.\nOur custom <code>PageFaultExceptionFilter<\/code> recognizes this\nas an access violation in a page that it is responsible for,\nand the page hasn&#8217;t yet been committed, so it commits the page as\nread\/write and resumes execution.\n<\/p>\n<table STYLE=\"border-collapse: collapse;text-align: center\">\n<tr>\n<td COLSPAN=\"16\" ALIGN=\"center\" STYLE=\"border-top: solid .75pt black;border-left: solid .75pt black;border-right: solid .75pt black\">64KB<\/td>\n<td><\/td>\n<\/tr>\n<tr>\n<td BGCOLOR=\"#FFFF80\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>0000<\/code><\/font><br \/>C<\/td>\n<td BGCOLOR=\"#FFFF80\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>1000<\/code><\/font><br \/>C<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>2000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>3000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>4000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>5000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>6000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>7000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>8000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>9000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>A000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>B000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>C000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>D000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>E000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>F000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0079<br \/>0000<\/code><\/font><br \/>X<\/td>\n<\/tr>\n<tr>\n<td><\/td>\n<td COLSPAN=\"15\" ALIGN=\"left\">^ output pointer<\/td>\n<\/tr>\n<\/table>\n<p>\nCopying character 2049 now succeeds, as does the copying of characters\n2050 through 4096.\nWhen we hit character 4097, the cycle repeats:\n<\/p>\n<table STYLE=\"border-collapse: collapse;text-align: center\">\n<tr>\n<td COLSPAN=\"16\" ALIGN=\"center\" STYLE=\"border-top: solid .75pt black;border-left: solid .75pt black;border-right: solid .75pt black\">64KB<\/td>\n<td><\/td>\n<\/tr>\n<tr>\n<td BGCOLOR=\"#FFFF80\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>0000<\/code><\/font><br \/>C<\/td>\n<td BGCOLOR=\"#FFFF80\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>1000<\/code><\/font><br \/>C<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>2000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>3000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>4000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>5000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>6000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>7000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>8000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>9000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>A000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>B000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>C000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>D000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>E000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>F000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0079<br \/>0000<\/code><\/font><br \/>X<\/td>\n<\/tr>\n<tr>\n<td COLSPAN=\"2\"><\/td>\n<td COLSPAN=\"14\" ALIGN=\"left\">^ output pointer<\/td>\n<\/tr>\n<\/table>\n<p>\nAgain, the first-chance exception is sent down the chain,\nour\n<code>PageFaultExceptionFilter<\/code>\nrecognizes this as a page it is responsible for,\nand it commits the page and resumes execution.\n<\/p>\n<table STYLE=\"border-collapse: collapse;text-align: center\">\n<tr>\n<td COLSPAN=\"16\" ALIGN=\"center\" STYLE=\"border-top: solid .75pt black;border-left: solid .75pt black;border-right: solid .75pt black\">64KB<\/td>\n<td><\/td>\n<\/tr>\n<tr>\n<td BGCOLOR=\"#FFFF80\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>0000<\/code><\/font><br \/>C<\/td>\n<td BGCOLOR=\"#FFFF80\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>1000<\/code><\/font><br \/>C<\/td>\n<td BGCOLOR=\"#FFFF80\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>2000<\/code><\/font><br \/>C<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>3000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>4000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>5000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>6000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>7000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>8000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>9000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>A000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>B000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>C000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>D000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>E000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>F000<\/code><\/font><br \/>R<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0079<br \/>0000<\/code><\/font><br \/>X<\/td>\n<\/tr>\n<tr>\n<td COLSPAN=\"2\"><\/td>\n<td COLSPAN=\"14\" ALIGN=\"left\">^ output pointer<\/td>\n<\/tr>\n<\/table>\n<p>\nIf you think about it, this is exactly what the memory manager does\nwith memory that has been allocated but not yet accessed:\nThe memory is not present,\nand the moment an application tries to access it,\nthe not-present page fault is raised,\nthe memory manager commits the page,\nand then execution resumes normally.\nIt&#8217;s memory-on-demand, which is one of the essential elements of\nvirtual memory.\nWhat&#8217;s going on with the <code>DEMANDBUFFER<\/code> is that we are\nsimulating in user mode what the memory manager does in kernel mode.\n(The difference is that while the memory manager takes committed\nmemory and makes it present on demand,\nthe <code>DEMANDBUFFER<\/code> takes reserved address space\nand commits it on demand.)\n<\/p>\n<p>\nThe cycle repeats 13 more times, and then we reach another interesting\npart of the scenario:\n<\/p>\n<table STYLE=\"border-collapse: collapse;text-align: center\">\n<tr>\n<td COLSPAN=\"16\" ALIGN=\"center\" STYLE=\"border-top: solid .75pt black;border-left: solid .75pt black;border-right: solid .75pt black\">64KB<\/td>\n<td><\/td>\n<\/tr>\n<tr>\n<td BGCOLOR=\"#FFFF80\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>0000<\/code><\/font><br \/>C<\/td>\n<td BGCOLOR=\"#FFFF80\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>1000<\/code><\/font><br \/>C<\/td>\n<td BGCOLOR=\"#FFFF80\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>2000<\/code><\/font><br \/>C<\/td>\n<td BGCOLOR=\"#FFFF80\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>3000<\/code><\/font><br \/>C<\/td>\n<td BGCOLOR=\"#FFFF80\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>4000<\/code><\/font><br \/>C<\/td>\n<td BGCOLOR=\"#FFFF80\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>5000<\/code><\/font><br \/>C<\/td>\n<td BGCOLOR=\"#FFFF80\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>6000<\/code><\/font><br \/>C<\/td>\n<td BGCOLOR=\"#FFFF80\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>7000<\/code><\/font><br \/>C<\/td>\n<td BGCOLOR=\"#FFFF80\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>8000<\/code><\/font><br \/>C<\/td>\n<td BGCOLOR=\"#FFFF80\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>9000<\/code><\/font><br \/>C<\/td>\n<td BGCOLOR=\"#FFFF80\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>A000<\/code><\/font><br \/>C<\/td>\n<td BGCOLOR=\"#FFFF80\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>B000<\/code><\/font><br \/>C<\/td>\n<td BGCOLOR=\"#FFFF80\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>C000<\/code><\/font><br \/>C<\/td>\n<td BGCOLOR=\"#FFFF80\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>D000<\/code><\/font><br \/>C<\/td>\n<td BGCOLOR=\"#FFFF80\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>E000<\/code><\/font><br \/>C<\/td>\n<td BGCOLOR=\"#FFFF80\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>F000<\/code><\/font><br \/>C<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0079<br \/>0000<\/code><\/font><br \/>X<\/td>\n<\/tr>\n<tr>\n<td COLSPAN=\"16\" ALIGN=\"right\">output pointer ^<\/td>\n<\/tr>\n<\/table>\n<p>\nWe are about to write 32768th character into the\n<code>DEMANDBUFFER<\/code>.\nOnce that&#8217;s done, the buffer will be completely full.\nOne more byte and we will overflow the buffer.\n(Not even a wafer-thin byte will fit.)\n<\/p>\n<p>\nLet&#8217;s write that last character and\n<a HREF=\"http:\/\/www.slipups.com\/items\/698.html\">\ncover our ears in anticipation<\/a>.\n<\/p>\n<table STYLE=\"border-collapse: collapse;text-align: center\">\n<tr>\n<td COLSPAN=\"16\" ALIGN=\"center\" STYLE=\"border-top: solid .75pt black;border-left: solid .75pt black;border-right: solid .75pt black\">64KB<\/td>\n<td><\/td>\n<\/tr>\n<tr>\n<td BGCOLOR=\"#FFFF80\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>0000<\/code><\/font><br \/>C<\/td>\n<td BGCOLOR=\"#FFFF80\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>1000<\/code><\/font><br \/>C<\/td>\n<td BGCOLOR=\"#FFFF80\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>2000<\/code><\/font><br \/>C<\/td>\n<td BGCOLOR=\"#FFFF80\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>3000<\/code><\/font><br \/>C<\/td>\n<td BGCOLOR=\"#FFFF80\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>4000<\/code><\/font><br \/>C<\/td>\n<td BGCOLOR=\"#FFFF80\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>5000<\/code><\/font><br \/>C<\/td>\n<td BGCOLOR=\"#FFFF80\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>6000<\/code><\/font><br \/>C<\/td>\n<td BGCOLOR=\"#FFFF80\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>7000<\/code><\/font><br \/>C<\/td>\n<td BGCOLOR=\"#FFFF80\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>8000<\/code><\/font><br \/>C<\/td>\n<td BGCOLOR=\"#FFFF80\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>9000<\/code><\/font><br \/>C<\/td>\n<td BGCOLOR=\"#FFFF80\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>A000<\/code><\/font><br \/>C<\/td>\n<td BGCOLOR=\"#FFFF80\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>B000<\/code><\/font><br \/>C<\/td>\n<td BGCOLOR=\"#FFFF80\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>C000<\/code><\/font><br \/>C<\/td>\n<td BGCOLOR=\"#FFFF80\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>D000<\/code><\/font><br \/>C<\/td>\n<td BGCOLOR=\"#FFFF80\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>E000<\/code><\/font><br \/>C<\/td>\n<td BGCOLOR=\"#FFFF80\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0078<br \/>F000<\/code><\/font><br \/>C<\/td>\n<td BGCOLOR=\"#808080\" STYLE=\"width: 5%;border: .75pt solid black\"><font SIZE=\"-1\"><code>0079<br \/>0000<\/code><\/font><br \/>X<\/td>\n<\/tr>\n<tr>\n<td COLSPAN=\"16\" align=\"right\">output pointer<\/td>\n<td ALIGN=\"left\">^<\/td>\n<\/tr>\n<\/table>\n<p>\nOh noes!\nCompletely full!\nRun for cover!\n<\/p>\n<p>\nBut wait.\nWe passed a buffer size to the\n<code>_vsnwprintf<\/code> function, remember?\nWe already told it never to write more than 32768 characters.\nAs it&#8217;s about to write character 32769, it realizes,\n&#8220;Wait a second, this would overflow the buffer I was given.\nI&#8217;ll return a failure code instead.&#8221;\n<\/p>\n<p>\nThe feared write of the 32769th character never takes place.\nWe never write to the &#8220;X&#8221; page.\nInstead, the <code>_vnswprintf<\/code> call returns that the\nbuffer was not large enough, which is converted into\n<code>ERROR_MORE_DATA<\/code> and returned to the caller.\n<\/p>\n<p>\nIf you follow through the entire story, you see that everything\nworked as it was supposed to and no overflow took place.\nThe <code>_vnswprintf<\/code> function ran up to the brink of\ndisaster but stopped before taking that last step.\nThis is hardly anything surprising; it happens whenever\nthe <code>_vnswprintf<\/code> function encounters a buffer\ntoo small to hold the output.\nThe only difference is that along the way, we saw a few\nfirst-chance exceptions,\nexceptions that had nothing to do with avoiding the buffer\noverflow in the first place.\nThey were just part of <code>FormatMessage<\/code>&#8216;s\nfancy buffer management.\n<\/p>\n<p>\nIt so happens that in Windows Vista,\nthe fancy buffer management technique was abandoned, and\nthe code just allocates 64KB of memory up front and doesn&#8217;t\ntry any fancy commit-on-demand games.\nComputer memory has become plentiful enough that a momentary allocation\nof 64KB has less of an impact than it did twenty years ago,\nand performance measurements showed that the new\n&#8220;Stop trying to be so clever&#8221; technique was now about 80 times\nfaster than the &#8220;gotta scrimp and save every last byte of memory&#8221;\ntechnique.\n<\/p>\n<p>\nThe change had more than just a performance effect.\nIt also removed the first-chance exception from <code>FormatMessage<\/code>,\nwhich means that it no longer does that thing which everybody\nmistakes for a security vulnerability.\nThe good news is that nobody reports this as a vulnerability\nin Windows&nbsp;Vista any more.\nThe bad news is that people still report it as a vulnerability\nin Windows&nbsp;XP,\nand each\ntime this issue comes up,\nsomebody (possibly me) has to sit down and\n<a HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2007\/12\/18\/6793468.aspx\">\nreverify that the previous analysis is still correct<\/a>,\nin the specific scenario being reported,\nbecause who knows, maybe this time they really did find a problem.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Every so often, somebody &#8220;discovers&#8221; an alleged security vulnerability in the Format&shy;Message function. You can try it yourself: #include &lt;windows.h&gt; #include &lt;stdio.h&gt; char buf[2048]; char extralong[128*1024]; int __cdecl main(int argc, char **argv) { memset(extralong, &#8216;x&#8217;, 128 * 1024 &#8211; 1); DWORD_PTR args[] = { (DWORD_PTR)extralong }; FormatMessage(FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ARGUMENT_ARRAY, &#8220;%1&#8221;, 0, 0, buf, 2048, (va_list*)args); [&hellip;]<\/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-8333","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Every so often, somebody &#8220;discovers&#8221; an alleged security vulnerability in the Format&shy;Message function. You can try it yourself: #include &lt;windows.h&gt; #include &lt;stdio.h&gt; char buf[2048]; char extralong[128*1024]; int __cdecl main(int argc, char **argv) { memset(extralong, &#8216;x&#8217;, 128 * 1024 &#8211; 1); DWORD_PTR args[] = { (DWORD_PTR)extralong }; FormatMessage(FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ARGUMENT_ARRAY, &#8220;%1&#8221;, 0, 0, buf, 2048, (va_list*)args); [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/8333","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=8333"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/8333\/revisions"}],"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=8333"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=8333"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=8333"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}