{"id":107902,"date":"2023-03-06T07:00:00","date_gmt":"2023-03-06T15:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=107902"},"modified":"2023-03-06T07:16:51","modified_gmt":"2023-03-06T15:16:51","slug":"20230306-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20230306-00\/?p=107902","title":{"rendered":"I can create a read-only page, but why not a write-only page?"},"content":{"rendered":"<p>There is an interesting hole in the diagram of page protections supported by the <code>Virtual\u00adAlloc<\/code> function:<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse;\" border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<th>\u00a0<\/th>\n<th>Deny write<\/th>\n<th>Allow write<\/th>\n<\/tr>\n<tr>\n<th>Deny read<\/th>\n<td><code>PAGE_<wbr \/>NO\u00adACCESS<\/code><\/td>\n<td align=\"CENTER\">???<\/td>\n<\/tr>\n<tr>\n<th>Allow read<\/th>\n<td><code>PAGE_<wbr \/>READ\u00adONLY<\/code><\/td>\n<td><code>PAGE_<wbr \/>READ\u00adWRITE<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>The missing value is <code>PAGE_<wbr \/>WRITE\u00adONLY<\/code>. Why is there no <code>PAGE_<wbr \/>WRITE\u00adONLY<\/code>?<\/p>\n<p>The short answer is &#8220;Because no processor supports it.&#8221;<\/p>\n<p>The page protections in most processors are not three bits, one for read, one for write, and one for execute. Different processors do things differently.\u00b9 Typically, they start with a <i>valid<\/i> bit, which says whether any access is allowed at all. If the <i>valid<\/i> bit is not set, then you immediately get <code>PAGE_<wbr \/>NO\u00adACCESS<\/code>, and the rest of the page table entry is ignored.\u00b2 If the <i>valid<\/i> bit is set, then other bits are used to configure the details of the protection. One way is to have separate bits for controlling write and execute access independently.\u00b3 Another is to take a few bits and treat them as an enumeration which selects from a list of possible page protection combinations, and &#8220;write-only&#8221; is not on the list of possibilities. Either way, there is no separate <var>read<\/var> bit; the <var>read<\/var> bit overloaded onto the <var>valid<\/var> bit: Every valid page is implicitly readable.<\/p>\n<p>Naturally, if no processors support an operation, there&#8217;s no point adding a flag for it to the operating system. &#8220;Hi, here&#8217;s a flag that is not supported by anyone. If you set it, the operation always fails. Good luck with that!&#8221;<\/p>\n<p>It&#8217;s also unclear how write-only pages would work anyway. CPUs nowadays typically do not issue precise writes. Rather, when you issue a write, the CPU loads the entire enclosing cache line, modifies the written bytes, and then writes the updated cache line back to memory (usually lazily). If the page were write-only, then the attempt to load the enclosing cache line would fail with an access violation,\u2074 and you&#8217;d never get around to writing the updated cache line.<\/p>\n<p>\u00b9 For the purpose of this discussion, we&#8217;re looking only at user mode access.<\/p>\n<p>\u00b2 Operating systems often use the ignored bits to record information about the invalid page, so that if a page fault occurs, the operating system can quickly look up what it should do next. For example, the unused bits could be a pointer to a kernel data structure that says, &#8220;If somebody tries to access this page, then instead of raising an access violation, try to page the data in from the page file. The data is stored in frame <var>N<\/var>. After you read the data, change the protection to <code>PAGE_<wbr \/>READ\u00adONLY<\/code> and restart the instruction.&#8221;<\/p>\n<p>\u00b3 On some processors, the control is done by setting the bit to allow access. On other processors, setting the bit denies access.<\/p>\n<p>\u2074 Alternatively, the CPU might load the cache line, bypassing the write-only protection, but try to prevent the code from observing any of the values that were loaded into that cache line. This could end up vulnerable to a side-channel attack.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>At the end of the day, it comes down to processor support.<\/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-107902","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>At the end of the day, it comes down to processor support.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/107902","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=107902"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/107902\/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=107902"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=107902"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=107902"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}