CINXE.COM
Project Zero: December 2020
<!DOCTYPE html> <html class='v2' dir='ltr' lang='en' xmlns='http://www.w3.org/1999/xhtml' xmlns:b='http://www.google.com/2005/gml/b' xmlns:data='http://www.google.com/2005/gml/data' xmlns:expr='http://www.google.com/2005/gml/expr'> <head> <link href='https://www.blogger.com/static/v1/widgets/3566091532-css_bundle_v2.css' rel='stylesheet' type='text/css'/> <meta content='width=1100' name='viewport'/> <meta content='text/html; charset=UTF-8' http-equiv='Content-Type'/> <meta content='blogger' name='generator'/> <link href='https://googleprojectzero.blogspot.com/favicon.ico' rel='icon' type='image/x-icon'/> <link href='https://googleprojectzero.blogspot.com/2020/12/' rel='canonical'/> <link rel="alternate" type="application/atom+xml" title="Project Zero - Atom" href="https://googleprojectzero.blogspot.com/feeds/posts/default" /> <link rel="alternate" type="application/rss+xml" title="Project Zero - RSS" href="https://googleprojectzero.blogspot.com/feeds/posts/default?alt=rss" /> <link rel="service.post" type="application/atom+xml" title="Project Zero - Atom" href="https://www.blogger.com/feeds/4838136820032157985/posts/default" /> <!--Can't find substitution for tag [blog.ieCssRetrofitLinks]--> <meta content='https://googleprojectzero.blogspot.com/2020/12/' property='og:url'/> <meta content='Project Zero' property='og:title'/> <meta content='News and updates from the Project Zero team at Google' property='og:description'/> <title>Project Zero: December 2020</title> <style type='text/css'>@font-face{font-family:'Open Sans';font-style:normal;font-weight:400;font-stretch:normal;font-display:swap;src:url(//fonts.gstatic.com/s/opensans/v40/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsjZ0B4gaVY.eot);}</style> <style id='page-skin-1' type='text/css'><!-- /* ----------------------------------------------- Blogger Template Style Name: Simple Designer: Blogger URL: www.blogger.com ----------------------------------------------- */ /* Variable definitions ==================== <Variable name="keycolor" description="Main Color" type="color" default="#66bbdd"/> <Group description="Page Text" selector="body"> <Variable name="body.font" description="Font" type="font" default="normal normal 12px Arial, Tahoma, Helvetica, FreeSans, sans-serif"/> <Variable name="body.text.color" description="Text Color" type="color" default="#222222"/> </Group> <Group description="Backgrounds" selector=".body-fauxcolumns-outer"> <Variable name="body.background.color" description="Outer Background" type="color" default="#66bbdd"/> <Variable name="content.background.color" description="Main Background" type="color" default="#ffffff"/> <Variable name="header.background.color" description="Header Background" type="color" default="transparent"/> </Group> <Group description="Links" selector=".main-outer"> <Variable name="link.color" description="Link Color" type="color" default="#2288bb"/> <Variable name="link.visited.color" description="Visited Color" type="color" default="#888888"/> <Variable name="link.hover.color" description="Hover Color" type="color" default="#33aaff"/> </Group> <Group description="Blog Title" selector=".header h1"> <Variable name="header.font" description="Font" type="font" default="normal normal 60px Arial, Tahoma, Helvetica, FreeSans, sans-serif"/> <Variable name="header.text.color" description="Title Color" type="color" default="#3399bb" /> </Group> <Group description="Blog Description" selector=".header .description"> <Variable name="description.text.color" description="Description Color" type="color" default="#777777" /> </Group> <Group description="Tabs Text" selector=".tabs-inner .widget li a"> <Variable name="tabs.font" description="Font" type="font" default="normal normal 14px Arial, Tahoma, Helvetica, FreeSans, sans-serif"/> <Variable name="tabs.text.color" description="Text Color" type="color" default="#999999"/> <Variable name="tabs.selected.text.color" description="Selected Color" type="color" default="#000000"/> </Group> <Group description="Tabs Background" selector=".tabs-outer .PageList"> <Variable name="tabs.background.color" description="Background Color" type="color" default="#f5f5f5"/> <Variable name="tabs.selected.background.color" description="Selected Color" type="color" default="#eeeeee"/> </Group> <Group description="Post Title" selector="h3.post-title, .comments h4"> <Variable name="post.title.font" description="Font" type="font" default="normal normal 22px Arial, Tahoma, Helvetica, FreeSans, sans-serif"/> </Group> <Group description="Date Header" selector=".date-header"> <Variable name="date.header.color" description="Text Color" type="color" default="#000000"/> <Variable name="date.header.background.color" description="Background Color" type="color" default="transparent"/> <Variable name="date.header.font" description="Text Font" type="font" default="normal bold 11px Arial, Tahoma, Helvetica, FreeSans, sans-serif"/> <Variable name="date.header.padding" description="Date Header Padding" type="string" default="inherit"/> <Variable name="date.header.letterspacing" description="Date Header Letter Spacing" type="string" default="inherit"/> <Variable name="date.header.margin" description="Date Header Margin" type="string" default="inherit"/> </Group> <Group description="Post Footer" selector=".post-footer"> <Variable name="post.footer.text.color" description="Text Color" type="color" default="#666666"/> <Variable name="post.footer.background.color" description="Background Color" type="color" default="#f9f9f9"/> <Variable name="post.footer.border.color" description="Shadow Color" type="color" default="#eeeeee"/> </Group> <Group description="Gadgets" selector="h2"> <Variable name="widget.title.font" description="Title Font" type="font" default="normal bold 11px Arial, Tahoma, Helvetica, FreeSans, sans-serif"/> <Variable name="widget.title.text.color" description="Title Color" type="color" default="#000000"/> <Variable name="widget.alternate.text.color" description="Alternate Color" type="color" default="#999999"/> </Group> <Group description="Images" selector=".main-inner"> <Variable name="image.background.color" description="Background Color" type="color" default="#ffffff"/> <Variable name="image.border.color" description="Border Color" type="color" default="#eeeeee"/> <Variable name="image.text.color" description="Caption Text Color" type="color" default="#000000"/> </Group> <Group description="Accents" selector=".content-inner"> <Variable name="body.rule.color" description="Separator Line Color" type="color" default="#eeeeee"/> <Variable name="tabs.border.color" description="Tabs Border Color" type="color" default="transparent"/> </Group> <Variable name="body.background" description="Body Background" type="background" color="#eeeeee" default="$(color) none repeat scroll top left"/> <Variable name="body.background.override" description="Body Background Override" type="string" default=""/> <Variable name="body.background.gradient.cap" description="Body Gradient Cap" type="url" default="url(https://resources.blogblog.com/blogblog/data/1kt/simple/gradients_light.png)"/> <Variable name="body.background.gradient.tile" description="Body Gradient Tile" type="url" default="url(https://resources.blogblog.com/blogblog/data/1kt/simple/body_gradient_tile_light.png)"/> <Variable name="content.background.color.selector" description="Content Background Color Selector" type="string" default=".content-inner"/> <Variable name="content.padding" description="Content Padding" type="length" default="10px" min="0" max="100px"/> <Variable name="content.padding.horizontal" description="Content Horizontal Padding" type="length" default="10px" min="0" max="100px"/> <Variable name="content.shadow.spread" description="Content Shadow Spread" type="length" default="40px" min="0" max="100px"/> <Variable name="content.shadow.spread.webkit" description="Content Shadow Spread (WebKit)" type="length" default="5px" min="0" max="100px"/> <Variable name="content.shadow.spread.ie" description="Content Shadow Spread (IE)" type="length" default="10px" min="0" max="100px"/> <Variable name="main.border.width" description="Main Border Width" type="length" default="0" min="0" max="10px"/> <Variable name="header.background.gradient" description="Header Gradient" type="url" default="none"/> <Variable name="header.shadow.offset.left" description="Header Shadow Offset Left" type="length" default="-1px" min="-50px" max="50px"/> <Variable name="header.shadow.offset.top" description="Header Shadow Offset Top" type="length" default="-1px" min="-50px" max="50px"/> <Variable name="header.shadow.spread" description="Header Shadow Spread" type="length" default="1px" min="0" max="100px"/> <Variable name="header.padding" description="Header Padding" type="length" default="30px" min="0" max="100px"/> <Variable name="header.border.size" description="Header Border Size" type="length" default="1px" min="0" max="10px"/> <Variable name="header.bottom.border.size" description="Header Bottom Border Size" type="length" default="1px" min="0" max="10px"/> <Variable name="header.border.horizontalsize" description="Header Horizontal Border Size" type="length" default="0" min="0" max="10px"/> <Variable name="description.text.size" description="Description Text Size" type="string" default="140%"/> <Variable name="tabs.margin.top" description="Tabs Margin Top" type="length" default="0" min="0" max="100px"/> <Variable name="tabs.margin.side" description="Tabs Side Margin" type="length" default="30px" min="0" max="100px"/> <Variable name="tabs.background.gradient" description="Tabs Background Gradient" type="url" default="url(https://resources.blogblog.com/blogblog/data/1kt/simple/gradients_light.png)"/> <Variable name="tabs.border.width" description="Tabs Border Width" type="length" default="1px" min="0" max="10px"/> <Variable name="tabs.bevel.border.width" description="Tabs Bevel Border Width" type="length" default="1px" min="0" max="10px"/> <Variable name="post.margin.bottom" description="Post Bottom Margin" type="length" default="25px" min="0" max="100px"/> <Variable name="image.border.small.size" description="Image Border Small Size" type="length" default="2px" min="0" max="10px"/> <Variable name="image.border.large.size" description="Image Border Large Size" type="length" default="5px" min="0" max="10px"/> <Variable name="page.width.selector" description="Page Width Selector" type="string" default=".region-inner"/> <Variable name="page.width" description="Page Width" type="string" default="auto"/> <Variable name="main.section.margin" description="Main Section Margin" type="length" default="15px" min="0" max="100px"/> <Variable name="main.padding" description="Main Padding" type="length" default="15px" min="0" max="100px"/> <Variable name="main.padding.top" description="Main Padding Top" type="length" default="30px" min="0" max="100px"/> <Variable name="main.padding.bottom" description="Main Padding Bottom" type="length" default="30px" min="0" max="100px"/> <Variable name="paging.background" color="#ffffff" description="Background of blog paging area" type="background" default="transparent none no-repeat scroll top center"/> <Variable name="footer.bevel" description="Bevel border length of footer" type="length" default="0" min="0" max="10px"/> <Variable name="mobile.background.overlay" description="Mobile Background Overlay" type="string" default="transparent none repeat scroll top left"/> <Variable name="mobile.background.size" description="Mobile Background Size" type="string" default="auto"/> <Variable name="mobile.button.color" description="Mobile Button Color" type="color" default="#ffffff" /> <Variable name="startSide" description="Side where text starts in blog language" type="automatic" default="left"/> <Variable name="endSide" description="Side where text ends in blog language" type="automatic" default="right"/> */ /* Content ----------------------------------------------- */ body { font: normal normal 12px Open Sans; color: #000000; background: #eeeeee none repeat scroll top left; padding: 0 0 0 0; } html body .region-inner { min-width: 0; max-width: 100%; width: auto; } h2 { font-size: 22px; } a:link { text-decoration:none; color: #2288bb; } a:visited { text-decoration:none; color: #888888; } a:hover { text-decoration:underline; color: #33aaff; } .body-fauxcolumn-outer .fauxcolumn-inner { background: transparent none repeat scroll top left; _background-image: none; } .body-fauxcolumn-outer .cap-top { position: absolute; z-index: 1; height: 400px; width: 100%; } .body-fauxcolumn-outer .cap-top .cap-left { width: 100%; background: transparent none repeat-x scroll top left; _background-image: none; } .content-outer { -moz-box-shadow: 0 0 0 rgba(0, 0, 0, .15); -webkit-box-shadow: 0 0 0 rgba(0, 0, 0, .15); -goog-ms-box-shadow: 0 0 0 #333333; box-shadow: 0 0 0 rgba(0, 0, 0, .15); margin-bottom: 1px; } .content-inner { padding: 10px 40px; } .content-inner { background-color: #ffffff; } /* Header ----------------------------------------------- */ .header-outer { background: transparent none repeat-x scroll 0 -400px; _background-image: none; } .Header h1 { font: normal normal 40px Open Sans; color: #000000; text-shadow: 0 0 0 rgba(0, 0, 0, .2); } .Header h1 a { color: #000000; } .Header .description { font-size: 18px; color: #000000; } .header-inner .Header .titlewrapper { padding: 22px 0; } .header-inner .Header .descriptionwrapper { padding: 0 0; } /* Tabs ----------------------------------------------- */ .tabs-inner .section:first-child { border-top: 0 solid #dddddd; } .tabs-inner .section:first-child ul { margin-top: -1px; border-top: 1px solid #dddddd; border-left: 1px solid #dddddd; border-right: 1px solid #dddddd; } .tabs-inner .widget ul { background: transparent none repeat-x scroll 0 -800px; _background-image: none; border-bottom: 1px solid #dddddd; margin-top: 0; margin-left: -30px; margin-right: -30px; } .tabs-inner .widget li a { display: inline-block; padding: .6em 1em; font: normal normal 12px Open Sans; color: #000000; border-left: 1px solid #ffffff; border-right: 1px solid #dddddd; } .tabs-inner .widget li:first-child a { border-left: none; } .tabs-inner .widget li.selected a, .tabs-inner .widget li a:hover { color: #000000; background-color: #eeeeee; text-decoration: none; } /* Columns ----------------------------------------------- */ .main-outer { border-top: 0 solid transparent; } .fauxcolumn-left-outer .fauxcolumn-inner { border-right: 1px solid transparent; } .fauxcolumn-right-outer .fauxcolumn-inner { border-left: 1px solid transparent; } /* Headings ----------------------------------------------- */ div.widget > h2, div.widget h2.title { margin: 0 0 1em 0; font: normal bold 11px 'Trebuchet MS',Trebuchet,Verdana,sans-serif; color: #000000; } /* Widgets ----------------------------------------------- */ .widget .zippy { color: #999999; text-shadow: 2px 2px 1px rgba(0, 0, 0, .1); } .widget .popular-posts ul { list-style: none; } /* Posts ----------------------------------------------- */ h2.date-header { font: normal bold 11px Arial, Tahoma, Helvetica, FreeSans, sans-serif; } .date-header span { background-color: #bbbbbb; color: #ffffff; padding: 0.4em; letter-spacing: 3px; margin: inherit; } .main-inner { padding-top: 35px; padding-bottom: 65px; } .main-inner .column-center-inner { padding: 0 0; } .main-inner .column-center-inner .section { margin: 0 1em; } .post { margin: 0 0 45px 0; } h3.post-title, .comments h4 { font: normal normal 22px Open Sans; margin: .75em 0 0; } .post-body { font-size: 110%; line-height: 1.4; position: relative; } .post-body img, .post-body .tr-caption-container, .Profile img, .Image img, .BlogList .item-thumbnail img { padding: 2px; background: #ffffff; border: 1px solid #eeeeee; -moz-box-shadow: 1px 1px 5px rgba(0, 0, 0, .1); -webkit-box-shadow: 1px 1px 5px rgba(0, 0, 0, .1); box-shadow: 1px 1px 5px rgba(0, 0, 0, .1); } .post-body img, .post-body .tr-caption-container { padding: 5px; } .post-body .tr-caption-container { color: #666666; } .post-body .tr-caption-container img { padding: 0; background: transparent; border: none; -moz-box-shadow: 0 0 0 rgba(0, 0, 0, .1); -webkit-box-shadow: 0 0 0 rgba(0, 0, 0, .1); box-shadow: 0 0 0 rgba(0, 0, 0, .1); } .post-header { margin: 0 0 1.5em; line-height: 1.6; font-size: 90%; } .post-footer { margin: 20px -2px 0; padding: 5px 10px; color: #666666; background-color: #eeeeee; border-bottom: 1px solid #eeeeee; line-height: 1.6; font-size: 90%; } #comments .comment-author { padding-top: 1.5em; border-top: 1px solid transparent; background-position: 0 1.5em; } #comments .comment-author:first-child { padding-top: 0; border-top: none; } .avatar-image-container { margin: .2em 0 0; } #comments .avatar-image-container img { border: 1px solid #eeeeee; } /* Comments ----------------------------------------------- */ .comments .comments-content .icon.blog-author { background-repeat: no-repeat; background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEgAACxIB0t1+/AAAAAd0SU1FB9sLFwMeCjjhcOMAAAD+SURBVDjLtZSvTgNBEIe/WRRnm3U8RC1neQdsm1zSBIU9VVF1FkUguQQsD9ITmD7ECZIJSE4OZo9stoVjC/zc7ky+zH9hXwVwDpTAWWLrgS3QAe8AZgaAJI5zYAmc8r0G4AHYHQKVwII8PZrZFsBFkeRCABYiMh9BRUhnSkPTNCtVXYXURi1FpBDgArj8QU1eVXUzfnjv7yP7kwu1mYrkWlU33vs1QNu2qU8pwN0UpKoqokjWwCztrMuBhEhmh8bD5UDqur75asbcX0BGUB9/HAMB+r32hznJgXy2v0sGLBcyAJ1EK3LFcbo1s91JeLwAbwGYu7TP/3ZGfnXYPgAVNngtqatUNgAAAABJRU5ErkJggg==); } .comments .comments-content .loadmore a { border-top: 1px solid #999999; border-bottom: 1px solid #999999; } .comments .comment-thread.inline-thread { background-color: #eeeeee; } .comments .continue { border-top: 2px solid #999999; } /* Accents ---------------------------------------------- */ .section-columns td.columns-cell { border-left: 1px solid transparent; } .blog-pager { background: transparent url(//www.blogblog.com/1kt/simple/paging_dot.png) repeat-x scroll top center; } .blog-pager-older-link, .home-link, .blog-pager-newer-link { background-color: #ffffff; padding: 5px; } .footer-outer { border-top: 1px dashed #bbbbbb; } /* Mobile ----------------------------------------------- */ body.mobile { background-size: auto; } .mobile .body-fauxcolumn-outer { background: transparent none repeat scroll top left; } .mobile .body-fauxcolumn-outer .cap-top { background-size: 100% auto; } .mobile .content-outer { -webkit-box-shadow: 0 0 3px rgba(0, 0, 0, .15); box-shadow: 0 0 3px rgba(0, 0, 0, .15); } .mobile .tabs-inner .widget ul { margin-left: 0; margin-right: 0; } .mobile .post { margin: 0; } .mobile .main-inner .column-center-inner .section { margin: 0; } .mobile .date-header span { padding: 0.1em 10px; margin: 0 -10px; } .mobile h3.post-title { margin: 0; } .mobile .blog-pager { background: transparent none no-repeat scroll top center; } .mobile .footer-outer { border-top: none; } .mobile .main-inner, .mobile .footer-inner { background-color: #ffffff; } .mobile-index-contents { color: #000000; } .mobile-link-button { background-color: #2288bb; } .mobile-link-button a:link, .mobile-link-button a:visited { color: #ffffff; } .mobile .tabs-inner .section:first-child { border-top: none; } .mobile .tabs-inner .PageList .widget-content { background-color: #eeeeee; color: #000000; border-top: 1px solid #dddddd; border-bottom: 1px solid #dddddd; } .mobile .tabs-inner .PageList .widget-content .pagelist-arrow { border-left: 1px solid #dddddd; } --></style> <style id='template-skin-1' type='text/css'><!-- body { min-width: 1120px; } .content-outer, .content-fauxcolumn-outer, .region-inner { min-width: 1120px; max-width: 1120px; _width: 1120px; } .main-inner .columns { padding-left: 0; padding-right: 310px; } .main-inner .fauxcolumn-center-outer { left: 0; right: 310px; /* IE6 does not respect left and right together */ _width: expression(this.parentNode.offsetWidth - parseInt("0") - parseInt("310px") + 'px'); } .main-inner .fauxcolumn-left-outer { width: 0; } .main-inner .fauxcolumn-right-outer { width: 310px; } .main-inner .column-left-outer { width: 0; right: 100%; margin-left: -0; } .main-inner .column-right-outer { width: 310px; margin-right: -310px; } #layout { min-width: 0; } #layout .content-outer { min-width: 0; width: 800px; } #layout .region-inner { min-width: 0; width: auto; } body#layout div.add_widget { padding: 8px; } body#layout div.add_widget a { margin-left: 32px; } --></style> <script type='text/javascript'> (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); ga('create', 'UA-240546891-1', 'auto', 'blogger'); ga('blogger.send', 'pageview'); </script> <link href='https://www.blogger.com/dyn-css/authorization.css?targetBlogID=4838136820032157985&zx=89189630-7e30-43b5-91d3-8fdab32d43bc' media='none' onload='if(media!='all')media='all'' rel='stylesheet'/><noscript><link href='https://www.blogger.com/dyn-css/authorization.css?targetBlogID=4838136820032157985&zx=89189630-7e30-43b5-91d3-8fdab32d43bc' rel='stylesheet'/></noscript> <meta name='google-adsense-platform-account' content='ca-host-pub-1556223355139109'/> <meta name='google-adsense-platform-domain' content='blogspot.com'/> </head> <body class='loading'> <div class='navbar section' id='navbar' name='Navbar'><div class='widget Navbar' data-version='1' id='Navbar1'><script type="text/javascript"> function setAttributeOnload(object, attribute, val) { if(window.addEventListener) { window.addEventListener('load', function(){ object[attribute] = val; }, false); } else { window.attachEvent('onload', function(){ object[attribute] = val; }); } } </script> <div id="navbar-iframe-container"></div> <script type="text/javascript" src="https://apis.google.com/js/platform.js"></script> <script type="text/javascript"> gapi.load("gapi.iframes:gapi.iframes.style.bubble", function() { if (gapi.iframes && gapi.iframes.getContext) { gapi.iframes.getContext().openChild({ url: 'https://www.blogger.com/navbar.g?targetBlogID\x3d4838136820032157985\x26blogName\x3dProject+Zero\x26publishMode\x3dPUBLISH_MODE_BLOGSPOT\x26navbarType\x3dLIGHT\x26layoutType\x3dLAYOUTS\x26searchRoot\x3dhttps://googleprojectzero.blogspot.com/search\x26blogLocale\x3den\x26v\x3d2\x26homepageUrl\x3dhttps://googleprojectzero.blogspot.com/\x26vt\x3d7568236161501195533', where: document.getElementById("navbar-iframe-container"), id: "navbar-iframe", messageHandlersFilter: gapi.iframes.CROSS_ORIGIN_IFRAMES_FILTER, messageHandlers: { 'blogger-ping': function() {} } }); } }); </script><script type="text/javascript"> (function() { var script = document.createElement('script'); script.type = 'text/javascript'; script.src = '//pagead2.googlesyndication.com/pagead/js/google_top_exp.js'; var head = document.getElementsByTagName('head')[0]; if (head) { head.appendChild(script); }})(); </script> </div></div> <div class='body-fauxcolumns'> <div class='fauxcolumn-outer body-fauxcolumn-outer'> <div class='cap-top'> <div class='cap-left'></div> <div class='cap-right'></div> </div> <div class='fauxborder-left'> <div class='fauxborder-right'></div> <div class='fauxcolumn-inner'> </div> </div> <div class='cap-bottom'> <div class='cap-left'></div> <div class='cap-right'></div> </div> </div> </div> <div class='content'> <div class='content-fauxcolumns'> <div class='fauxcolumn-outer content-fauxcolumn-outer'> <div class='cap-top'> <div class='cap-left'></div> <div class='cap-right'></div> </div> <div class='fauxborder-left'> <div class='fauxborder-right'></div> <div class='fauxcolumn-inner'> </div> </div> <div class='cap-bottom'> <div class='cap-left'></div> <div class='cap-right'></div> </div> </div> </div> <div class='content-outer'> <div class='content-cap-top cap-top'> <div class='cap-left'></div> <div class='cap-right'></div> </div> <div class='fauxborder-left content-fauxborder-left'> <div class='fauxborder-right content-fauxborder-right'></div> <div class='content-inner'> <header> <div class='header-outer'> <div class='header-cap-top cap-top'> <div class='cap-left'></div> <div class='cap-right'></div> </div> <div class='fauxborder-left header-fauxborder-left'> <div class='fauxborder-right header-fauxborder-right'></div> <div class='region-inner header-inner'> <div class='header section' id='header' name='Header'><div class='widget Header' data-version='1' id='Header1'> <div id='header-inner'> <div class='titlewrapper'> <h1 class='title'> <a href='https://googleprojectzero.blogspot.com/'> Project Zero </a> </h1> </div> <div class='descriptionwrapper'> <p class='description'><span>News and updates from the Project Zero team at Google</span></p> </div> </div> </div></div> </div> </div> <div class='header-cap-bottom cap-bottom'> <div class='cap-left'></div> <div class='cap-right'></div> </div> </div> </header> <div class='tabs-outer'> <div class='tabs-cap-top cap-top'> <div class='cap-left'></div> <div class='cap-right'></div> </div> <div class='fauxborder-left tabs-fauxborder-left'> <div class='fauxborder-right tabs-fauxborder-right'></div> <div class='region-inner tabs-inner'> <div class='tabs no-items section' id='crosscol' name='Cross-Column'></div> <div class='tabs no-items section' id='crosscol-overflow' name='Cross-Column 2'></div> </div> </div> <div class='tabs-cap-bottom cap-bottom'> <div class='cap-left'></div> <div class='cap-right'></div> </div> </div> <div class='main-outer'> <div class='main-cap-top cap-top'> <div class='cap-left'></div> <div class='cap-right'></div> </div> <div class='fauxborder-left main-fauxborder-left'> <div class='fauxborder-right main-fauxborder-right'></div> <div class='region-inner main-inner'> <div class='columns fauxcolumns'> <div class='fauxcolumn-outer fauxcolumn-center-outer'> <div class='cap-top'> <div class='cap-left'></div> <div class='cap-right'></div> </div> <div class='fauxborder-left'> <div class='fauxborder-right'></div> <div class='fauxcolumn-inner'> </div> </div> <div class='cap-bottom'> <div class='cap-left'></div> <div class='cap-right'></div> </div> </div> <div class='fauxcolumn-outer fauxcolumn-left-outer'> <div class='cap-top'> <div class='cap-left'></div> <div class='cap-right'></div> </div> <div class='fauxborder-left'> <div class='fauxborder-right'></div> <div class='fauxcolumn-inner'> </div> </div> <div class='cap-bottom'> <div class='cap-left'></div> <div class='cap-right'></div> </div> </div> <div class='fauxcolumn-outer fauxcolumn-right-outer'> <div class='cap-top'> <div class='cap-left'></div> <div class='cap-right'></div> </div> <div class='fauxborder-left'> <div class='fauxborder-right'></div> <div class='fauxcolumn-inner'> </div> </div> <div class='cap-bottom'> <div class='cap-left'></div> <div class='cap-right'></div> </div> </div> <!-- corrects IE6 width calculation --> <div class='columns-inner'> <div class='column-center-outer'> <div class='column-center-inner'> <div class='main section' id='main' name='Main'><div class='widget Blog' data-version='1' id='Blog1'> <div class='blog-posts hfeed'> <div class="date-outer"> <h2 class='date-header'><span>Monday, December 21, 2020</span></h2> <div class="date-posts"> <div class='post-outer'> <div class='post hentry uncustomized-post-template' itemprop='blogPost' itemscope='itemscope' itemtype='http://schema.org/BlogPosting'> <meta content='https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjPxqmpHY7a9wVU9N6EaSUaZJ55fQcZGSW02pooIY5zBkFGd0byoBv6kQORU_QhaF0zsJky3K7E5XxLAmUh5rvgCVqI7CQDWQ04dgev9lh-PAA4IXEHVtkyPUzO3LXSOaI9A9_mh6EzV9fVHpqNiMN5flxkLnOStf0FlLiRF_UcJHIOKx7iIP6kK7AT/s1000/unnamed%20%284%29.png' itemprop='image_url'/> <meta content='4838136820032157985' itemprop='blogId'/> <meta content='4113191367925981084' itemprop='postId'/> <a name='4113191367925981084'></a> <h3 class='post-title entry-title' itemprop='name'> <a href='https://googleprojectzero.blogspot.com/2020/12/an-ios-hacker-tries-android.html'>An iOS hacker tries Android</a> </h3> <div class='post-header'> <div class='post-header-line-1'></div> </div> <div class='post-body entry-content' id='post-body-4113191367925981084' itemprop='description articleBody'> <style type="text/css">@import url('https://themes.googleusercontent.com/fonts/css?kit=lhDjYqiy3mZ0x6ROQEUoUw');ol.lst-kix_m7n4vga7agj0-8.start{counter-reset:lst-ctn-kix_m7n4vga7agj0-8 0}ol.lst-kix_s7qtkfn3qfov-6.start{counter-reset:lst-ctn-kix_s7qtkfn3qfov-6 0}.lst-kix_2atftb74ca0r-5>li{counter-increment:lst-ctn-kix_2atftb74ca0r-5}ol.lst-kix_lgfdoxi035fa-6.start{counter-reset:lst-ctn-kix_lgfdoxi035fa-6 0}ol.lst-kix_v82n41p8858f-6.start{counter-reset:lst-ctn-kix_v82n41p8858f-6 0}.lst-kix_8ndrlnfdlj1m-4>li{counter-increment:lst-ctn-kix_8ndrlnfdlj1m-4}ol.lst-kix_t794825wy5z-4.start{counter-reset:lst-ctn-kix_t794825wy5z-4 0}.lst-kix_t794825wy5z-5>li{counter-increment:lst-ctn-kix_t794825wy5z-5}ol.lst-kix_79xr8vp1jz9w-7.start{counter-reset:lst-ctn-kix_79xr8vp1jz9w-7 0}ol.lst-kix_8ndrlnfdlj1m-8{list-style-type:none}.lst-kix_dfignz4kto-6>li:before{content:"" counter(lst-ctn-kix_dfignz4kto-6,decimal) ". "}ol.lst-kix_8ndrlnfdlj1m-7{list-style-type:none}.lst-kix_n34nhxg9gk9-8>li{counter-increment:lst-ctn-kix_n34nhxg9gk9-8}ol.lst-kix_8ndrlnfdlj1m-6{list-style-type:none}.lst-kix_dfignz4kto-5>li:before{content:"" counter(lst-ctn-kix_dfignz4kto-5,lower-roman) ". "}.lst-kix_dfignz4kto-7>li:before{content:"" counter(lst-ctn-kix_dfignz4kto-7,lower-latin) ". "}ol.lst-kix_8ndrlnfdlj1m-5{list-style-type:none}ol.lst-kix_8ndrlnfdlj1m-4{list-style-type:none}ol.lst-kix_8ndrlnfdlj1m-3{list-style-type:none}ol.lst-kix_8ndrlnfdlj1m-2{list-style-type:none}.lst-kix_s7qtkfn3qfov-2>li{counter-increment:lst-ctn-kix_s7qtkfn3qfov-2}ol.lst-kix_8ndrlnfdlj1m-1{list-style-type:none}ol.lst-kix_8ndrlnfdlj1m-0{list-style-type:none}.lst-kix_dfignz4kto-8>li:before{content:"" counter(lst-ctn-kix_dfignz4kto-8,lower-roman) ". "}ol.lst-kix_s2bakucqamn4-5{list-style-type:none}.lst-kix_97v7yur38q2r-3>li{counter-increment:lst-ctn-kix_97v7yur38q2r-3}ol.lst-kix_s2bakucqamn4-6{list-style-type:none}ol.lst-kix_s2bakucqamn4-3{list-style-type:none}ol.lst-kix_s2bakucqamn4-4{list-style-type:none}.lst-kix_2atftb74ca0r-3>li:before{content:"" counter(lst-ctn-kix_2atftb74ca0r-3,decimal) ". "}ol.lst-kix_s2bakucqamn4-1{list-style-type:none}ol.lst-kix_s2bakucqamn4-2{list-style-type:none}ol.lst-kix_dfignz4kto-3.start{counter-reset:lst-ctn-kix_dfignz4kto-3 0}.lst-kix_2atftb74ca0r-2>li:before{content:"" counter(lst-ctn-kix_2atftb74ca0r-2,lower-roman) ". "}ol.lst-kix_s2bakucqamn4-0{list-style-type:none}.lst-kix_2atftb74ca0r-1>li:before{content:"" counter(lst-ctn-kix_2atftb74ca0r-1,lower-latin) ". "}.lst-kix_2atftb74ca0r-0>li:before{content:"" counter(lst-ctn-kix_2atftb74ca0r-0,decimal) ". "}ol.lst-kix_s2bakucqamn4-7{list-style-type:none}.lst-kix_s2bakucqamn4-8>li{counter-increment:lst-ctn-kix_s2bakucqamn4-8}.lst-kix_m7n4vga7agj0-1>li{counter-increment:lst-ctn-kix_m7n4vga7agj0-1}ol.lst-kix_41dur2ixsyfx-3.start{counter-reset:lst-ctn-kix_41dur2ixsyfx-3 0}ol.lst-kix_s2bakucqamn4-8{list-style-type:none}ol.lst-kix_n34nhxg9gk9-6.start{counter-reset:lst-ctn-kix_n34nhxg9gk9-6 0}.lst-kix_s8u2k5vztyaw-4>li{counter-increment:lst-ctn-kix_s8u2k5vztyaw-4}ol.lst-kix_8ndrlnfdlj1m-6.start{counter-reset:lst-ctn-kix_8ndrlnfdlj1m-6 0}.lst-kix_41dur2ixsyfx-6>li{counter-increment:lst-ctn-kix_41dur2ixsyfx-6}ol.lst-kix_v82n41p8858f-1.start{counter-reset:lst-ctn-kix_v82n41p8858f-1 0}.lst-kix_dfignz4kto-1>li{counter-increment:lst-ctn-kix_dfignz4kto-1}.lst-kix_mz7gq3g0njgs-6>li{counter-increment:lst-ctn-kix_mz7gq3g0njgs-6}ol.lst-kix_2atftb74ca0r-7.start{counter-reset:lst-ctn-kix_2atftb74ca0r-7 0}ol.lst-kix_s8u2k5vztyaw-3.start{counter-reset:lst-ctn-kix_s8u2k5vztyaw-3 0}.lst-kix_s2bakucqamn4-1>li{counter-increment:lst-ctn-kix_s2bakucqamn4-1}ol.lst-kix_s7qtkfn3qfov-1.start{counter-reset:lst-ctn-kix_s7qtkfn3qfov-1 0}ol.lst-kix_lgfdoxi035fa-1.start{counter-reset:lst-ctn-kix_lgfdoxi035fa-1 0}.lst-kix_yn1p6euud4dt-1>li{counter-increment:lst-ctn-kix_yn1p6euud4dt-1}ol.lst-kix_mz7gq3g0njgs-4.start{counter-reset:lst-ctn-kix_mz7gq3g0njgs-4 0}.lst-kix_vcalqlf3fy1b-5>li{counter-increment:lst-ctn-kix_vcalqlf3fy1b-5}ol.lst-kix_m7n4vga7agj0-3.start{counter-reset:lst-ctn-kix_m7n4vga7agj0-3 0}ol.lst-kix_2atftb74ca0r-0.start{counter-reset:lst-ctn-kix_2atftb74ca0r-0 0}.lst-kix_97v7yur38q2r-7>li{counter-increment:lst-ctn-kix_97v7yur38q2r-7}.lst-kix_v82n41p8858f-7>li{counter-increment:lst-ctn-kix_v82n41p8858f-7}.lst-kix_vcalqlf3fy1b-7>li:before{content:"" counter(lst-ctn-kix_vcalqlf3fy1b-7,lower-latin) ". "}ol.lst-kix_yn1p6euud4dt-8{list-style-type:none}ol.lst-kix_yn1p6euud4dt-7{list-style-type:none}ol.lst-kix_yn1p6euud4dt-6{list-style-type:none}.lst-kix_lgfdoxi035fa-3>li{counter-increment:lst-ctn-kix_lgfdoxi035fa-3}ol.lst-kix_yn1p6euud4dt-5{list-style-type:none}.lst-kix_vcalqlf3fy1b-8>li:before{content:"" counter(lst-ctn-kix_vcalqlf3fy1b-8,lower-roman) ". "}ol.lst-kix_yn1p6euud4dt-4{list-style-type:none}.lst-kix_79xr8vp1jz9w-2>li{counter-increment:lst-ctn-kix_79xr8vp1jz9w-2}.lst-kix_lgfdoxi035fa-6>li{counter-increment:lst-ctn-kix_lgfdoxi035fa-6}.lst-kix_8ndrlnfdlj1m-8>li{counter-increment:lst-ctn-kix_8ndrlnfdlj1m-8}.lst-kix_yn1p6euud4dt-8>li{counter-increment:lst-ctn-kix_yn1p6euud4dt-8}.lst-kix_s8u2k5vztyaw-8>li{counter-increment:lst-ctn-kix_s8u2k5vztyaw-8}ol.lst-kix_97v7yur38q2r-1.start{counter-reset:lst-ctn-kix_97v7yur38q2r-1 0}ol.lst-kix_97v7yur38q2r-8.start{counter-reset:lst-ctn-kix_97v7yur38q2r-8 0}ol.lst-kix_vcalqlf3fy1b-4.start{counter-reset:lst-ctn-kix_vcalqlf3fy1b-4 0}ol.lst-kix_s8u2k5vztyaw-8.start{counter-reset:lst-ctn-kix_s8u2k5vztyaw-8 0}.lst-kix_s2bakucqamn4-4>li{counter-increment:lst-ctn-kix_s2bakucqamn4-4}.lst-kix_t794825wy5z-1>li{counter-increment:lst-ctn-kix_t794825wy5z-1}ol.lst-kix_mz7gq3g0njgs-2.start{counter-reset:lst-ctn-kix_mz7gq3g0njgs-2 0}.lst-kix_m7n4vga7agj0-8>li{counter-increment:lst-ctn-kix_m7n4vga7agj0-8}.lst-kix_yn1p6euud4dt-4>li{counter-increment:lst-ctn-kix_yn1p6euud4dt-4}ol.lst-kix_s8u2k5vztyaw-1.start{counter-reset:lst-ctn-kix_s8u2k5vztyaw-1 0}.lst-kix_mz7gq3g0njgs-2>li{counter-increment:lst-ctn-kix_mz7gq3g0njgs-2}.lst-kix_vcalqlf3fy1b-2>li{counter-increment:lst-ctn-kix_vcalqlf3fy1b-2}ol.lst-kix_79xr8vp1jz9w-1{list-style-type:none}ol.lst-kix_79xr8vp1jz9w-0{list-style-type:none}ol.lst-kix_79xr8vp1jz9w-0.start{counter-reset:lst-ctn-kix_79xr8vp1jz9w-0 0}ol.lst-kix_dfignz4kto-1.start{counter-reset:lst-ctn-kix_dfignz4kto-1 0}ol.lst-kix_yn1p6euud4dt-6.start{counter-reset:lst-ctn-kix_yn1p6euud4dt-6 0}ol.lst-kix_79xr8vp1jz9w-5{list-style-type:none}ol.lst-kix_79xr8vp1jz9w-4{list-style-type:none}ol.lst-kix_79xr8vp1jz9w-3{list-style-type:none}ol.lst-kix_8ndrlnfdlj1m-4.start{counter-reset:lst-ctn-kix_8ndrlnfdlj1m-4 0}ol.lst-kix_79xr8vp1jz9w-2{list-style-type:none}ol.lst-kix_79xr8vp1jz9w-8{list-style-type:none}ol.lst-kix_79xr8vp1jz9w-7{list-style-type:none}ol.lst-kix_79xr8vp1jz9w-6{list-style-type:none}.lst-kix_n34nhxg9gk9-1>li:before{content:"" counter(lst-ctn-kix_n34nhxg9gk9-1,lower-latin) ". "}ol.lst-kix_m7n4vga7agj0-1.start{counter-reset:lst-ctn-kix_m7n4vga7agj0-1 0}ol.lst-kix_1xkddpu9nsxr-2.start{counter-reset:lst-ctn-kix_1xkddpu9nsxr-2 0}ol.lst-kix_yn1p6euud4dt-3{list-style-type:none}ol.lst-kix_yn1p6euud4dt-2{list-style-type:none}ol.lst-kix_yn1p6euud4dt-1{list-style-type:none}ol.lst-kix_yn1p6euud4dt-0{list-style-type:none}.lst-kix_97v7yur38q2r-0>li{counter-increment:lst-ctn-kix_97v7yur38q2r-0}.lst-kix_s7qtkfn3qfov-6>li{counter-increment:lst-ctn-kix_s7qtkfn3qfov-6}ol.lst-kix_lgfdoxi035fa-0{list-style-type:none}.lst-kix_vcalqlf3fy1b-2>li:before{content:"" counter(lst-ctn-kix_vcalqlf3fy1b-2,lower-roman) ". "}.lst-kix_vcalqlf3fy1b-6>li:before{content:"" counter(lst-ctn-kix_vcalqlf3fy1b-6,decimal) ". "}.lst-kix_n34nhxg9gk9-3>li:before{content:"" counter(lst-ctn-kix_n34nhxg9gk9-3,decimal) ". "}.lst-kix_n34nhxg9gk9-5>li:before{content:"" counter(lst-ctn-kix_n34nhxg9gk9-5,lower-roman) ". "}.lst-kix_vcalqlf3fy1b-4>li:before{content:"" counter(lst-ctn-kix_vcalqlf3fy1b-4,lower-latin) ". "}.lst-kix_s8u2k5vztyaw-7>li{counter-increment:lst-ctn-kix_s8u2k5vztyaw-7}.lst-kix_n34nhxg9gk9-7>li:before{content:"" counter(lst-ctn-kix_n34nhxg9gk9-7,lower-latin) ". "}.lst-kix_1xkddpu9nsxr-6>li{counter-increment:lst-ctn-kix_1xkddpu9nsxr-6}.lst-kix_mz7gq3g0njgs-3>li{counter-increment:lst-ctn-kix_mz7gq3g0njgs-3}ol.lst-kix_s2bakucqamn4-6.start{counter-reset:lst-ctn-kix_s2bakucqamn4-6 0}ol.lst-kix_t794825wy5z-2.start{counter-reset:lst-ctn-kix_t794825wy5z-2 0}.lst-kix_vcalqlf3fy1b-0>li:before{content:"" counter(lst-ctn-kix_vcalqlf3fy1b-0,decimal) ". "}.lst-kix_t794825wy5z-2>li{counter-increment:lst-ctn-kix_t794825wy5z-2}.lst-kix_t794825wy5z-8>li{counter-increment:lst-ctn-kix_t794825wy5z-8}ol.lst-kix_vcalqlf3fy1b-2.start{counter-reset:lst-ctn-kix_vcalqlf3fy1b-2 0}ol.lst-kix_79xr8vp1jz9w-2.start{counter-reset:lst-ctn-kix_79xr8vp1jz9w-2 0}.lst-kix_s8u2k5vztyaw-0>li{counter-increment:lst-ctn-kix_s8u2k5vztyaw-0}.lst-kix_lgfdoxi035fa-7>li{counter-increment:lst-ctn-kix_lgfdoxi035fa-7}.lst-kix_dfignz4kto-5>li{counter-increment:lst-ctn-kix_dfignz4kto-5}.lst-kix_s7qtkfn3qfov-5>li{counter-increment:lst-ctn-kix_s7qtkfn3qfov-5}.lst-kix_2atftb74ca0r-1>li{counter-increment:lst-ctn-kix_2atftb74ca0r-1}.lst-kix_2atftb74ca0r-4>li:before{content:"" counter(lst-ctn-kix_2atftb74ca0r-4,lower-latin) ". "}ol.lst-kix_s7qtkfn3qfov-4.start{counter-reset:lst-ctn-kix_s7qtkfn3qfov-4 0}ol.lst-kix_41dur2ixsyfx-8.start{counter-reset:lst-ctn-kix_41dur2ixsyfx-8 0}.lst-kix_1xkddpu9nsxr-7>li{counter-increment:lst-ctn-kix_1xkddpu9nsxr-7}.lst-kix_2atftb74ca0r-6>li:before{content:"" counter(lst-ctn-kix_2atftb74ca0r-6,decimal) ". "}.lst-kix_8ndrlnfdlj1m-6>li:before{content:"" counter(lst-ctn-kix_8ndrlnfdlj1m-6,decimal) ". "}ol.lst-kix_1xkddpu9nsxr-0.start{counter-reset:lst-ctn-kix_1xkddpu9nsxr-0 0}.lst-kix_dfignz4kto-2>li:before{content:"" counter(lst-ctn-kix_dfignz4kto-2,lower-roman) ". "}ol.lst-kix_8ndrlnfdlj1m-1.start{counter-reset:lst-ctn-kix_8ndrlnfdlj1m-1 0}.lst-kix_n34nhxg9gk9-1>li{counter-increment:lst-ctn-kix_n34nhxg9gk9-1}ol.lst-kix_n34nhxg9gk9-8.start{counter-reset:lst-ctn-kix_n34nhxg9gk9-8 0}.lst-kix_dfignz4kto-4>li:before{content:"" counter(lst-ctn-kix_dfignz4kto-4,lower-latin) ". "}ol.lst-kix_yn1p6euud4dt-4.start{counter-reset:lst-ctn-kix_yn1p6euud4dt-4 0}.lst-kix_2atftb74ca0r-8>li:before{content:"" counter(lst-ctn-kix_2atftb74ca0r-8,lower-roman) ". "}.lst-kix_8ndrlnfdlj1m-0>li:before{content:"" counter(lst-ctn-kix_8ndrlnfdlj1m-0,decimal) ". "}.lst-kix_8ndrlnfdlj1m-8>li:before{content:"" counter(lst-ctn-kix_8ndrlnfdlj1m-8,lower-roman) ". "}ol.lst-kix_s7qtkfn3qfov-3.start{counter-reset:lst-ctn-kix_s7qtkfn3qfov-3 0}ol.lst-kix_s2bakucqamn4-8.start{counter-reset:lst-ctn-kix_s2bakucqamn4-8 0}.lst-kix_v82n41p8858f-0>li{counter-increment:lst-ctn-kix_v82n41p8858f-0}.lst-kix_41dur2ixsyfx-2>li{counter-increment:lst-ctn-kix_41dur2ixsyfx-2}.lst-kix_8ndrlnfdlj1m-2>li:before{content:"" counter(lst-ctn-kix_8ndrlnfdlj1m-2,lower-roman) ". "}.lst-kix_m7n4vga7agj0-2>li{counter-increment:lst-ctn-kix_m7n4vga7agj0-2}.lst-kix_8ndrlnfdlj1m-4>li:before{content:"" counter(lst-ctn-kix_8ndrlnfdlj1m-4,lower-latin) ". "}.lst-kix_dfignz4kto-0>li:before{content:"" counter(lst-ctn-kix_dfignz4kto-0,decimal) ". "}ol.lst-kix_vcalqlf3fy1b-1{list-style-type:none}ol.lst-kix_vcalqlf3fy1b-2{list-style-type:none}.lst-kix_m7n4vga7agj0-4>li:before{content:"" counter(lst-ctn-kix_m7n4vga7agj0-4,lower-latin) ". "}ol.lst-kix_vcalqlf3fy1b-0{list-style-type:none}.lst-kix_m7n4vga7agj0-3>li:before{content:"" counter(lst-ctn-kix_m7n4vga7agj0-3,decimal) ". "}ol.lst-kix_79xr8vp1jz9w-4.start{counter-reset:lst-ctn-kix_79xr8vp1jz9w-4 0}.lst-kix_m7n4vga7agj0-7>li:before{content:"" counter(lst-ctn-kix_m7n4vga7agj0-7,lower-latin) ". "}ol.lst-kix_41dur2ixsyfx-3{list-style-type:none}.lst-kix_s8u2k5vztyaw-3>li{counter-increment:lst-ctn-kix_s8u2k5vztyaw-3}ol.lst-kix_41dur2ixsyfx-2{list-style-type:none}ol.lst-kix_41dur2ixsyfx-5{list-style-type:none}ol.lst-kix_41dur2ixsyfx-4{list-style-type:none}ol.lst-kix_41dur2ixsyfx-7{list-style-type:none}ol.lst-kix_41dur2ixsyfx-6{list-style-type:none}ol.lst-kix_41dur2ixsyfx-8{list-style-type:none}ol.lst-kix_s2bakucqamn4-4.start{counter-reset:lst-ctn-kix_s2bakucqamn4-4 0}.lst-kix_mz7gq3g0njgs-7>li{counter-increment:lst-ctn-kix_mz7gq3g0njgs-7}ol.lst-kix_mz7gq3g0njgs-6.start{counter-reset:lst-ctn-kix_mz7gq3g0njgs-6 0}ol.lst-kix_41dur2ixsyfx-6.start{counter-reset:lst-ctn-kix_41dur2ixsyfx-6 0}ol.lst-kix_41dur2ixsyfx-1{list-style-type:none}ol.lst-kix_41dur2ixsyfx-0{list-style-type:none}.lst-kix_t794825wy5z-6>li{counter-increment:lst-ctn-kix_t794825wy5z-6}.lst-kix_s7qtkfn3qfov-6>li:before{content:"" counter(lst-ctn-kix_s7qtkfn3qfov-6,decimal) ". "}ol.lst-kix_vcalqlf3fy1b-0.start{counter-reset:lst-ctn-kix_vcalqlf3fy1b-0 0}.lst-kix_m7n4vga7agj0-0>li:before{content:"" counter(lst-ctn-kix_m7n4vga7agj0-0,decimal) ". "}.lst-kix_lgfdoxi035fa-0>li{counter-increment:lst-ctn-kix_lgfdoxi035fa-0}.lst-kix_97v7yur38q2r-6>li{counter-increment:lst-ctn-kix_97v7yur38q2r-6}ol.lst-kix_n34nhxg9gk9-0{list-style-type:none}.lst-kix_vcalqlf3fy1b-6>li{counter-increment:lst-ctn-kix_vcalqlf3fy1b-6}ol.lst-kix_n34nhxg9gk9-2{list-style-type:none}ol.lst-kix_n34nhxg9gk9-1{list-style-type:none}ol.lst-kix_n34nhxg9gk9-4{list-style-type:none}.lst-kix_s7qtkfn3qfov-3>li{counter-increment:lst-ctn-kix_s7qtkfn3qfov-3}ol.lst-kix_n34nhxg9gk9-3{list-style-type:none}ol.lst-kix_n34nhxg9gk9-6{list-style-type:none}ol.lst-kix_n34nhxg9gk9-5{list-style-type:none}ol.lst-kix_n34nhxg9gk9-8{list-style-type:none}ol.lst-kix_n34nhxg9gk9-7{list-style-type:none}.lst-kix_s2bakucqamn4-6>li:before{content:"" counter(lst-ctn-kix_s2bakucqamn4-6,decimal) ". "}.lst-kix_m7n4vga7agj0-4>li{counter-increment:lst-ctn-kix_m7n4vga7agj0-4}.lst-kix_79xr8vp1jz9w-6>li:before{content:"" counter(lst-ctn-kix_79xr8vp1jz9w-6,decimal) ". "}.lst-kix_s2bakucqamn4-5>li:before{content:"" counter(lst-ctn-kix_s2bakucqamn4-5,lower-roman) ". "}.lst-kix_41dur2ixsyfx-3>li{counter-increment:lst-ctn-kix_41dur2ixsyfx-3}ol.lst-kix_n34nhxg9gk9-3.start{counter-reset:lst-ctn-kix_n34nhxg9gk9-3 0}ol.lst-kix_m7n4vga7agj0-5.start{counter-reset:lst-ctn-kix_m7n4vga7agj0-5 0}.lst-kix_s2bakucqamn4-0>li{counter-increment:lst-ctn-kix_s2bakucqamn4-0}.lst-kix_s2bakucqamn4-2>li:before{content:"" counter(lst-ctn-kix_s2bakucqamn4-2,lower-roman) ". "}ol.lst-kix_s8u2k5vztyaw-0.start{counter-reset:lst-ctn-kix_s8u2k5vztyaw-0 0}.lst-kix_79xr8vp1jz9w-1>li{counter-increment:lst-ctn-kix_79xr8vp1jz9w-1}.lst-kix_79xr8vp1jz9w-3>li:before{content:"" counter(lst-ctn-kix_79xr8vp1jz9w-3,decimal) ". "}.lst-kix_s8u2k5vztyaw-1>li{counter-increment:lst-ctn-kix_s8u2k5vztyaw-1}ol.lst-kix_41dur2ixsyfx-0.start{counter-reset:lst-ctn-kix_41dur2ixsyfx-0 0}ol.lst-kix_8ndrlnfdlj1m-3.start{counter-reset:lst-ctn-kix_8ndrlnfdlj1m-3 0}.lst-kix_79xr8vp1jz9w-2>li:before{content:"" counter(lst-ctn-kix_79xr8vp1jz9w-2,lower-roman) ". "}.lst-kix_s2bakucqamn4-1>li:before{content:"" counter(lst-ctn-kix_s2bakucqamn4-1,lower-latin) ". "}.lst-kix_1xkddpu9nsxr-0>li{counter-increment:lst-ctn-kix_1xkddpu9nsxr-0}.lst-kix_n34nhxg9gk9-0>li{counter-increment:lst-ctn-kix_n34nhxg9gk9-0}.lst-kix_97v7yur38q2r-5>li:before{content:"" counter(lst-ctn-kix_97v7yur38q2r-5,lower-roman) ". "}.lst-kix_41dur2ixsyfx-7>li{counter-increment:lst-ctn-kix_41dur2ixsyfx-7}ol.lst-kix_n34nhxg9gk9-4.start{counter-reset:lst-ctn-kix_n34nhxg9gk9-4 0}.lst-kix_v82n41p8858f-8>li{counter-increment:lst-ctn-kix_v82n41p8858f-8}.lst-kix_lgfdoxi035fa-2>li:before{content:"" counter(lst-ctn-kix_lgfdoxi035fa-2,lower-roman) ". "}.lst-kix_2atftb74ca0r-8>li{counter-increment:lst-ctn-kix_2atftb74ca0r-8}ol.lst-kix_yn1p6euud4dt-8.start{counter-reset:lst-ctn-kix_yn1p6euud4dt-8 0}.lst-kix_97v7yur38q2r-4>li:before{content:"" counter(lst-ctn-kix_97v7yur38q2r-4,lower-latin) ". "}ol.lst-kix_dfignz4kto-6.start{counter-reset:lst-ctn-kix_dfignz4kto-6 0}.lst-kix_n34nhxg9gk9-5>li{counter-increment:lst-ctn-kix_n34nhxg9gk9-5}.lst-kix_m7n4vga7agj0-6>li{counter-increment:lst-ctn-kix_m7n4vga7agj0-6}ol.lst-kix_m7n4vga7agj0-3{list-style-type:none}ol.lst-kix_m7n4vga7agj0-2{list-style-type:none}.lst-kix_79xr8vp1jz9w-7>li:before{content:"" counter(lst-ctn-kix_79xr8vp1jz9w-7,lower-latin) ". "}ol.lst-kix_m7n4vga7agj0-1{list-style-type:none}ol.lst-kix_v82n41p8858f-8.start{counter-reset:lst-ctn-kix_v82n41p8858f-8 0}.lst-kix_t794825wy5z-1>li:before{content:"" counter(lst-ctn-kix_t794825wy5z-1,lower-latin) ". "}ol.lst-kix_m7n4vga7agj0-0{list-style-type:none}ol.lst-kix_lgfdoxi035fa-2{list-style-type:none}ol.lst-kix_lgfdoxi035fa-1{list-style-type:none}ol.lst-kix_lgfdoxi035fa-4{list-style-type:none}.lst-kix_t794825wy5z-0>li:before{content:"" counter(lst-ctn-kix_t794825wy5z-0,decimal) ". "}.lst-kix_dfignz4kto-6>li{counter-increment:lst-ctn-kix_dfignz4kto-6}ol.lst-kix_lgfdoxi035fa-3{list-style-type:none}.lst-kix_97v7yur38q2r-8>li:before{content:"" counter(lst-ctn-kix_97v7yur38q2r-8,lower-roman) ". "}ol.lst-kix_lgfdoxi035fa-6{list-style-type:none}ol.lst-kix_79xr8vp1jz9w-5.start{counter-reset:lst-ctn-kix_79xr8vp1jz9w-5 0}ol.lst-kix_lgfdoxi035fa-5{list-style-type:none}ol.lst-kix_lgfdoxi035fa-8{list-style-type:none}.lst-kix_v82n41p8858f-1>li{counter-increment:lst-ctn-kix_v82n41p8858f-1}ol.lst-kix_lgfdoxi035fa-7{list-style-type:none}.lst-kix_s7qtkfn3qfov-2>li:before{content:"" counter(lst-ctn-kix_s7qtkfn3qfov-2,lower-roman) ". "}.lst-kix_79xr8vp1jz9w-8>li{counter-increment:lst-ctn-kix_79xr8vp1jz9w-8}.lst-kix_s8u2k5vztyaw-5>li{counter-increment:lst-ctn-kix_s8u2k5vztyaw-5}.lst-kix_8ndrlnfdlj1m-5>li{counter-increment:lst-ctn-kix_8ndrlnfdlj1m-5}.lst-kix_s7qtkfn3qfov-1>li:before{content:"" counter(lst-ctn-kix_s7qtkfn3qfov-1,lower-latin) ". "}.lst-kix_s7qtkfn3qfov-5>li:before{content:"" counter(lst-ctn-kix_s7qtkfn3qfov-5,lower-roman) ". "}ol.lst-kix_s2bakucqamn4-5.start{counter-reset:lst-ctn-kix_s2bakucqamn4-5 0}ol.lst-kix_dfignz4kto-5.start{counter-reset:lst-ctn-kix_dfignz4kto-5 0}ol.lst-kix_mz7gq3g0njgs-8{list-style-type:none}ol.lst-kix_m7n4vga7agj0-7{list-style-type:none}ol.lst-kix_mz7gq3g0njgs-7{list-style-type:none}ol.lst-kix_m7n4vga7agj0-6{list-style-type:none}ol.lst-kix_m7n4vga7agj0-5{list-style-type:none}ol.lst-kix_m7n4vga7agj0-4{list-style-type:none}ol.lst-kix_m7n4vga7agj0-4.start{counter-reset:lst-ctn-kix_m7n4vga7agj0-4 0}ol.lst-kix_mz7gq3g0njgs-5.start{counter-reset:lst-ctn-kix_mz7gq3g0njgs-5 0}ol.lst-kix_m7n4vga7agj0-8{list-style-type:none}ol.lst-kix_mz7gq3g0njgs-0{list-style-type:none}ol.lst-kix_41dur2ixsyfx-5.start{counter-reset:lst-ctn-kix_41dur2ixsyfx-5 0}ol.lst-kix_mz7gq3g0njgs-2{list-style-type:none}ol.lst-kix_mz7gq3g0njgs-1{list-style-type:none}.lst-kix_t794825wy5z-4>li:before{content:"" counter(lst-ctn-kix_t794825wy5z-4,lower-latin) ". "}.lst-kix_lgfdoxi035fa-7>li:before{content:"" counter(lst-ctn-kix_lgfdoxi035fa-7,lower-latin) ". "}ol.lst-kix_mz7gq3g0njgs-4{list-style-type:none}.lst-kix_t794825wy5z-4>li{counter-increment:lst-ctn-kix_t794825wy5z-4}.lst-kix_vcalqlf3fy1b-8>li{counter-increment:lst-ctn-kix_vcalqlf3fy1b-8}ol.lst-kix_mz7gq3g0njgs-3{list-style-type:none}ol.lst-kix_mz7gq3g0njgs-6{list-style-type:none}ol.lst-kix_mz7gq3g0njgs-5{list-style-type:none}.lst-kix_t794825wy5z-5>li:before{content:"" counter(lst-ctn-kix_t794825wy5z-5,lower-roman) ". "}.lst-kix_lgfdoxi035fa-6>li:before{content:"" counter(lst-ctn-kix_lgfdoxi035fa-6,decimal) ". "}.lst-kix_s7qtkfn3qfov-1>li{counter-increment:lst-ctn-kix_s7qtkfn3qfov-1}.lst-kix_s2bakucqamn4-7>li{counter-increment:lst-ctn-kix_s2bakucqamn4-7}.lst-kix_m7n4vga7agj0-8>li:before{content:"" counter(lst-ctn-kix_m7n4vga7agj0-8,lower-roman) ". "}ol.lst-kix_vcalqlf3fy1b-7{list-style-type:none}.lst-kix_t794825wy5z-8>li:before{content:"" counter(lst-ctn-kix_t794825wy5z-8,lower-roman) ". "}ol.lst-kix_vcalqlf3fy1b-8{list-style-type:none}.lst-kix_lgfdoxi035fa-3>li:before{content:"" counter(lst-ctn-kix_lgfdoxi035fa-3,decimal) ". "}ol.lst-kix_vcalqlf3fy1b-5{list-style-type:none}ol.lst-kix_yn1p6euud4dt-7.start{counter-reset:lst-ctn-kix_yn1p6euud4dt-7 0}.lst-kix_97v7yur38q2r-0>li:before{content:"" counter(lst-ctn-kix_97v7yur38q2r-0,decimal) ". "}.lst-kix_97v7yur38q2r-1>li:before{content:"" counter(lst-ctn-kix_97v7yur38q2r-1,lower-latin) ". "}ol.lst-kix_vcalqlf3fy1b-6{list-style-type:none}ol.lst-kix_vcalqlf3fy1b-3{list-style-type:none}ol.lst-kix_vcalqlf3fy1b-4{list-style-type:none}ol.lst-kix_41dur2ixsyfx-2.start{counter-reset:lst-ctn-kix_41dur2ixsyfx-2 0}.lst-kix_n34nhxg9gk9-3>li{counter-increment:lst-ctn-kix_n34nhxg9gk9-3}.lst-kix_41dur2ixsyfx-2>li:before{content:"" counter(lst-ctn-kix_41dur2ixsyfx-2,lower-roman) ". "}.lst-kix_s8u2k5vztyaw-2>li:before{content:"" counter(lst-ctn-kix_s8u2k5vztyaw-2,lower-roman) ". "}ol.lst-kix_n34nhxg9gk9-2.start{counter-reset:lst-ctn-kix_n34nhxg9gk9-2 0}.lst-kix_79xr8vp1jz9w-6>li{counter-increment:lst-ctn-kix_79xr8vp1jz9w-6}ol.lst-kix_2atftb74ca0r-5.start{counter-reset:lst-ctn-kix_2atftb74ca0r-5 0}.lst-kix_s8u2k5vztyaw-6>li:before{content:"" counter(lst-ctn-kix_s8u2k5vztyaw-6,decimal) ". "}.lst-kix_dfignz4kto-8>li{counter-increment:lst-ctn-kix_dfignz4kto-8}.lst-kix_yn1p6euud4dt-1>li:before{content:"" counter(lst-ctn-kix_yn1p6euud4dt-1,lower-latin) ". "}.lst-kix_yn1p6euud4dt-5>li:before{content:"" counter(lst-ctn-kix_yn1p6euud4dt-5,lower-roman) ". "}ol.lst-kix_dfignz4kto-7.start{counter-reset:lst-ctn-kix_dfignz4kto-7 0}ol.lst-kix_s8u2k5vztyaw-4.start{counter-reset:lst-ctn-kix_s8u2k5vztyaw-4 0}ol.lst-kix_8ndrlnfdlj1m-7.start{counter-reset:lst-ctn-kix_8ndrlnfdlj1m-7 0}.lst-kix_1xkddpu9nsxr-5>li{counter-increment:lst-ctn-kix_1xkddpu9nsxr-5}.lst-kix_41dur2ixsyfx-6>li:before{content:"" counter(lst-ctn-kix_41dur2ixsyfx-6,decimal) ". "}.lst-kix_vcalqlf3fy1b-3>li:before{content:"" counter(lst-ctn-kix_vcalqlf3fy1b-3,decimal) ". "}.lst-kix_s2bakucqamn4-5>li{counter-increment:lst-ctn-kix_s2bakucqamn4-5}.lst-kix_n34nhxg9gk9-2>li:before{content:"" counter(lst-ctn-kix_n34nhxg9gk9-2,lower-roman) ". "}ol.lst-kix_79xr8vp1jz9w-8.start{counter-reset:lst-ctn-kix_79xr8vp1jz9w-8 0}ol.lst-kix_s8u2k5vztyaw-7.start{counter-reset:lst-ctn-kix_s8u2k5vztyaw-7 0}ol.lst-kix_s2bakucqamn4-0.start{counter-reset:lst-ctn-kix_s2bakucqamn4-0 0}.lst-kix_mz7gq3g0njgs-0>li{counter-increment:lst-ctn-kix_mz7gq3g0njgs-0}.lst-kix_41dur2ixsyfx-0>li{counter-increment:lst-ctn-kix_41dur2ixsyfx-0}.lst-kix_8ndrlnfdlj1m-0>li{counter-increment:lst-ctn-kix_8ndrlnfdlj1m-0}.lst-kix_s7qtkfn3qfov-8>li{counter-increment:lst-ctn-kix_s7qtkfn3qfov-8}.lst-kix_1xkddpu9nsxr-3>li{counter-increment:lst-ctn-kix_1xkddpu9nsxr-3}.lst-kix_vcalqlf3fy1b-1>li{counter-increment:lst-ctn-kix_vcalqlf3fy1b-1}.lst-kix_n34nhxg9gk9-6>li:before{content:"" counter(lst-ctn-kix_n34nhxg9gk9-6,decimal) ". "}ol.lst-kix_s2bakucqamn4-3.start{counter-reset:lst-ctn-kix_s2bakucqamn4-3 0}ol.lst-kix_mz7gq3g0njgs-7.start{counter-reset:lst-ctn-kix_mz7gq3g0njgs-7 0}ol.lst-kix_2atftb74ca0r-2.start{counter-reset:lst-ctn-kix_2atftb74ca0r-2 0}.lst-kix_lgfdoxi035fa-5>li{counter-increment:lst-ctn-kix_lgfdoxi035fa-5}ol.lst-kix_s7qtkfn3qfov-8.start{counter-reset:lst-ctn-kix_s7qtkfn3qfov-8 0}.lst-kix_8ndrlnfdlj1m-7>li{counter-increment:lst-ctn-kix_8ndrlnfdlj1m-7}.lst-kix_mz7gq3g0njgs-5>li{counter-increment:lst-ctn-kix_mz7gq3g0njgs-5}.lst-kix_mz7gq3g0njgs-1>li:before{content:"" counter(lst-ctn-kix_mz7gq3g0njgs-1,lower-latin) ". "}ol.lst-kix_s8u2k5vztyaw-6.start{counter-reset:lst-ctn-kix_s8u2k5vztyaw-6 0}ol.lst-kix_n34nhxg9gk9-0.start{counter-reset:lst-ctn-kix_n34nhxg9gk9-0 0}.lst-kix_97v7yur38q2r-8>li{counter-increment:lst-ctn-kix_97v7yur38q2r-8}ol.lst-kix_2atftb74ca0r-3.start{counter-reset:lst-ctn-kix_2atftb74ca0r-3 0}ol.lst-kix_m7n4vga7agj0-6.start{counter-reset:lst-ctn-kix_m7n4vga7agj0-6 0}ol.lst-kix_41dur2ixsyfx-1.start{counter-reset:lst-ctn-kix_41dur2ixsyfx-1 0}ol.lst-kix_s2bakucqamn4-2.start{counter-reset:lst-ctn-kix_s2bakucqamn4-2 0}.lst-kix_mz7gq3g0njgs-5>li:before{content:"" counter(lst-ctn-kix_mz7gq3g0njgs-5,lower-roman) ". "}ol.lst-kix_8ndrlnfdlj1m-8.start{counter-reset:lst-ctn-kix_8ndrlnfdlj1m-8 0}ol.lst-kix_s8u2k5vztyaw-8{list-style-type:none}.lst-kix_8ndrlnfdlj1m-5>li:before{content:"" counter(lst-ctn-kix_8ndrlnfdlj1m-5,lower-roman) ". "}.lst-kix_2atftb74ca0r-7>li:before{content:"" counter(lst-ctn-kix_2atftb74ca0r-7,lower-latin) ". "}ol.lst-kix_s8u2k5vztyaw-6{list-style-type:none}ol.lst-kix_m7n4vga7agj0-7.start{counter-reset:lst-ctn-kix_m7n4vga7agj0-7 0}ol.lst-kix_s8u2k5vztyaw-7{list-style-type:none}ol.lst-kix_s8u2k5vztyaw-4{list-style-type:none}ol.lst-kix_mz7gq3g0njgs-8.start{counter-reset:lst-ctn-kix_mz7gq3g0njgs-8 0}ol.lst-kix_s8u2k5vztyaw-5{list-style-type:none}ol.lst-kix_s8u2k5vztyaw-2{list-style-type:none}.lst-kix_41dur2ixsyfx-5>li{counter-increment:lst-ctn-kix_41dur2ixsyfx-5}ol.lst-kix_s8u2k5vztyaw-3{list-style-type:none}ol.lst-kix_s8u2k5vztyaw-0{list-style-type:none}.lst-kix_dfignz4kto-3>li:before{content:"" counter(lst-ctn-kix_dfignz4kto-3,decimal) ". "}.lst-kix_8ndrlnfdlj1m-1>li:before{content:"" counter(lst-ctn-kix_8ndrlnfdlj1m-1,lower-latin) ". "}ol.lst-kix_s8u2k5vztyaw-1{list-style-type:none}.lst-kix_yn1p6euud4dt-6>li{counter-increment:lst-ctn-kix_yn1p6euud4dt-6}ol.lst-kix_2atftb74ca0r-4.start{counter-reset:lst-ctn-kix_2atftb74ca0r-4 0}ol.lst-kix_v82n41p8858f-0{list-style-type:none}ol.lst-kix_s2bakucqamn4-1.start{counter-reset:lst-ctn-kix_s2bakucqamn4-1 0}ol.lst-kix_s8u2k5vztyaw-5.start{counter-reset:lst-ctn-kix_s8u2k5vztyaw-5 0}.lst-kix_v82n41p8858f-3>li{counter-increment:lst-ctn-kix_v82n41p8858f-3}ol.lst-kix_v82n41p8858f-5{list-style-type:none}ol.lst-kix_dfignz4kto-8.start{counter-reset:lst-ctn-kix_dfignz4kto-8 0}ol.lst-kix_v82n41p8858f-6{list-style-type:none}ol.lst-kix_v82n41p8858f-7{list-style-type:none}ol.lst-kix_v82n41p8858f-8{list-style-type:none}ol.lst-kix_v82n41p8858f-1{list-style-type:none}ol.lst-kix_n34nhxg9gk9-1.start{counter-reset:lst-ctn-kix_n34nhxg9gk9-1 0}ol.lst-kix_v82n41p8858f-2{list-style-type:none}ol.lst-kix_v82n41p8858f-3{list-style-type:none}ol.lst-kix_v82n41p8858f-4{list-style-type:none}.lst-kix_41dur2ixsyfx-4>li{counter-increment:lst-ctn-kix_41dur2ixsyfx-4}.lst-kix_1xkddpu9nsxr-1>li:before{content:"" counter(lst-ctn-kix_1xkddpu9nsxr-1,lower-latin) ". "}.lst-kix_mz7gq3g0njgs-8>li{counter-increment:lst-ctn-kix_mz7gq3g0njgs-8}.lst-kix_1xkddpu9nsxr-2>li:before{content:"" counter(lst-ctn-kix_1xkddpu9nsxr-2,lower-roman) ". "}.lst-kix_vcalqlf3fy1b-7>li{counter-increment:lst-ctn-kix_vcalqlf3fy1b-7}.lst-kix_1xkddpu9nsxr-4>li:before{content:"" counter(lst-ctn-kix_1xkddpu9nsxr-4,lower-latin) ". "}.lst-kix_1xkddpu9nsxr-3>li:before{content:"" counter(lst-ctn-kix_1xkddpu9nsxr-3,decimal) ". "}.lst-kix_1xkddpu9nsxr-6>li:before{content:"" counter(lst-ctn-kix_1xkddpu9nsxr-6,decimal) ". "}ol.lst-kix_s2bakucqamn4-7.start{counter-reset:lst-ctn-kix_s2bakucqamn4-7 0}.lst-kix_97v7yur38q2r-5>li{counter-increment:lst-ctn-kix_97v7yur38q2r-5}.lst-kix_1xkddpu9nsxr-5>li:before{content:"" counter(lst-ctn-kix_1xkddpu9nsxr-5,lower-roman) ". "}ol.lst-kix_97v7yur38q2r-2.start{counter-reset:lst-ctn-kix_97v7yur38q2r-2 0}.lst-kix_m7n4vga7agj0-3>li{counter-increment:lst-ctn-kix_m7n4vga7agj0-3}.lst-kix_1xkddpu9nsxr-8>li:before{content:"" counter(lst-ctn-kix_1xkddpu9nsxr-8,lower-roman) ". "}.lst-kix_1xkddpu9nsxr-7>li:before{content:"" counter(lst-ctn-kix_1xkddpu9nsxr-7,lower-latin) ". "}ol.lst-kix_m7n4vga7agj0-2.start{counter-reset:lst-ctn-kix_m7n4vga7agj0-2 0}ol.lst-kix_mz7gq3g0njgs-3.start{counter-reset:lst-ctn-kix_mz7gq3g0njgs-3 0}ol.lst-kix_2atftb74ca0r-6.start{counter-reset:lst-ctn-kix_2atftb74ca0r-6 0}.lst-kix_mz7gq3g0njgs-7>li:before{content:"" counter(lst-ctn-kix_mz7gq3g0njgs-7,lower-latin) ". "}.lst-kix_mz7gq3g0njgs-8>li:before{content:"" counter(lst-ctn-kix_mz7gq3g0njgs-8,lower-roman) ". "}.lst-kix_s7qtkfn3qfov-0>li{counter-increment:lst-ctn-kix_s7qtkfn3qfov-0}ol.lst-kix_vcalqlf3fy1b-3.start{counter-reset:lst-ctn-kix_vcalqlf3fy1b-3 0}.lst-kix_2atftb74ca0r-3>li{counter-increment:lst-ctn-kix_2atftb74ca0r-3}ol.lst-kix_8ndrlnfdlj1m-0.start{counter-reset:lst-ctn-kix_8ndrlnfdlj1m-0 0}ol.lst-kix_79xr8vp1jz9w-1.start{counter-reset:lst-ctn-kix_79xr8vp1jz9w-1 0}.lst-kix_1xkddpu9nsxr-0>li:before{content:"" counter(lst-ctn-kix_1xkddpu9nsxr-0,decimal) ". "}.lst-kix_8ndrlnfdlj1m-6>li{counter-increment:lst-ctn-kix_8ndrlnfdlj1m-6}.lst-kix_lgfdoxi035fa-8>li{counter-increment:lst-ctn-kix_lgfdoxi035fa-8}.lst-kix_v82n41p8858f-2>li{counter-increment:lst-ctn-kix_v82n41p8858f-2}.lst-kix_n34nhxg9gk9-6>li{counter-increment:lst-ctn-kix_n34nhxg9gk9-6}.lst-kix_97v7yur38q2r-1>li{counter-increment:lst-ctn-kix_97v7yur38q2r-1}.lst-kix_mz7gq3g0njgs-4>li{counter-increment:lst-ctn-kix_mz7gq3g0njgs-4}ol.lst-kix_t794825wy5z-3.start{counter-reset:lst-ctn-kix_t794825wy5z-3 0}ol.lst-kix_41dur2ixsyfx-4.start{counter-reset:lst-ctn-kix_41dur2ixsyfx-4 0}.lst-kix_v82n41p8858f-1>li:before{content:"" counter(lst-ctn-kix_v82n41p8858f-1,lower-latin) ". "}ol.lst-kix_97v7yur38q2r-7.start{counter-reset:lst-ctn-kix_97v7yur38q2r-7 0}.lst-kix_v82n41p8858f-0>li:before{content:"" counter(lst-ctn-kix_v82n41p8858f-0,decimal) ". "}.lst-kix_1xkddpu9nsxr-8>li{counter-increment:lst-ctn-kix_1xkddpu9nsxr-8}ol.lst-kix_s7qtkfn3qfov-0.start{counter-reset:lst-ctn-kix_s7qtkfn3qfov-0 0}ol.lst-kix_s8u2k5vztyaw-2.start{counter-reset:lst-ctn-kix_s8u2k5vztyaw-2 0}.lst-kix_s8u2k5vztyaw-2>li{counter-increment:lst-ctn-kix_s8u2k5vztyaw-2}ol.lst-kix_lgfdoxi035fa-7.start{counter-reset:lst-ctn-kix_lgfdoxi035fa-7 0}ol.lst-kix_2atftb74ca0r-1.start{counter-reset:lst-ctn-kix_2atftb74ca0r-1 0}ol.lst-kix_1xkddpu9nsxr-2{list-style-type:none}.lst-kix_dfignz4kto-3>li{counter-increment:lst-ctn-kix_dfignz4kto-3}ol.lst-kix_1xkddpu9nsxr-3{list-style-type:none}ol.lst-kix_1xkddpu9nsxr-0{list-style-type:none}ol.lst-kix_1xkddpu9nsxr-1{list-style-type:none}ol.lst-kix_s7qtkfn3qfov-7.start{counter-reset:lst-ctn-kix_s7qtkfn3qfov-7 0}.lst-kix_v82n41p8858f-2>li:before{content:"" counter(lst-ctn-kix_v82n41p8858f-2,lower-roman) ". "}ol.lst-kix_1xkddpu9nsxr-6{list-style-type:none}.lst-kix_8ndrlnfdlj1m-2>li{counter-increment:lst-ctn-kix_8ndrlnfdlj1m-2}ol.lst-kix_1xkddpu9nsxr-7{list-style-type:none}.lst-kix_yn1p6euud4dt-0>li:before{content:"" counter(lst-ctn-kix_yn1p6euud4dt-0,decimal) ". "}ol.lst-kix_1xkddpu9nsxr-4{list-style-type:none}ol.lst-kix_8ndrlnfdlj1m-5.start{counter-reset:lst-ctn-kix_8ndrlnfdlj1m-5 0}ol.lst-kix_1xkddpu9nsxr-5{list-style-type:none}.lst-kix_v82n41p8858f-3>li:before{content:"" counter(lst-ctn-kix_v82n41p8858f-3,decimal) ". "}ol.lst-kix_79xr8vp1jz9w-6.start{counter-reset:lst-ctn-kix_79xr8vp1jz9w-6 0}ol.lst-kix_1xkddpu9nsxr-8{list-style-type:none}.lst-kix_v82n41p8858f-4>li:before{content:"" counter(lst-ctn-kix_v82n41p8858f-4,lower-latin) ". "}.lst-kix_t794825wy5z-7>li{counter-increment:lst-ctn-kix_t794825wy5z-7}.lst-kix_v82n41p8858f-5>li:before{content:"" counter(lst-ctn-kix_v82n41p8858f-5,lower-roman) ". "}.lst-kix_v82n41p8858f-8>li:before{content:"" counter(lst-ctn-kix_v82n41p8858f-8,lower-roman) ". "}.lst-kix_v82n41p8858f-6>li:before{content:"" counter(lst-ctn-kix_v82n41p8858f-6,decimal) ". "}.lst-kix_s7qtkfn3qfov-4>li{counter-increment:lst-ctn-kix_s7qtkfn3qfov-4}.lst-kix_v82n41p8858f-7>li:before{content:"" counter(lst-ctn-kix_v82n41p8858f-7,lower-latin) ". "}ol.lst-kix_lgfdoxi035fa-0.start{counter-reset:lst-ctn-kix_lgfdoxi035fa-0 0}ol.lst-kix_yn1p6euud4dt-3.start{counter-reset:lst-ctn-kix_yn1p6euud4dt-3 0}.lst-kix_v82n41p8858f-5>li{counter-increment:lst-ctn-kix_v82n41p8858f-5}.lst-kix_s7qtkfn3qfov-7>li{counter-increment:lst-ctn-kix_s7qtkfn3qfov-7}.lst-kix_41dur2ixsyfx-1>li:before{content:"" counter(lst-ctn-kix_41dur2ixsyfx-1,lower-latin) ". "}.lst-kix_41dur2ixsyfx-5>li:before{content:"" counter(lst-ctn-kix_41dur2ixsyfx-5,lower-roman) ". "}.lst-kix_s8u2k5vztyaw-1>li:before{content:"" counter(lst-ctn-kix_s8u2k5vztyaw-1,lower-latin) ". "}.lst-kix_s8u2k5vztyaw-5>li:before{content:"" counter(lst-ctn-kix_s8u2k5vztyaw-5,lower-roman) ". "}.lst-kix_yn1p6euud4dt-6>li:before{content:"" counter(lst-ctn-kix_yn1p6euud4dt-6,decimal) ". "}.lst-kix_yn1p6euud4dt-8>li:before{content:"" counter(lst-ctn-kix_yn1p6euud4dt-8,lower-roman) ". "}.lst-kix_41dur2ixsyfx-3>li:before{content:"" counter(lst-ctn-kix_41dur2ixsyfx-3,decimal) ". "}.lst-kix_s8u2k5vztyaw-7>li:before{content:"" counter(lst-ctn-kix_s8u2k5vztyaw-7,lower-latin) ". "}.lst-kix_s2bakucqamn4-3>li{counter-increment:lst-ctn-kix_s2bakucqamn4-3}.lst-kix_yn1p6euud4dt-2>li:before{content:"" counter(lst-ctn-kix_yn1p6euud4dt-2,lower-roman) ". "}ol.lst-kix_2atftb74ca0r-0{list-style-type:none}ol.lst-kix_2atftb74ca0r-1{list-style-type:none}ol.lst-kix_2atftb74ca0r-2{list-style-type:none}.lst-kix_m7n4vga7agj0-7>li{counter-increment:lst-ctn-kix_m7n4vga7agj0-7}ol.lst-kix_2atftb74ca0r-3{list-style-type:none}ol.lst-kix_2atftb74ca0r-4{list-style-type:none}ol.lst-kix_2atftb74ca0r-5{list-style-type:none}ol.lst-kix_2atftb74ca0r-6{list-style-type:none}.lst-kix_mz7gq3g0njgs-1>li{counter-increment:lst-ctn-kix_mz7gq3g0njgs-1}.lst-kix_n34nhxg9gk9-2>li{counter-increment:lst-ctn-kix_n34nhxg9gk9-2}ol.lst-kix_2atftb74ca0r-7{list-style-type:none}ol.lst-kix_2atftb74ca0r-8{list-style-type:none}.lst-kix_yn1p6euud4dt-4>li:before{content:"" counter(lst-ctn-kix_yn1p6euud4dt-4,lower-latin) ". "}ol.lst-kix_n34nhxg9gk9-5.start{counter-reset:lst-ctn-kix_n34nhxg9gk9-5 0}ol.lst-kix_dfignz4kto-4.start{counter-reset:lst-ctn-kix_dfignz4kto-4 0}.lst-kix_41dur2ixsyfx-7>li:before{content:"" counter(lst-ctn-kix_41dur2ixsyfx-7,lower-latin) ". "}ol.lst-kix_s7qtkfn3qfov-5.start{counter-reset:lst-ctn-kix_s7qtkfn3qfov-5 0}.lst-kix_s2bakucqamn4-2>li{counter-increment:lst-ctn-kix_s2bakucqamn4-2}ol.lst-kix_2atftb74ca0r-8.start{counter-reset:lst-ctn-kix_2atftb74ca0r-8 0}.lst-kix_2atftb74ca0r-0>li{counter-increment:lst-ctn-kix_2atftb74ca0r-0}ol.lst-kix_v82n41p8858f-7.start{counter-reset:lst-ctn-kix_v82n41p8858f-7 0}ol.lst-kix_s7qtkfn3qfov-2.start{counter-reset:lst-ctn-kix_s7qtkfn3qfov-2 0}.lst-kix_vcalqlf3fy1b-4>li{counter-increment:lst-ctn-kix_vcalqlf3fy1b-4}.lst-kix_79xr8vp1jz9w-4>li{counter-increment:lst-ctn-kix_79xr8vp1jz9w-4}.lst-kix_s8u2k5vztyaw-3>li:before{content:"" counter(lst-ctn-kix_s8u2k5vztyaw-3,decimal) ". "}ol.lst-kix_8ndrlnfdlj1m-2.start{counter-reset:lst-ctn-kix_8ndrlnfdlj1m-2 0}ol.lst-kix_1xkddpu9nsxr-1.start{counter-reset:lst-ctn-kix_1xkddpu9nsxr-1 0}ol.lst-kix_dfignz4kto-2.start{counter-reset:lst-ctn-kix_dfignz4kto-2 0}.lst-kix_79xr8vp1jz9w-3>li{counter-increment:lst-ctn-kix_79xr8vp1jz9w-3}.lst-kix_mz7gq3g0njgs-0>li:before{content:"" counter(lst-ctn-kix_mz7gq3g0njgs-0,decimal) ". "}ol.lst-kix_41dur2ixsyfx-7.start{counter-reset:lst-ctn-kix_41dur2ixsyfx-7 0}.lst-kix_s8u2k5vztyaw-6>li{counter-increment:lst-ctn-kix_s8u2k5vztyaw-6}ol.lst-kix_yn1p6euud4dt-5.start{counter-reset:lst-ctn-kix_yn1p6euud4dt-5 0}ol.lst-kix_n34nhxg9gk9-7.start{counter-reset:lst-ctn-kix_n34nhxg9gk9-7 0}.lst-kix_mz7gq3g0njgs-2>li:before{content:"" counter(lst-ctn-kix_mz7gq3g0njgs-2,lower-roman) ". "}.lst-kix_mz7gq3g0njgs-4>li:before{content:"" counter(lst-ctn-kix_mz7gq3g0njgs-4,lower-latin) ". "}.lst-kix_t794825wy5z-3>li{counter-increment:lst-ctn-kix_t794825wy5z-3}.lst-kix_mz7gq3g0njgs-6>li:before{content:"" counter(lst-ctn-kix_mz7gq3g0njgs-6,decimal) ". "}.lst-kix_2atftb74ca0r-7>li{counter-increment:lst-ctn-kix_2atftb74ca0r-7}ol.lst-kix_79xr8vp1jz9w-3.start{counter-reset:lst-ctn-kix_79xr8vp1jz9w-3 0}.lst-kix_1xkddpu9nsxr-1>li{counter-increment:lst-ctn-kix_1xkddpu9nsxr-1}.lst-kix_vcalqlf3fy1b-3>li{counter-increment:lst-ctn-kix_vcalqlf3fy1b-3}ol.lst-kix_v82n41p8858f-5.start{counter-reset:lst-ctn-kix_v82n41p8858f-5 0}.lst-kix_41dur2ixsyfx-8>li{counter-increment:lst-ctn-kix_41dur2ixsyfx-8}ol.lst-kix_t794825wy5z-0.start{counter-reset:lst-ctn-kix_t794825wy5z-0 0}.lst-kix_yn1p6euud4dt-3>li{counter-increment:lst-ctn-kix_yn1p6euud4dt-3}ol.lst-kix_m7n4vga7agj0-0.start{counter-reset:lst-ctn-kix_m7n4vga7agj0-0 0}.lst-kix_lgfdoxi035fa-1>li{counter-increment:lst-ctn-kix_lgfdoxi035fa-1}.lst-kix_n34nhxg9gk9-7>li{counter-increment:lst-ctn-kix_n34nhxg9gk9-7}.lst-kix_v82n41p8858f-6>li{counter-increment:lst-ctn-kix_v82n41p8858f-6}ol.lst-kix_vcalqlf3fy1b-1.start{counter-reset:lst-ctn-kix_vcalqlf3fy1b-1 0}.lst-kix_m7n4vga7agj0-5>li:before{content:"" counter(lst-ctn-kix_m7n4vga7agj0-5,lower-roman) ". "}ol.lst-kix_dfignz4kto-0{list-style-type:none}ol.lst-kix_97v7yur38q2r-5.start{counter-reset:lst-ctn-kix_97v7yur38q2r-5 0}.lst-kix_dfignz4kto-2>li{counter-increment:lst-ctn-kix_dfignz4kto-2}.lst-kix_m7n4vga7agj0-6>li:before{content:"" counter(lst-ctn-kix_m7n4vga7agj0-6,decimal) ". "}ol.lst-kix_dfignz4kto-7{list-style-type:none}ol.lst-kix_dfignz4kto-8{list-style-type:none}ol.lst-kix_lgfdoxi035fa-3.start{counter-reset:lst-ctn-kix_lgfdoxi035fa-3 0}ol.lst-kix_dfignz4kto-5{list-style-type:none}ol.lst-kix_dfignz4kto-6{list-style-type:none}ol.lst-kix_dfignz4kto-3{list-style-type:none}ol.lst-kix_dfignz4kto-4{list-style-type:none}ol.lst-kix_dfignz4kto-1{list-style-type:none}ol.lst-kix_dfignz4kto-2{list-style-type:none}ol.lst-kix_t794825wy5z-0{list-style-type:none}.lst-kix_2atftb74ca0r-4>li{counter-increment:lst-ctn-kix_2atftb74ca0r-4}ol.lst-kix_t794825wy5z-2{list-style-type:none}ol.lst-kix_t794825wy5z-1{list-style-type:none}.lst-kix_s7qtkfn3qfov-8>li:before{content:"" counter(lst-ctn-kix_s7qtkfn3qfov-8,lower-roman) ". "}ol.lst-kix_t794825wy5z-1.start{counter-reset:lst-ctn-kix_t794825wy5z-1 0}ol.lst-kix_t794825wy5z-4{list-style-type:none}.lst-kix_97v7yur38q2r-4>li{counter-increment:lst-ctn-kix_97v7yur38q2r-4}ol.lst-kix_t794825wy5z-3{list-style-type:none}ol.lst-kix_t794825wy5z-6{list-style-type:none}ol.lst-kix_t794825wy5z-5{list-style-type:none}ol.lst-kix_t794825wy5z-8{list-style-type:none}.lst-kix_m7n4vga7agj0-1>li:before{content:"" counter(lst-ctn-kix_m7n4vga7agj0-1,lower-latin) ". "}ol.lst-kix_t794825wy5z-7{list-style-type:none}.lst-kix_m7n4vga7agj0-2>li:before{content:"" counter(lst-ctn-kix_m7n4vga7agj0-2,lower-roman) ". "}.lst-kix_s2bakucqamn4-8>li:before{content:"" counter(lst-ctn-kix_s2bakucqamn4-8,lower-roman) ". "}.lst-kix_s7qtkfn3qfov-7>li:before{content:"" counter(lst-ctn-kix_s7qtkfn3qfov-7,lower-latin) ". "}ol.lst-kix_97v7yur38q2r-0{list-style-type:none}ol.lst-kix_97v7yur38q2r-1{list-style-type:none}.lst-kix_2atftb74ca0r-6>li{counter-increment:lst-ctn-kix_2atftb74ca0r-6}ol.lst-kix_mz7gq3g0njgs-1.start{counter-reset:lst-ctn-kix_mz7gq3g0njgs-1 0}ol.lst-kix_s7qtkfn3qfov-8{list-style-type:none}.lst-kix_s2bakucqamn4-7>li:before{content:"" counter(lst-ctn-kix_s2bakucqamn4-7,lower-latin) ". "}ol.lst-kix_s7qtkfn3qfov-4{list-style-type:none}ol.lst-kix_s7qtkfn3qfov-5{list-style-type:none}.lst-kix_79xr8vp1jz9w-5>li:before{content:"" counter(lst-ctn-kix_79xr8vp1jz9w-5,lower-roman) ". "}ol.lst-kix_s7qtkfn3qfov-6{list-style-type:none}ol.lst-kix_s7qtkfn3qfov-7{list-style-type:none}.lst-kix_79xr8vp1jz9w-4>li:before{content:"" counter(lst-ctn-kix_79xr8vp1jz9w-4,lower-latin) ". "}.lst-kix_s2bakucqamn4-3>li:before{content:"" counter(lst-ctn-kix_s2bakucqamn4-3,decimal) ". "}.lst-kix_8ndrlnfdlj1m-3>li{counter-increment:lst-ctn-kix_8ndrlnfdlj1m-3}.lst-kix_s2bakucqamn4-4>li:before{content:"" counter(lst-ctn-kix_s2bakucqamn4-4,lower-latin) ". "}ol.lst-kix_dfignz4kto-0.start{counter-reset:lst-ctn-kix_dfignz4kto-0 0}.lst-kix_dfignz4kto-4>li{counter-increment:lst-ctn-kix_dfignz4kto-4}.lst-kix_79xr8vp1jz9w-0>li:before{content:"" counter(lst-ctn-kix_79xr8vp1jz9w-0,decimal) ". "}ol.lst-kix_97v7yur38q2r-6{list-style-type:none}ol.lst-kix_97v7yur38q2r-7{list-style-type:none}ol.lst-kix_vcalqlf3fy1b-6.start{counter-reset:lst-ctn-kix_vcalqlf3fy1b-6 0}ol.lst-kix_97v7yur38q2r-8{list-style-type:none}.lst-kix_s2bakucqamn4-0>li:before{content:"" counter(lst-ctn-kix_s2bakucqamn4-0,decimal) ". "}ol.lst-kix_97v7yur38q2r-2{list-style-type:none}ol.lst-kix_97v7yur38q2r-3{list-style-type:none}.lst-kix_79xr8vp1jz9w-1>li:before{content:"" counter(lst-ctn-kix_79xr8vp1jz9w-1,lower-latin) ". "}ol.lst-kix_97v7yur38q2r-4{list-style-type:none}ol.lst-kix_yn1p6euud4dt-2.start{counter-reset:lst-ctn-kix_yn1p6euud4dt-2 0}ol.lst-kix_97v7yur38q2r-5{list-style-type:none}.lst-kix_yn1p6euud4dt-7>li{counter-increment:lst-ctn-kix_yn1p6euud4dt-7}.lst-kix_97v7yur38q2r-6>li:before{content:"" counter(lst-ctn-kix_97v7yur38q2r-6,decimal) ". "}.lst-kix_41dur2ixsyfx-1>li{counter-increment:lst-ctn-kix_41dur2ixsyfx-1}ol.lst-kix_1xkddpu9nsxr-8.start{counter-reset:lst-ctn-kix_1xkddpu9nsxr-8 0}.lst-kix_97v7yur38q2r-7>li:before{content:"" counter(lst-ctn-kix_97v7yur38q2r-7,lower-latin) ". "}ol.lst-kix_v82n41p8858f-4.start{counter-reset:lst-ctn-kix_v82n41p8858f-4 0}ol.lst-kix_97v7yur38q2r-0.start{counter-reset:lst-ctn-kix_97v7yur38q2r-0 0}ol.lst-kix_lgfdoxi035fa-8.start{counter-reset:lst-ctn-kix_lgfdoxi035fa-8 0}.lst-kix_lgfdoxi035fa-2>li{counter-increment:lst-ctn-kix_lgfdoxi035fa-2}.lst-kix_97v7yur38q2r-3>li:before{content:"" counter(lst-ctn-kix_97v7yur38q2r-3,decimal) ". "}.lst-kix_t794825wy5z-2>li:before{content:"" counter(lst-ctn-kix_t794825wy5z-2,lower-roman) ". "}.lst-kix_lgfdoxi035fa-1>li:before{content:"" counter(lst-ctn-kix_lgfdoxi035fa-1,lower-latin) ". "}ol.lst-kix_mz7gq3g0njgs-0.start{counter-reset:lst-ctn-kix_mz7gq3g0njgs-0 0}.lst-kix_t794825wy5z-3>li:before{content:"" counter(lst-ctn-kix_t794825wy5z-3,decimal) ". "}.lst-kix_lgfdoxi035fa-0>li:before{content:"" counter(lst-ctn-kix_lgfdoxi035fa-0,decimal) ". "}.lst-kix_m7n4vga7agj0-0>li{counter-increment:lst-ctn-kix_m7n4vga7agj0-0}ol.lst-kix_s7qtkfn3qfov-0{list-style-type:none}ol.lst-kix_vcalqlf3fy1b-5.start{counter-reset:lst-ctn-kix_vcalqlf3fy1b-5 0}ol.lst-kix_s7qtkfn3qfov-1{list-style-type:none}ol.lst-kix_s7qtkfn3qfov-2{list-style-type:none}ol.lst-kix_s7qtkfn3qfov-3{list-style-type:none}.lst-kix_1xkddpu9nsxr-2>li{counter-increment:lst-ctn-kix_1xkddpu9nsxr-2}ol.lst-kix_yn1p6euud4dt-1.start{counter-reset:lst-ctn-kix_yn1p6euud4dt-1 0}.lst-kix_79xr8vp1jz9w-8>li:before{content:"" counter(lst-ctn-kix_79xr8vp1jz9w-8,lower-roman) ". "}.lst-kix_yn1p6euud4dt-2>li{counter-increment:lst-ctn-kix_yn1p6euud4dt-2}.lst-kix_n34nhxg9gk9-0>li:before{content:"" counter(lst-ctn-kix_n34nhxg9gk9-0,decimal) ". "}.lst-kix_79xr8vp1jz9w-5>li{counter-increment:lst-ctn-kix_79xr8vp1jz9w-5}ol.lst-kix_v82n41p8858f-3.start{counter-reset:lst-ctn-kix_v82n41p8858f-3 0}.lst-kix_dfignz4kto-0>li{counter-increment:lst-ctn-kix_dfignz4kto-0}.lst-kix_s7qtkfn3qfov-4>li:before{content:"" counter(lst-ctn-kix_s7qtkfn3qfov-4,lower-latin) ". "}.lst-kix_s7qtkfn3qfov-3>li:before{content:"" counter(lst-ctn-kix_s7qtkfn3qfov-3,decimal) ". "}.lst-kix_lgfdoxi035fa-8>li:before{content:"" counter(lst-ctn-kix_lgfdoxi035fa-8,lower-roman) ". "}.lst-kix_t794825wy5z-6>li:before{content:"" counter(lst-ctn-kix_t794825wy5z-6,decimal) ". "}.lst-kix_lgfdoxi035fa-5>li:before{content:"" counter(lst-ctn-kix_lgfdoxi035fa-5,lower-roman) ". "}ol.lst-kix_1xkddpu9nsxr-3.start{counter-reset:lst-ctn-kix_1xkddpu9nsxr-3 0}.lst-kix_97v7yur38q2r-2>li:before{content:"" counter(lst-ctn-kix_97v7yur38q2r-2,lower-roman) ". "}.lst-kix_2atftb74ca0r-2>li{counter-increment:lst-ctn-kix_2atftb74ca0r-2}.lst-kix_s7qtkfn3qfov-0>li:before{content:"" counter(lst-ctn-kix_s7qtkfn3qfov-0,decimal) ". "}.lst-kix_t794825wy5z-7>li:before{content:"" counter(lst-ctn-kix_t794825wy5z-7,lower-latin) ". "}.lst-kix_lgfdoxi035fa-4>li:before{content:"" counter(lst-ctn-kix_lgfdoxi035fa-4,lower-latin) ". "}.lst-kix_41dur2ixsyfx-4>li:before{content:"" counter(lst-ctn-kix_41dur2ixsyfx-4,lower-latin) ". "}.lst-kix_s8u2k5vztyaw-0>li:before{content:"" counter(lst-ctn-kix_s8u2k5vztyaw-0,decimal) ". "}.lst-kix_yn1p6euud4dt-7>li:before{content:"" counter(lst-ctn-kix_yn1p6euud4dt-7,lower-latin) ". "}ol.lst-kix_vcalqlf3fy1b-7.start{counter-reset:lst-ctn-kix_vcalqlf3fy1b-7 0}.lst-kix_lgfdoxi035fa-4>li{counter-increment:lst-ctn-kix_lgfdoxi035fa-4}ol.lst-kix_v82n41p8858f-2.start{counter-reset:lst-ctn-kix_v82n41p8858f-2 0}ol.lst-kix_yn1p6euud4dt-0.start{counter-reset:lst-ctn-kix_yn1p6euud4dt-0 0}.lst-kix_yn1p6euud4dt-3>li:before{content:"" counter(lst-ctn-kix_yn1p6euud4dt-3,decimal) ". "}ol.lst-kix_t794825wy5z-8.start{counter-reset:lst-ctn-kix_t794825wy5z-8 0}.lst-kix_s8u2k5vztyaw-8>li:before{content:"" counter(lst-ctn-kix_s8u2k5vztyaw-8,lower-roman) ". "}.lst-kix_v82n41p8858f-4>li{counter-increment:lst-ctn-kix_v82n41p8858f-4}.lst-kix_yn1p6euud4dt-5>li{counter-increment:lst-ctn-kix_yn1p6euud4dt-5}.lst-kix_41dur2ixsyfx-8>li:before{content:"" counter(lst-ctn-kix_41dur2ixsyfx-8,lower-roman) ". "}ol.lst-kix_t794825wy5z-5.start{counter-reset:lst-ctn-kix_t794825wy5z-5 0}ol.lst-kix_1xkddpu9nsxr-7.start{counter-reset:lst-ctn-kix_1xkddpu9nsxr-7 0}.lst-kix_vcalqlf3fy1b-5>li:before{content:"" counter(lst-ctn-kix_vcalqlf3fy1b-5,lower-roman) ". "}ol.lst-kix_97v7yur38q2r-6.start{counter-reset:lst-ctn-kix_97v7yur38q2r-6 0}.lst-kix_n34nhxg9gk9-4>li:before{content:"" counter(lst-ctn-kix_n34nhxg9gk9-4,lower-latin) ". "}.lst-kix_t794825wy5z-0>li{counter-increment:lst-ctn-kix_t794825wy5z-0}ol.lst-kix_lgfdoxi035fa-2.start{counter-reset:lst-ctn-kix_lgfdoxi035fa-2 0}ol.lst-kix_1xkddpu9nsxr-4.start{counter-reset:lst-ctn-kix_1xkddpu9nsxr-4 0}.lst-kix_n34nhxg9gk9-8>li:before{content:"" counter(lst-ctn-kix_n34nhxg9gk9-8,lower-roman) ". "}ol.lst-kix_lgfdoxi035fa-5.start{counter-reset:lst-ctn-kix_lgfdoxi035fa-5 0}.lst-kix_dfignz4kto-7>li{counter-increment:lst-ctn-kix_dfignz4kto-7}.lst-kix_vcalqlf3fy1b-1>li:before{content:"" counter(lst-ctn-kix_vcalqlf3fy1b-1,lower-latin) ". "}.lst-kix_41dur2ixsyfx-0>li:before{content:"" counter(lst-ctn-kix_41dur2ixsyfx-0,decimal) ". "}.lst-kix_s8u2k5vztyaw-4>li:before{content:"" counter(lst-ctn-kix_s8u2k5vztyaw-4,lower-latin) ". "}ol.lst-kix_97v7yur38q2r-3.start{counter-reset:lst-ctn-kix_97v7yur38q2r-3 0}.lst-kix_79xr8vp1jz9w-7>li{counter-increment:lst-ctn-kix_79xr8vp1jz9w-7}.lst-kix_8ndrlnfdlj1m-1>li{counter-increment:lst-ctn-kix_8ndrlnfdlj1m-1}.lst-kix_97v7yur38q2r-2>li{counter-increment:lst-ctn-kix_97v7yur38q2r-2}ol.lst-kix_1xkddpu9nsxr-5.start{counter-reset:lst-ctn-kix_1xkddpu9nsxr-5 0}ol.lst-kix_lgfdoxi035fa-4.start{counter-reset:lst-ctn-kix_lgfdoxi035fa-4 0}.lst-kix_s2bakucqamn4-6>li{counter-increment:lst-ctn-kix_s2bakucqamn4-6}.lst-kix_mz7gq3g0njgs-3>li:before{content:"" counter(lst-ctn-kix_mz7gq3g0njgs-3,decimal) ". "}ol.lst-kix_v82n41p8858f-0.start{counter-reset:lst-ctn-kix_v82n41p8858f-0 0}ol.lst-kix_97v7yur38q2r-4.start{counter-reset:lst-ctn-kix_97v7yur38q2r-4 0}ol.lst-kix_t794825wy5z-6.start{counter-reset:lst-ctn-kix_t794825wy5z-6 0}.lst-kix_2atftb74ca0r-5>li:before{content:"" counter(lst-ctn-kix_2atftb74ca0r-5,lower-roman) ". "}.lst-kix_8ndrlnfdlj1m-7>li:before{content:"" counter(lst-ctn-kix_8ndrlnfdlj1m-7,lower-latin) ". "}ol.lst-kix_vcalqlf3fy1b-8.start{counter-reset:lst-ctn-kix_vcalqlf3fy1b-8 0}.lst-kix_vcalqlf3fy1b-0>li{counter-increment:lst-ctn-kix_vcalqlf3fy1b-0}.lst-kix_yn1p6euud4dt-0>li{counter-increment:lst-ctn-kix_yn1p6euud4dt-0}.lst-kix_1xkddpu9nsxr-4>li{counter-increment:lst-ctn-kix_1xkddpu9nsxr-4}ol.lst-kix_t794825wy5z-7.start{counter-reset:lst-ctn-kix_t794825wy5z-7 0}.lst-kix_79xr8vp1jz9w-0>li{counter-increment:lst-ctn-kix_79xr8vp1jz9w-0}ol.lst-kix_1xkddpu9nsxr-6.start{counter-reset:lst-ctn-kix_1xkddpu9nsxr-6 0}.lst-kix_n34nhxg9gk9-4>li{counter-increment:lst-ctn-kix_n34nhxg9gk9-4}.lst-kix_dfignz4kto-1>li:before{content:"" counter(lst-ctn-kix_dfignz4kto-1,lower-latin) ". "}.lst-kix_8ndrlnfdlj1m-3>li:before{content:"" counter(lst-ctn-kix_8ndrlnfdlj1m-3,decimal) ". "}.lst-kix_m7n4vga7agj0-5>li{counter-increment:lst-ctn-kix_m7n4vga7agj0-5}ol{margin:0;padding:0}table td,table th{padding:0}.c19{border-right-style:solid;padding:5pt 5pt 5pt 5pt;border-bottom-color:#e0e0e0;border-top-width:1pt;border-right-width:1pt;border-left-color:#e0e0e0;vertical-align:top;border-right-color:#e0e0e0;border-left-width:1pt;border-top-style:solid;background-color:#fafafa;border-left-style:solid;border-bottom-width:1pt;width:468pt;border-top-color:#e0e0e0;border-bottom-style:solid}.c27{margin-left:72pt;padding-top:0pt;padding-bottom:0pt;line-height:1.5;orphans:2;widows:2;text-align:center;margin-right:72pt}.c21{padding-top:20pt;padding-bottom:6pt;line-height:1.5;page-break-after:avoid;orphans:2;widows:2;text-align:left}.c25{padding-top:18pt;padding-bottom:6pt;line-height:1.5;page-break-after:avoid;orphans:2;widows:2;text-align:left}.c33{padding-top:0pt;padding-bottom:0pt;line-height:1.5;orphans:2;widows:2;text-align:center}.c6{padding-top:0pt;padding-bottom:0pt;line-height:1.5;orphans:2;widows:2;text-align:left}.c4{color:#000000;text-decoration:none;vertical-align:baseline;font-size:11pt;font-style:normal}.c1{font-size:10pt;font-family:"Consolas";color:#000000;font-weight:400}.c11{text-decoration-skip-ink:none;-webkit-text-decoration-skip:none;color:#1155cc;text-decoration:underline}.c20{padding-top:0pt;padding-bottom:0pt;line-height:1.0;text-align:left}.c2{font-size:10pt;font-family:"Consolas";color:#9c27b0;font-weight:400}.c0{font-size:10pt;font-family:"Consolas";color:#616161;font-weight:400}.c16{font-size:10pt;font-family:"Consolas";color:#c53929;font-weight:400}.c35{font-size:10pt;font-family:"Consolas";color:#0f9d58;font-weight:400}.c23{font-size:10pt;font-family:"Consolas";color:#455a64;font-weight:400}.c30{color:#000000;text-decoration:none;vertical-align:baseline;font-size:11pt}.c10{border-spacing:0;border-collapse:collapse;margin-right:auto}.c9{padding-top:0pt;padding-bottom:0pt;line-height:1.5;text-align:left}.c18{background-color:#ffffff;max-width:468pt;padding:72pt 72pt 72pt 72pt}.c5{text-decoration:none;vertical-align:baseline;font-style:normal}.c32{padding:0;margin:0}.c13{color:#008200;font-size:11pt}.c22{margin-left:36pt;padding-left:0pt}.c26{color:inherit;text-decoration:inherit}.c12{font-weight:700;font-family:"Courier New"}.c17{color:#000000;font-size:20pt}.c28{color:#000000;font-size:16pt}.c36{border:1px solid black;margin:5px}.c3{font-weight:400;font-family:"Courier New"}.c14{color:#000000;font-size:9pt}.c7{font-weight:400;font-family:"Arial"}.c8{height:11pt}.c34{font-size:11pt}.c29{color:#0a0085}.c31{color:#008200}.c15{height:0pt}.c24{font-style:italic}.title{padding-top:0pt;color:#000000;font-size:26pt;padding-bottom:3pt;font-family:"Arial";line-height:1.5;page-break-after:avoid;orphans:2;widows:2;text-align:left}.subtitle{padding-top:0pt;color:#666666;font-size:15pt;padding-bottom:16pt;font-family:"Arial";line-height:1.5;page-break-after:avoid;orphans:2;widows:2;text-align:left}li{color:#000000;font-size:11pt;font-family:"Arial"}p{margin:0;color:#000000;font-size:11pt;font-family:"Arial"}h1{padding-top:20pt;color:#000000;font-size:20pt;padding-bottom:6pt;font-family:"Arial";line-height:1.5;page-break-after:avoid;orphans:2;widows:2;text-align:left}h2{padding-top:18pt;color:#000000;font-size:16pt;padding-bottom:6pt;font-family:"Arial";line-height:1.5;page-break-after:avoid;orphans:2;widows:2;text-align:left}h3{padding-top:16pt;color:#434343;font-size:14pt;padding-bottom:4pt;font-family:"Arial";line-height:1.5;page-break-after:avoid;orphans:2;widows:2;text-align:left}h4{padding-top:14pt;color:#666666;font-size:12pt;padding-bottom:4pt;font-family:"Arial";line-height:1.5;page-break-after:avoid;orphans:2;widows:2;text-align:left}h5{padding-top:12pt;color:#666666;font-size:11pt;padding-bottom:4pt;font-family:"Arial";line-height:1.5;page-break-after:avoid;orphans:2;widows:2;text-align:left}h6{padding-top:12pt;color:#666666;font-size:11pt;padding-bottom:4pt;font-family:"Arial";line-height:1.5;page-break-after:avoid;font-style:italic;orphans:2;widows:2;text-align:left}</style></head><body class="c18"><p class="c6"><span class="c4 c7">Written by Brandon Azad, when working at Project Zero<br></span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span class="c4 c7">One of the amazing aspects of working at Project Zero is having the flexibility to direct my own research agenda. My prior work has almost exclusively focused on iOS exploitation, but back in August, I thought it could be interesting to try writing a kernel exploit for Android to see how it compares. I have two aims for this blog post: First, I will walk you through my full journey from bug description to kernel read/write/execute on the Samsung Galaxy S10, starting from the perspective of a pure-iOS security researcher. Second, I will try to emphasize some of the major security/exploitation differences between the two platforms that I have observed.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>You can find the fully-commented exploit code attached in issue </span><span class="c11"><a class="c26" href="https://www.google.com/url?q=https://bugs.chromium.org/p/project-zero/issues/detail?id%3D2073%23c1&sa=D&ust=1608578955006000&usg=AOvVaw3UPfNIWRfIVLvQA7Uep8n5">2073</a></span><span class="c4 c7">. </span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span class="c4 c7">In November 2020, Samsung released a patch addressing the issue for devices that are eligible for receiving security updates. This issue was assigned CVE-2020-28343 and a Samsung-specific SVE-2020-18610.</span></p><h1 class="c21" id="h.u4uc2587hqn9"><span class="c5 c17 c7">The initial vulnerability report</span></h1><p class="c6"><span>In early August, fellow Google Project Zero researcher </span><span class="c11"><a class="c26" href="https://www.google.com/url?q=https://twitter.com/benhawkes&sa=D&ust=1608578955006000&usg=AOvVaw1rF_F5FoSd-EX00q18bHpq">Ben Hawkes</a></span><span> reported several </span><span class="c11"><a class="c26" href="https://www.google.com/url?q=https://bugs.chromium.org/p/project-zero/issues/detail?id%3D2073&sa=D&ust=1608578955007000&usg=AOvVaw09mW9P0adU3guzPW2dQki8">vulnerabilities</a></span><span> in the Samsung </span><span class="c11"><a class="c26" href="https://www.google.com/url?q=https://www.samsung.com/global/galaxy/what-is/npu/&sa=D&ust=1608578955007000&usg=AOvVaw0qcoEFkqwaU015eUgdfCyA">Neural Processing Unit</a></span><span> (NPU) kernel </span><span class="c11"><a class="c26" href="https://www.google.com/url?q=https://opensource.samsung.com/uploadSearch?searchValue%3Dsm-g973f&sa=D&ust=1608578955007000&usg=AOvVaw1qTHnoqa4kN9yny_bEheE4">driver</a></span><span class="c4 c7"> due to a complete lack of input sanitization. At a high level, the NPU is a coprocessor dedicated to machine learning and neural network computations, especially for visual processing. The bugs Ben found were in Samsug's Android kernel driver for the NPU, responsible (among other things) for sending neural models from an Android app in userspace over to the NPU coprocessor.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span class="c4 c7">The following few sections will look at the vulnerabilities. These bugs aren't actually that interesting: they are quite ordinary out-of-bounds writes due to unsanitized input, and could almost be treated as black-box primitives for the purposes of writing an exploit (which was my ultimate goal). The primitives are quite strong and, to my knowledge, no novel techniques are required to build a practical exploit out of them in practice. However, since I was coming from the perspective of never having written a Linux kernel exploit, understanding these bugs and their constraints in detail was crucial for me to begin to visualize how they might be used to gain kernel read/write.</span></p><h2 class="c25" id="h.mm2wz85dr0q"><span class="c5 c28 c7">🤖 📱 🧠 👓 🐛🐛</span></h2><p class="c6"><span>The vulnerabilities are reached by opening a handle to the device </span><span class="c3">/dev/vertex10</span><span>, which Ben had determined is accessible to a normal Android app. Calling </span><span class="c3">open()</span><span> on this device causes the kernel to allocate and initialize a new </span><span class="c3">npu_session</span><span> object that gets associated with the returned file descriptor. The process can then invoke </span><span class="c3">ioctl()</span><span class="c4 c7"> on the file descriptor to interact with the session. For example, consider the following code:</span></p><p class="c6 c8"><span class="c4 c7"></span></p><a id="t.a1079553430700260100dd0db21d8c3afa6e3c24"></a><a id="t.0"></a><table class="c10"><tbody><tr class="c15"><td class="c19" colspan="1" rowspan="1"><p class="c9"><span class="c2">int</span><span class="c1"> ncp_fd </span><span class="c0">=</span><span class="c1"> open</span><span class="c0">(</span><span class="c35">"/dev/vertex10"</span><span class="c0">,</span><span class="c1"> O_RDONLY</span><span class="c0">);</span></p><p class="c9"><span class="c2">struct</span><span class="c1"> vs4l_graph vs4l_graph </span><span class="c0">=</span><span class="c1"> </span><span class="c23">/* ioctl data */</span><span class="c0">;</span></p><p class="c9"><span class="c1">ioctl</span><span class="c0">(</span><span class="c1">ncp_fd</span><span class="c0">,</span><span class="c1"> VS4L_VERTEXIOC_S_GRAPH</span><span class="c0">,</span><span class="c1"> </span><span class="c0">&</span><span class="c1">vs4l_graph</span><span class="c0">);</span></p></td></tr></tbody></table><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>The preceding syscalls will result in the kernel calling the function </span><span class="c3">npu_vertex_s_graph()</span><span>, which calls </span><span class="c3">npu_session_s_graph()</span><span> on the associated </span><span class="c3">npu_session</span><span> (see </span><span class="c3">drivers/vision/npu/npu-vertex.c</span><span>). </span><span class="c3">npu_session_s_graph()</span><span> then calls </span><span class="c3">__config_session_info()</span><span> to parse the input data to this ioctl (see </span><span class="c3">npu-session.c</span><span class="c4 c7">):</span></p><p class="c6 c8"><span class="c4 c7"></span></p><a id="t.ca631e95957f0a88649c9bd5ff8f29af4f654e3f"></a><a id="t.1"></a><table class="c10"><tbody><tr class="c15"><td class="c19" colspan="1" rowspan="1"><p class="c9"><span class="c2">int</span><span class="c1"> __config_session_info</span><span class="c0">(</span><span class="c2">struct</span><span class="c1"> npu_session </span><span class="c0">*</span><span class="c1">session</span><span class="c0">)</span></p><p class="c9"><span class="c0">{</span></p><p class="c9"><span class="c0">...</span></p><p class="c9"><span class="c1"> ret </span><span class="c0">=</span><span class="c1"> __pilot_parsing_ncp</span><span class="c0">(</span><span class="c1">session</span><span class="c0">,</span><span class="c1"> </span><span class="c0">&</span><span class="c1">temp_IFM_cnt</span><span class="c0">,</span><span class="c1"> </span><span class="c0">&</span><span class="c1">temp_OFM_cnt</span><span class="c0">,</span><span class="c1"> </span><span class="c0">&</span><span class="c1">temp_IMB_cnt</span><span class="c0">,</span><span class="c1"> </span><span class="c0">&</span><span class="c1">WGT_cnt</span><span class="c0">);</span></p><p class="c9 c8"><span class="c1 c5"></span></p><p class="c9"><span class="c1"> temp_IFM_av </span><span class="c0">=</span><span class="c1"> kcalloc</span><span class="c0">(</span><span class="c1">temp_IFM_cnt</span><span class="c0">,</span><span class="c1"> </span><span class="c2">sizeof</span><span class="c0">(</span><span class="c2">struct</span><span class="c1"> temp_av</span><span class="c0">),</span><span class="c1"> GFP_KERNEL</span><span class="c0">);</span></p><p class="c9"><span class="c1"> temp_OFM_av </span><span class="c0">=</span><span class="c1"> kcalloc</span><span class="c0">(</span><span class="c1">temp_OFM_cnt</span><span class="c0">,</span><span class="c1"> </span><span class="c2">sizeof</span><span class="c0">(</span><span class="c2">struct</span><span class="c1"> temp_av</span><span class="c0">),</span><span class="c1"> GFP_KERNEL</span><span class="c0">);</span></p><p class="c9"><span class="c1"> temp_IMB_av </span><span class="c0">=</span><span class="c1"> kcalloc</span><span class="c0">(</span><span class="c1">temp_IMB_cnt</span><span class="c0">,</span><span class="c1"> </span><span class="c2">sizeof</span><span class="c0">(</span><span class="c2">struct</span><span class="c1"> temp_av</span><span class="c0">),</span><span class="c1"> GFP_KERNEL</span><span class="c0">);</span></p><p class="c9"><span class="c0">...</span></p><p class="c9"><span class="c1"> ret </span><span class="c0">=</span><span class="c1"> __second_parsing_ncp</span><span class="c0">(</span><span class="c1">session</span><span class="c0">,</span><span class="c1"> </span><span class="c0">&</span><span class="c1">temp_IFM_av</span><span class="c0">,</span><span class="c1"> </span><span class="c0">&</span><span class="c1">temp_OFM_av</span><span class="c0">,</span><span class="c1"> </span><span class="c0">&</span><span class="c1">temp_IMB_av</span><span class="c0">,</span><span class="c1"> </span><span class="c0">&</span><span class="c1">WGT_av</span><span class="c0">);</span></p><p class="c9"><span class="c0">...</span></p><p class="c9"><span class="c0">}</span></p></td></tr></tbody></table><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>As can be seen above, </span><span class="c3">__config_session_info()</span><span> calls two other functions to perform the actual parsing of the input: </span><span class="c3">__pilot_parsing_ncp()</span><span> and </span><span class="c3">__second_parsing_ncp()</span><span>. As their names suggest, </span><span class="c3">__pilot_parsing_ncp()</span><span> performs a preliminary "pilot" parsing of the input data in order to compute the sizes of a few arrays that will need to be allocated for the full parsing; meanwhile, </span><span class="c3">__second_parsing_ncp()</span><span class="c4 c7"> performs the full parsing, populating the arrays that were just allocated.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>Let's take a quick look at </span><span class="c3">__pilot_parsing_ncp()</span><span class="c4 c7">:</span></p><p class="c6 c8"><span class="c4 c7"></span></p><a id="t.1f5a019d1b0ee60533bfe2f7ad1c4a0efe2c8acd"></a><a id="t.2"></a><table class="c10"><tbody><tr class="c15"><td class="c19" colspan="1" rowspan="1"><p class="c9"><span class="c2">int</span><span class="c1"> __pilot_parsing_ncp</span><span class="c0">(</span><span class="c2">struct</span><span class="c1"> npu_session </span><span class="c0">*</span><span class="c1">session</span><span class="c0">,</span><span class="c1"> u32 </span><span class="c0">*</span><span class="c1">IFM_cnt</span><span class="c0">,</span><span class="c1"> u32 </span><span class="c0">*</span><span class="c1">OFM_cnt</span><span class="c0">,</span><span class="c1"> u32 </span><span class="c0">*</span><span class="c1">IMB_cnt</span><span class="c0">,</span><span class="c1"> u32 </span><span class="c0">*</span><span class="c1">WGT_cnt</span><span class="c0">)</span></p><p class="c9"><span class="c0">{</span></p><p class="c9"><span class="c0">...</span></p><p class="c9"><span class="c1"> ncp_vaddr </span><span class="c0">=</span><span class="c1"> </span><span class="c0">(</span><span class="c2">char</span><span class="c1"> </span><span class="c0">*)</span><span class="c1">session</span><span class="c0">-></span><span class="c1">ncp_mem_buf</span><span class="c0">-></span><span class="c1">vaddr</span><span class="c0">;</span></p><p class="c9"><span class="c1"> ncp </span><span class="c0">=</span><span class="c1"> </span><span class="c0">(</span><span class="c2">struct</span><span class="c1"> ncp_header </span><span class="c0">*)</span><span class="c1">ncp_vaddr</span><span class="c0">;</span></p><p class="c9"><span class="c1"> memory_vector_offset </span><span class="c0">=</span><span class="c1"> ncp</span><span class="c0">-></span><span class="c1">memory_vector_offset</span><span class="c0">;</span></p><p class="c9"><span class="c1"> memory_vector_cnt </span><span class="c0">=</span><span class="c1"> ncp</span><span class="c0">-></span><span class="c1">memory_vector_cnt</span><span class="c0">;</span></p><p class="c9"><span class="c1"> mv </span><span class="c0">=</span><span class="c1"> </span><span class="c0">(</span><span class="c2">struct</span><span class="c1"> memory_vector </span><span class="c0">*)(</span><span class="c1">ncp_vaddr </span><span class="c0">+</span><span class="c1"> memory_vector_offset</span><span class="c0">);</span></p><p class="c9 c8"><span class="c1 c5"></span></p><p class="c9"><span class="c1"> </span><span class="c2">for</span><span class="c1"> </span><span class="c0">(</span><span class="c1">i </span><span class="c0">=</span><span class="c1"> </span><span class="c16">0</span><span class="c0">;</span><span class="c1"> i </span><span class="c0"><</span><span class="c1"> memory_vector_cnt</span><span class="c0">;</span><span class="c1"> i</span><span class="c0">++)</span><span class="c1"> </span><span class="c0">{</span></p><p class="c9"><span class="c1"> u32 memory_type </span><span class="c0">=</span><span class="c1"> </span><span class="c0">(</span><span class="c1">mv </span><span class="c0">+</span><span class="c1"> i</span><span class="c0">)-></span><span class="c1">type</span><span class="c0">;</span></p><p class="c9"><span class="c1"> </span><span class="c2">switch</span><span class="c1"> </span><span class="c0">(</span><span class="c1">memory_type</span><span class="c0">)</span><span class="c1"> </span><span class="c0">{</span></p><p class="c9"><span class="c1"> </span><span class="c2">case</span><span class="c1"> MEMORY_TYPE_IN_FMAP</span><span class="c0">:</span></p><p class="c9"><span class="c1"> </span><span class="c0">{</span></p><p class="c9"><span class="c1"> </span><span class="c0">(*</span><span class="c1">IFM_cnt</span><span class="c0">)++;</span></p><p class="c9"><span class="c1"> </span><span class="c2">break</span><span class="c0">;</span></p><p class="c9"><span class="c1"> </span><span class="c0">}</span></p><p class="c9"><span class="c1"> </span><span class="c2">case</span><span class="c1"> MEMORY_TYPE_OT_FMAP</span><span class="c0">:</span></p><p class="c9"><span class="c1"> </span><span class="c0">{</span></p><p class="c9"><span class="c1"> </span><span class="c0">(*</span><span class="c1">OFM_cnt</span><span class="c0">)++;</span></p><p class="c9"><span class="c1"> </span><span class="c2">break</span><span class="c0">;</span></p><p class="c9"><span class="c1"> </span><span class="c0">}</span></p><p class="c9"><span class="c0">...</span></p><p class="c9"><span class="c1"> </span><span class="c0">}</span></p><p class="c9"><span class="c1"> </span><span class="c0">}</span></p><p class="c9"><span class="c1"> </span><span class="c2">return</span><span class="c1"> ret</span><span class="c0">;</span></p><p class="c9"><span class="c0">}</span></p></td></tr></tbody></table><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span class="c4 c7">There are a few things to note here.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>First, the input data is being parsed from a buffer pointed to by the field </span><span class="c3">session->ncp_mem_buf->vaddr</span><span>. This turns out to be an </span><span class="c11"><a class="c26" href="https://www.google.com/url?q=https://lwn.net/Articles/480055/&sa=D&ust=1608578955027000&usg=AOvVaw25H9zLJhDyXO6_36JfQEhp">ION buffer</a></span><span class="c4 c7">, which is a type of memory buffer shareable between userspace, the kernel, and coprocessors. The ION interface was introduced by Google into the Android kernel to facilitate allocating device-accessible memory and sharing that memory with various hardware components, all without needing to copy the data between buffers. In this case, the userspace app will initialize the model directly inside an ION buffer, and then the model will be mapped directly into the kernel for pre-processing before the ION buffer's device address is sent to the NPU to perform the actual work.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>The important takeaway from this first observation is that the </span><span class="c3">session->ncp_mem_buf->vaddr</span><span class="c4 c7"> field points to an ION buffer that is currently mapped into both userspace and the kernel: that is, it's shared memory.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>The second thing to note in the function </span><span class="c3">__pilot_parsing_ncp()</span><span> is that the only thing it does is count the number of times each type of element appears in the </span><span class="c3">memory_vector</span><span> array in the shared ION buffer. Each </span><span class="c3">memory_vector</span><span> element has an associated </span><span class="c3">type</span><span class="c4 c7">, and this function simply tallies up the number of times it sees each type, and nothing else. There isn't even a failure case for this function.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>This is interesting for a few reasons. For one, there's no input sanitization to ensure that </span><span class="c3">memory_vector_cnt</span><span> (which is read directly from the shared memory buffer and thus is attacker-controlled) is a sane value. It could be </span><span class="c3">0xffffffff</span><span>, leading the for loop to process </span><span class="c3">memory_vector</span><span class="c4 c7"> elements off the end of the ION buffer. Probably a kernel crash at worst, but it's certainly an indication of unfuzzed code.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>More importantly, since only the count of each type is stored, it seems likely that this code is setting itself up for a double-read, time-of-check-to-time-of-use (TOCTOU) issue when it later processes the </span><span class="c3">memory_vector</span><span>s a second time. Each of the returned counts is used to allocate an array in kernel memory (see </span><span class="c3">__config_session_info()</span><span>). But because the </span><span class="c3">memory_vector_cnt</span><span> and </span><span class="c3">type</span><span>s reside in shared memory, they could be changed by code running in userspace between </span><span class="c3">__pilot_parsing_ncp()</span><span> and </span><span class="c3">__second_parsing_ncp()</span><span>, meaning that the second function is passed incorrectly sized arrays for the </span><span class="c3">memory_vector</span><span class="c4 c7"> data it eventually sees in the shared buffer.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>Of course, to determine whether this is actually a bug, we have to look at the implementation of </span><span class="c3">__second_parsing_ncp()</span><span class="c4 c7">:</span></p><p class="c6 c8"><span class="c4 c7"></span></p><a id="t.73c2468f47195f3f1400fee5fe6b73347d944038"></a><a id="t.3"></a><table class="c10"><tbody><tr class="c15"><td class="c19" colspan="1" rowspan="1"><p class="c9"><span class="c2">int</span><span class="c1"> __second_parsing_ncp</span><span class="c0">(</span></p><p class="c9"><span class="c1"> </span><span class="c2">struct</span><span class="c1"> npu_session </span><span class="c0">*</span><span class="c1">session</span><span class="c0">,</span></p><p class="c9"><span class="c1"> </span><span class="c2">struct</span><span class="c1"> temp_av </span><span class="c0">**</span><span class="c1">temp_IFM_av</span><span class="c0">,</span><span class="c1"> </span><span class="c2">struct</span><span class="c1"> temp_av </span><span class="c0">**</span><span class="c1">temp_OFM_av</span><span class="c0">,</span></p><p class="c9"><span class="c1"> </span><span class="c2">struct</span><span class="c1"> temp_av </span><span class="c0">**</span><span class="c1">temp_IMB_av</span><span class="c0">,</span><span class="c1"> </span><span class="c2">struct</span><span class="c1"> addr_info </span><span class="c0">**</span><span class="c1">WGT_av</span><span class="c0">)</span></p><p class="c9"><span class="c0">{</span></p><p class="c9"><span class="c1"> ncp_vaddr </span><span class="c0">=</span><span class="c1"> </span><span class="c0">(</span><span class="c2">char</span><span class="c1"> </span><span class="c0">*)</span><span class="c1">session</span><span class="c0">-></span><span class="c1">ncp_mem_buf</span><span class="c0">-></span><span class="c1">vaddr</span><span class="c0">;</span></p><p class="c9"><span class="c1"> ncp_daddr </span><span class="c0">=</span><span class="c1"> session</span><span class="c0">-></span><span class="c1">ncp_mem_buf</span><span class="c0">-></span><span class="c1">daddr</span><span class="c0">;</span></p><p class="c9"><span class="c1"> ncp </span><span class="c0">=</span><span class="c1"> </span><span class="c0">(</span><span class="c2">struct</span><span class="c1"> ncp_header </span><span class="c0">*)</span><span class="c1">ncp_vaddr</span><span class="c0">;</span></p><p class="c9 c8"><span class="c1 c5"></span></p><p class="c9"><span class="c1"> address_vector_offset </span><span class="c0">=</span><span class="c1"> ncp</span><span class="c0">-></span><span class="c1">address_vector_offset</span><span class="c0">;</span></p><p class="c9"><span class="c1"> address_vector_cnt </span><span class="c0">=</span><span class="c1"> ncp</span><span class="c0">-></span><span class="c1">address_vector_cnt</span><span class="c0">;</span></p><p class="c9"><span class="c0">...</span></p><p class="c9"><span class="c1"> memory_vector_offset </span><span class="c0">=</span><span class="c1"> ncp</span><span class="c0">-></span><span class="c1">memory_vector_offset</span><span class="c0">;</span></p><p class="c9"><span class="c1"> memory_vector_cnt </span><span class="c0">=</span><span class="c1"> ncp</span><span class="c0">-></span><span class="c1">memory_vector_cnt</span><span class="c0">;</span></p><p class="c9 c8"><span class="c1 c5"></span></p><p class="c9"><span class="c1"> mv </span><span class="c0">=</span><span class="c1"> </span><span class="c0">(</span><span class="c2">struct</span><span class="c1"> memory_vector </span><span class="c0">*)(</span><span class="c1">ncp_vaddr </span><span class="c0">+</span><span class="c1"> memory_vector_offset</span><span class="c0">);</span></p><p class="c9"><span class="c1"> av </span><span class="c0">=</span><span class="c1"> </span><span class="c0">(</span><span class="c2">struct</span><span class="c1"> address_vector </span><span class="c0">*)(</span><span class="c1">ncp_vaddr </span><span class="c0">+</span><span class="c1"> address_vector_offset</span><span class="c0">);</span></p><p class="c9"><span class="c0">...</span></p><p class="c9"><span class="c1"> </span><span class="c2">for</span><span class="c1"> </span><span class="c0">(</span><span class="c1">i </span><span class="c0">=</span><span class="c1"> </span><span class="c16">0</span><span class="c0">;</span><span class="c1"> i </span><span class="c0"><</span><span class="c1"> memory_vector_cnt</span><span class="c0">;</span><span class="c1"> i</span><span class="c0">++)</span><span class="c1"> </span><span class="c0">{</span></p><p class="c9"><span class="c1"> u32 memory_type </span><span class="c0">=</span><span class="c1"> </span><span class="c0">(</span><span class="c1">mv </span><span class="c0">+</span><span class="c1"> i</span><span class="c0">)-></span><span class="c1">type</span><span class="c0">;</span></p><p class="c9"><span class="c1"> u32 address_vector_index</span><span class="c0">;</span></p><p class="c9"><span class="c1"> u32 weight_offset</span><span class="c0">;</span></p><p class="c9 c8"><span class="c1 c5"></span></p><p class="c9"><span class="c1"> </span><span class="c2">switch</span><span class="c1"> </span><span class="c0">(</span><span class="c1">memory_type</span><span class="c0">)</span><span class="c1"> </span><span class="c0">{</span></p><p class="c9"><span class="c1"> </span><span class="c2">case</span><span class="c1"> MEMORY_TYPE_IN_FMAP</span><span class="c0">:</span></p><p class="c9"><span class="c1"> </span><span class="c0">{</span></p><p class="c9"><span class="c1"> address_vector_index </span><span class="c0">=</span><span class="c1"> </span><span class="c0">(</span><span class="c1">mv </span><span class="c0">+</span><span class="c1"> i</span><span class="c0">)-></span><span class="c1">address_vector_index</span><span class="c0">;</span></p><p class="c9"><span class="c1"> </span><span class="c2">if</span><span class="c1"> </span><span class="c0">(!</span><span class="c1">EVER_FIND_FM</span><span class="c0">(</span><span class="c1">IFM_cnt</span><span class="c0">,</span><span class="c1"> </span><span class="c0">*</span><span class="c1">temp_IFM_av</span><span class="c0">,</span><span class="c1"> address_vector_index</span><span class="c0">))</span><span class="c1"> </span><span class="c0">{</span></p><p class="c9"><span class="c1"> </span><span class="c0">(*</span><span class="c1">temp_IFM_av </span><span class="c0">+</span><span class="c1"> </span><span class="c0">(*</span><span class="c1">IFM_cnt</span><span class="c0">))-></span><span class="c1">index </span><span class="c0">=</span><span class="c1"> address_vector_index</span><span class="c0">;</span></p><p class="c9"><span class="c1"> </span><span class="c0">(*</span><span class="c1">temp_IFM_av </span><span class="c0">+</span><span class="c1"> </span><span class="c0">(*</span><span class="c1">IFM_cnt</span><span class="c0">))-></span><span class="c1">size </span><span class="c0">=</span><span class="c1"> </span><span class="c0">(</span><span class="c1">av </span><span class="c0">+</span><span class="c1"> address_vector_index</span><span class="c0">)-></span><span class="c1">size</span><span class="c0">;</span></p><p class="c9"><span class="c1"> </span><span class="c0">(*</span><span class="c1">temp_IFM_av </span><span class="c0">+</span><span class="c1"> </span><span class="c0">(*</span><span class="c1">IFM_cnt</span><span class="c0">))-></span><span class="c1">pixel_format </span><span class="c0">=</span><span class="c1"> </span><span class="c0">(</span><span class="c1">mv </span><span class="c0">+</span><span class="c1"> i</span><span class="c0">)-></span><span class="c1">pixel_format</span><span class="c0">;</span></p><p class="c9"><span class="c1"> </span><span class="c0">(*</span><span class="c1">temp_IFM_av </span><span class="c0">+</span><span class="c1"> </span><span class="c0">(*</span><span class="c1">IFM_cnt</span><span class="c0">))-></span><span class="c1">width </span><span class="c0">=</span><span class="c1"> </span><span class="c0">(</span><span class="c1">mv </span><span class="c0">+</span><span class="c1"> i</span><span class="c0">)-></span><span class="c1">width</span><span class="c0">;</span></p><p class="c9"><span class="c1"> </span><span class="c0">(*</span><span class="c1">temp_IFM_av </span><span class="c0">+</span><span class="c1"> </span><span class="c0">(*</span><span class="c1">IFM_cnt</span><span class="c0">))-></span><span class="c1">height </span><span class="c0">=</span><span class="c1"> </span><span class="c0">(</span><span class="c1">mv </span><span class="c0">+</span><span class="c1"> i</span><span class="c0">)-></span><span class="c1">height</span><span class="c0">;</span></p><p class="c9"><span class="c1"> </span><span class="c0">(*</span><span class="c1">temp_IFM_av </span><span class="c0">+</span><span class="c1"> </span><span class="c0">(*</span><span class="c1">IFM_cnt</span><span class="c0">))-></span><span class="c1">channels </span><span class="c0">=</span><span class="c1"> </span><span class="c0">(</span><span class="c1">mv </span><span class="c0">+</span><span class="c1"> i</span><span class="c0">)-></span><span class="c1">channels</span><span class="c0">;</span></p><p class="c9"><span class="c1"> </span><span class="c0">(</span><span class="c1">mv </span><span class="c0">+</span><span class="c1"> i</span><span class="c0">)-></span><span class="c1">stride </span><span class="c0">=</span><span class="c1"> </span><span class="c16">0</span><span class="c0">;</span></p><p class="c9"><span class="c1"> </span><span class="c0">(*</span><span class="c1">temp_IFM_av </span><span class="c0">+</span><span class="c1"> </span><span class="c0">(*</span><span class="c1">IFM_cnt</span><span class="c0">))-></span><span class="c1">stride </span><span class="c0">=</span><span class="c1"> </span><span class="c0">(</span><span class="c1">mv </span><span class="c0">+</span><span class="c1"> i</span><span class="c0">)-></span><span class="c1">stride</span><span class="c0">;</span></p><p class="c9"><span class="c0">...</span></p><p class="c9"><span class="c1"> </span><span class="c0">(*</span><span class="c1">IFM_cnt</span><span class="c0">)++;</span></p><p class="c9"><span class="c1"> </span><span class="c0">}</span></p><p class="c9"><span class="c1"> </span><span class="c2">break</span><span class="c0">;</span></p><p class="c9"><span class="c1"> </span><span class="c0">}</span></p><p class="c9"><span class="c0">...</span></p><p class="c9"><span class="c1"> </span><span class="c2">case</span><span class="c1"> MEMORY_TYPE_WEIGHT</span><span class="c0">:</span></p><p class="c9"><span class="c1"> </span><span class="c0">{</span></p><p class="c9"><span class="c1"> </span><span class="c23">// update address vector, m_addr with ncp_alloc_daddr + offset</span></p><p class="c9"><span class="c1"> address_vector_index </span><span class="c0">=</span><span class="c1"> </span><span class="c0">(</span><span class="c1">mv </span><span class="c0">+</span><span class="c1"> i</span><span class="c0">)-></span><span class="c1">address_vector_index</span><span class="c0">;</span></p><p class="c9"><span class="c1"> weight_offset </span><span class="c0">=</span><span class="c1"> </span><span class="c0">(</span><span class="c1">av </span><span class="c0">+</span><span class="c1"> address_vector_index</span><span class="c0">)-></span><span class="c1">m_addr</span><span class="c0">;</span></p><p class="c9"><span class="c1"> </span><span class="c2">if</span><span class="c1"> </span><span class="c0">(</span><span class="c1">weight_offset </span><span class="c0">></span><span class="c1"> </span><span class="c0">(</span><span class="c1">u32</span><span class="c0">)</span><span class="c1">session</span><span class="c0">-></span><span class="c1">ncp_mem_buf</span><span class="c0">-></span><span class="c1">size</span><span class="c0">)</span><span class="c1"> </span><span class="c0">{</span></p><p class="c9"><span class="c1"> ret </span><span class="c0">=</span><span class="c1"> </span><span class="c0">-</span><span class="c1">EINVAL</span><span class="c0">;</span></p><p class="c9"><span class="c1"> npu_uerr</span><span class="c0">(</span><span class="c35">"weight_offset is invalid, offset(0x%x), ncp_daddr(0x%x)\n"</span><span class="c0">,</span></p><p class="c9"><span class="c1"> session</span><span class="c0">,</span><span class="c1"> </span><span class="c0">(</span><span class="c1">u32</span><span class="c0">)</span><span class="c1">weight_offset</span><span class="c0">,</span><span class="c1"> </span><span class="c0">(</span><span class="c1">u32</span><span class="c0">)</span><span class="c1">session</span><span class="c0">-></span><span class="c1">ncp_mem_buf</span><span class="c0">-></span><span class="c1">size</span><span class="c0">);</span></p><p class="c9"><span class="c1"> </span><span class="c2">goto</span><span class="c1"> p_err</span><span class="c0">;</span></p><p class="c9"><span class="c1"> </span><span class="c0">}</span></p><p class="c9"><span class="c1"> </span><span class="c0">(</span><span class="c1">av </span><span class="c0">+</span><span class="c1"> address_vector_index</span><span class="c0">)-></span><span class="c1">m_addr </span><span class="c0">=</span><span class="c1"> weight_offset </span><span class="c0">+</span><span class="c1"> ncp_daddr</span><span class="c0">;</span></p><p class="c9"><span class="c0">...</span></p><p class="c9"><span class="c1"> </span><span class="c0">(*</span><span class="c1">WGT_cnt</span><span class="c0">)++;</span></p><p class="c9"><span class="c1"> </span><span class="c2">break</span><span class="c0">;</span></p><p class="c9"><span class="c1"> </span><span class="c0">}</span></p><p class="c9"><span class="c0">...</span></p><p class="c9"><span class="c1"> </span><span class="c0">}</span></p><p class="c9"><span class="c1"> </span><span class="c0">}</span></p><p class="c9"><span class="c0">...</span></p><p class="c9"><span class="c0">}</span></p></td></tr></tbody></table><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span class="c4 c7">Once again, there are several things to note here.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>First off, it does indeed turn out that </span><span class="c3">memory_vector_cnt</span><span> is read a second time from the shared ION buffer, and there are no sanity checks to ensure that the arrays </span><span class="c3">temp_IFM_av</span><span>, </span><span class="c3">temp_OFM_av</span><span>, and </span><span class="c3">temp_IMB_av</span><span class="c4 c7"> are not filled beyond the counts for which they were each allocated. So this is indeed a linear heap overflow bug.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>But secondly, when processing a </span><span class="c3">memory_vector</span><span> element of type </span><span class="c3">MEMORY_TYPE_WEIGHT</span><span>, it appears that there's another issue as well: A controlled 32-bit value </span><span class="c3">address_vector_index</span><span> is read from the </span><span class="c3">memory_vector</span><span> entry and used as an index into an </span><span class="c3">address_vector</span><span> array without any bounds checks. And not only is the out-of-bounds </span><span class="c3">address_vector</span><span>'s </span><span class="c3">m_addr</span><span class="c4 c7"> field read, it's also written a few lines later after adding in the ION buffer's device address! So this is an out-of-bounds addition at a controlled offset.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>These are the two most serious issues described in Ben's </span><span class="c11"><a class="c26" href="https://www.google.com/url?q=https://bugs.chromium.org/p/project-zero/issues/detail?id%3D2073&sa=D&ust=1608578955052000&usg=AOvVaw3ZwfboJCZZsR9puUJNnD-u">report</a></span><span> to Samsung</span><span class="c4 c7">. We'll look at each in more detail in the following two sections in order to understand their constraints.</span></p><h2 class="c25" id="h.7x7yp924n86a"><span class="c5 c28 c7">The heap overflow</span></h2><p class="c6"><span class="c4 c7">How might the heap overflow be triggered?</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>First, </span><span class="c3">__config_session_info()</span><span> will call </span><span class="c3">__pilot_parsing_ncp()</span><span> to count the number of elements of each type in the </span><span class="c3">memory_vector</span><span> array in the shared ION buffer. Imagine that initially the value of </span><span class="c3">ncp->memory_vector_cnt</span><span> is 1, and the single </span><span class="c3">memory_vector</span><span> has type </span><span class="c3">MEMORY_TYPE_IN_FMAP</span><span>. Then </span><span class="c3">__pilot_parsing_ncp()</span><span> will return with </span><span class="c3">IFM_cnt</span><span class="c4 c7"> set to 1.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>Next, </span><span class="c3">__config_session_info()</span><span> will allocate the </span><span class="c3">temp_IFM_av</span><span> with space for a single </span><span class="c3">temp_av</span><span class="c4 c7"> element.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>Concurrently, a thread in userspace will race to change the value of </span><span class="c3">ncp->memory_vector_cnt</span><span> in the shared ION buffer from 1 to 100. The </span><span class="c3">memory_vector</span><span> now appears to have 100 elements, and userspace will ensure that the extra elements all have type </span><span class="c3">MEMORY_TYPE_IN_FMAP</span><span class="c4 c7"> as well.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>Back in the kernel: After allocating the arrays, </span><span class="c3">__config_session_info()</span><span> will call </span><span class="c3">__second_parsing_ncp()</span><span> to perform the second stage of parsing. </span><span class="c3">__second_parsing_ncp()</span><span> will read </span><span class="c3">memory_vector_cnt</span><span> again, this time getting the value 100. Thus, in the </span><span class="c3">for</span><span> loop, it will try to process 100 elements from the </span><span class="c3">memory_vector</span><span> array, and each will be of type </span><span class="c3">MEMORY_TYPE_IN_FMAP</span><span>. Each iteration will populate another </span><span class="c3">temp_av</span><span> element in the </span><span class="c3">temp_IFM_av</span><span class="c4 c7"> array, and elements after the first will be written off the end of the array.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>Furthermore, the out-of-bounds </span><span class="c3">temp_av</span><span class="c4 c7"> element's fields are written with contents read from the ION buffer, which means that the contents of the out-of-bounds write can be fully controlled (except for padding between fields).</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>This seems like an excellent primitive: we're performing an out-of-bounds write off the end of a kernel allocation of controlled size, and we can control both the size of the overflow and the contents of the data we write. This level of control means that it should theoretically be possible to place the </span><span class="c3">temp_IFM_av</span><span class="c4 c7"> allocation next to many different types of objects and control the amount of the victim object we overflow. Having such flexibility means that we can choose from a very wide selection of victim objects when deciding the easiest and most reliable exploit strategy.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>The one thing to be aware of is that a simple implementation would probably win the race to increase </span><span class="c3">memory_vector_cnt</span><span> about 25% of the time. The reason for this is that it's probably tricky to get the timing of when to flip from 1 to 100 exactly right, so the simplest strategy is simply to have a user thread alternate between the two values continuously. If each of the reads in </span><span class="c3">__pilot_parsing_ncp()</span><span> and </span><span class="c3">__second_parsing_ncp()</span><span class="c4 c7"> reads either 1 or 100 randomly, then there's a 1 in 4 chance that the first read is 1 and the second is 100. The good thing is that nothing too bad happens if we lose the race: there's possibly a memory leak, but the kernel doesn't crash. Thus, we can just try this over and over until we win.</span></p><h2 class="c25" id="h.50ngf3cmswox"><span class="c5 c7 c28">The out-of-bounds addition</span></h2><p class="c6"><span class="c4 c7">Now that we've seen the heap overflow, let's look at the out-of-bounds addition primitive. Unlike the prior primitive, this one is a straightforward, deterministic out-of-bounds addition.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>Once again, we'll initialize our ION buffer to have a single </span><span class="c3">memory_vector</span><span> element, but this time its type will be </span><span class="c3">MEMORY_TYPE_WEIGHT</span><span>. Nothing interesting happens in </span><span class="c3">__pilot_parsing_ncp()</span><span>, so we'll jump ahead to </span><span class="c3">__second_parsing_ncp()</span><span class="c4 c7">.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>In </span><span class="c3">__second_parsing_ncp()</span><span>, </span><span class="c3">address_vector_offset</span><span> is read directly from the ION buffer without input validation. This is added to the ION buffer address to compute the address of an array of </span><span class="c3">address_vector</span><span> structs; since the offset was unchecked, this supposed </span><span class="c3">address_vector</span><span> array could lie entirely in out-of-bounds memory. And importantly, there are no alignment checks, so the </span><span class="c3">address_vector</span><span class="c4 c7"> array could start at any odd unaligned address we want.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><a id="t.72839d0597c2bc964e359a74c5e1f9519a0d0fea"></a><a id="t.4"></a><table class="c10"><tbody><tr class="c15"><td class="c19" colspan="1" rowspan="1"><p class="c9"><span class="c1"> address_vector_offset </span><span class="c0">=</span><span class="c1"> ncp</span><span class="c0">-></span><span class="c1">address_vector_offset</span><span class="c0">;</span></p><p class="c9"><span class="c0">...</span></p><p class="c9"><span class="c1"> av </span><span class="c0">=</span><span class="c1"> </span><span class="c0">(</span><span class="c2">struct</span><span class="c1"> address_vector </span><span class="c0">*)(</span><span class="c1">ncp_vaddr </span><span class="c0">+</span><span class="c1"> address_vector_offset</span><span class="c0">);</span></p></td></tr></tbody></table><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>We next enter the </span><span class="c3">for</span><span> loop to process the single </span><span class="c3">memory_vector</span><span> entry, and jump to the case for </span><span class="c3">MEMORY_TYPE_WEIGHT</span><span>. This code reads the </span><span class="c3">address_vector_index</span><span> field of the </span><span class="c3">memory_vector</span><span>, which is again completely controlled and unvalidated. The (potentially out-of-bounds) </span><span class="c3">address_vector</span><span class="c4 c7"> element at the specified index is then accessed.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><a id="t.4b0f7e977cb8f15f551baff81d2c32d40d159d78"></a><a id="t.5"></a><table class="c10"><tbody><tr class="c15"><td class="c19" colspan="1" rowspan="1"><p class="c9"><span class="c1"> </span><span class="c23">// update address vector, m_addr with ncp_alloc_daddr + offset</span></p><p class="c9"><span class="c1"> address_vector_index </span><span class="c0">=</span><span class="c1"> </span><span class="c0">(</span><span class="c1">mv </span><span class="c0">+</span><span class="c1"> i</span><span class="c0">)-></span><span class="c1">address_vector_index</span><span class="c0">;</span></p><p class="c9"><span class="c1"> weight_offset </span><span class="c0">=</span><span class="c1"> </span><span class="c0">(</span><span class="c1">av </span><span class="c0">+</span><span class="c1"> address_vector_index</span><span class="c0">)-></span><span class="c1">m_addr</span><span class="c0">;</span></p></td></tr></tbody></table><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>Reading the </span><span class="c3">m_addr</span><span> field (at offset </span><span class="c3">0x4</span><span> in </span><span class="c3">address_vector</span><span class="c4 c7">) will thus possibly perform an out-of-bounds read.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span class="c4 c7">But there's a check we need to pass before we can hit the out-of-bounds write:</span></p><p class="c6 c8"><span class="c4 c7"></span></p><a id="t.7a5f6fa86d0d405bea909473edf116d2b4c17e36"></a><a id="t.6"></a><table class="c10"><tbody><tr class="c15"><td class="c19" colspan="1" rowspan="1"><p class="c9"><span class="c1"> </span><span class="c2">if</span><span class="c1"> </span><span class="c0">(</span><span class="c1">weight_offset </span><span class="c0">></span><span class="c1"> </span><span class="c0">(</span><span class="c1">u32</span><span class="c0">)</span><span class="c1">session</span><span class="c0">-></span><span class="c1">ncp_mem_buf</span><span class="c0">-></span><span class="c1">size</span><span class="c0">)</span><span class="c1"> </span><span class="c0">{</span></p><p class="c9"><span class="c1"> ret </span><span class="c0">=</span><span class="c1"> </span><span class="c0">-</span><span class="c1">EINVAL</span><span class="c0">;</span></p><p class="c9"><span class="c1"> npu_uerr</span><span class="c0">(</span><span class="c35">"weight_offset is invalid, offset(0x%x), ncp_daddr(0x%x)\n"</span><span class="c0">,</span></p><p class="c9"><span class="c1"> session</span><span class="c0">,</span><span class="c1"> </span><span class="c0">(</span><span class="c1">u32</span><span class="c0">)</span><span class="c1">weight_offset</span><span class="c0">,</span><span class="c1"> </span><span class="c0">(</span><span class="c1">u32</span><span class="c0">)</span><span class="c1">session</span><span class="c0">-></span><span class="c1">ncp_mem_buf</span><span class="c0">-></span><span class="c1">size</span><span class="c0">);</span></p><p class="c9"><span class="c1"> </span><span class="c2">goto</span><span class="c1"> p_err</span><span class="c0">;</span></p><p class="c9"><span class="c1"> </span><span class="c0">}</span></p></td></tr></tbody></table><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>Basically, what this does is compare the original value of the out-of-bounds read to the size of the ION buffer, and if the original value is greater than the ION buffer size, then </span><span class="c3">__second_parsing_ncp()</span><span class="c4 c7"> aborts without performing the write.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span class="c4 c7">But, assuming that the original value is less than the ION buffer size, it gets updated by adding in the device address (daddr) of the ION buffer:</span></p><p class="c6 c8"><span class="c4 c7"></span></p><a id="t.77d061e8f7d9c1f2347960e4b1dde90ed864bf96"></a><a id="t.7"></a><table class="c10"><tbody><tr class="c15"><td class="c19" colspan="1" rowspan="1"><p class="c9"><span class="c1"> </span><span class="c0">(</span><span class="c1">av </span><span class="c0">+</span><span class="c1"> address_vector_index</span><span class="c0">)-></span><span class="c1">m_addr </span><span class="c0">=</span><span class="c1"> weight_offset </span><span class="c0">+</span><span class="c1"> ncp_daddr</span><span class="c0">;</span></p></td></tr></tbody></table><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>The device address is (</span><span>presumably</span><span>) the address at which a device could access the buffer; this could be a physical address, or if the system has an </span><span class="c11"><a class="c26" href="https://www.google.com/url?q=https://en.wikipedia.org/wiki/Input%25E2%2580%2593output_memory_management_unit&sa=D&ust=1608578955066000&usg=AOvVaw2VvVpsNucmrr9KywnAvow2">IOMMU</a></span><span> in front of the hardware device performing the access, it would be an IOMMU-translated address. Essentially, this code expects that the </span><span class="c3">m_addr</span><span> field in the </span><span class="c3">address_vector</span><span class="c4 c7"> will initially be an offset from the start of the ION buffer, and it updates it into an absolute (device) address by adding the ION buffer's daddr.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span class="c4 c7">So, if the original value at the out-of-bounds addition location is quite small, and in particular smaller than the ION buffer size, then this primitive will allow us to add in the ION buffer's daddr, making the value rather large instead.</span></p><h1 class="c21" id="h.9mrvv09b7yfu"><span class="c5 c17 c7">"Hey Siri, how do I exploit Android?"</span></h1><p class="c6"><span class="c4 c7">Everything described up to here comes more or less directly from Ben's initial report to Samsung. So: what now?</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span class="c4 c7">Even after reading Ben's initial report and looking at the NPU driver's source code to understand the bugs more fully, I felt rather lost as to how to proceed. As the title and intro to this post suggest, Android is not my area.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>On the other hand, I have written a </span><span>few</span><span> </span><span class="c11"><a class="c26" href="https://www.google.com/url?q=https://bugs.chromium.org/p/project-zero/issues/detail?id%3D1731%23c10&sa=D&ust=1608578955067000&usg=AOvVaw1wJHXUsIIMf-oP3QbEuCok">iOS</a></span><span> </span><span class="c11"><a class="c26" href="https://www.google.com/url?q=https://bugs.chromium.org/p/project-zero/issues/detail?id%3D2035%23c4&sa=D&ust=1608578955067000&usg=AOvVaw3xhDiq6oGnKYzCOmuvnI2z">kernel</a></span><span> </span><span class="c11"><a class="c26" href="https://www.google.com/url?q=https://bugs.chromium.org/p/project-zero/issues/detail?id%3D1986%23c7&sa=D&ust=1608578955067000&usg=AOvVaw2ylmRP3FBMafCqM5QvURpz">exploits</a></span><span> for memory corruption bugs before. Based on that experience, I suspected that both the heap overflow and the out-of-bounds addition could be exploited using straightforward applications of </span><span class="c11"><a class="c26" href="https://www.google.com/url?q=https://googleprojectzero.blogspot.com/2020/06/a-survey-of-recent-ios-kernel-exploits.html&sa=D&ust=1608578955067000&usg=AOvVaw3st_8GMjjc3i3vQH2OsB84">existing techniques</a></span><span>,</span><span class="c4 c7"> had the bugs existed on iOS. So I embarked on a thought experiment: what would the full exploit flows for each of these primitives be if the bugs existed on iOS instead of Android? My hope was that I might be able to draw parallels between the two, such that thinking about the exploit on iOS would inform how these bugs could be exploited on Android.</span></p><h2 class="c25" id="h.l7bb193iwsn7"><span class="c5 c28 c7">Thought experiment: an iOS heap overflow</span></h2><p class="c6"><span class="c4 c7">So, imagine that an equivalent to the heap overflow bug existed on iOS; that is, imagine there were a race with a 25% win rate that allowed us to perform a fully controlled linear heap buffer overflow out of a controlled-size allocation. How could we turn this into arbitrary kernel read/write?</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>On iOS, the de-facto standard for a final kernel read/write capability has been the use of an object called a kernel task port. A task port is a handle that gives us control over the virtual memory of a task, and a kernel task port is a task port for the kernel itself. Thus, if you can construct a kernel task port, you can use standard APIs like </span><span class="c3">mach_vm_read()</span><span> and </span><span class="c3">mach_vm_write()</span><span class="c4 c7"> to read and write kernel memory from userspace.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>Comparing this heap overflow to the vulnerabilities listed in the </span><span class="c11"><a class="c26" href="https://www.google.com/url?q=https://googleprojectzero.blogspot.com/2020/06/a-survey-of-recent-ios-kernel-exploits.html&sa=D&ust=1608578955068000&usg=AOvVaw0M9D8sxoocSmd-n4XZZ58w">survey</a></span><span>, the initial primitive is most similar to that granted by CVE-2017-2370, which was exploited by </span><span class="c11"><a class="c26" href="https://www.google.com/url?q=https://googleprojectzero.blogspot.com/2017/04/exception-oriented-exploitation-on-ios.html&sa=D&ust=1608578955069000&usg=AOvVaw3RcgkEZ6oq4v8I0UcrD1kA">extra_recipe</a></span><span> and </span><span class="c11"><a class="c26" href="https://www.google.com/url?q=https://github.com/kpwn/yalu102&sa=D&ust=1608578955069000&usg=AOvVaw1aV6k45s0OgHluj4kgY0pC">Yalu102</a></span><span class="c4 c7">. Thus, the exploit flow would likely be quite similar to those existing iOS exploits. And to make our thought experiment even easier, we can put aside any concern about reliability and just imagine the simplest exploit flow that could work generically.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span class="c4 c7">The most straightforward way to get a handle to a kernel task port using this primitive would likely be to overflow into an out-of-line Mach ports array and insert a pointer to a fake kernel task port, such that receiving the overflowed port array back in userspace grants us a handle to our fake port. This is essentially the strategy used by Yalu102, except that that exploit relies on the absence of PAN (i.e., it relies on being able to dereference userspace pointers from kernel mode). The same strategy should work on systems with PAN, it would just require a few extra steps.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>Unfortunately, the first time we trigger the vulnerability to give ourselves a handle to a fake task port, we won't have all the information needed to immediately construct a kernel task port: we'd need to leak the addresses of a few important kernel objects, such as the </span><span class="c3">kernel_map</span><span>, first. Consequently, we would need to start first by giving ourselves a "vanilla" fake task port and then use that to read arbitrary kernel memory via the traditional </span><span class="c3">pid_for_task()</span><span> technique. Once we can read kernel memory, we would locate the relevant kernel objects to build the final kernel task port.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>Now, since we'll need to perform multiple arbitrary reads using </span><span class="c3">pid_for_task()</span><span>, we need to be able to update the kernel address we want to read from. This can be a challenge because the pointer specifying the target read address lives in kernel memory. Fortunately, there already exist a few standard techniques for updating this pointer to read from new kernel addresses, such as reallocating the buffer holding the target read pointer (see the </span><span class="c11"><a class="c26" href="https://www.google.com/url?q=https://blogs.360.cn/post/IPC%2520Voucher%2520UaF%2520Remote%2520Jailbreak%2520Stage%25202%2520(EN).html&sa=D&ust=1608578955070000&usg=AOvVaw3eqSiB2LY1OfGk904vdnB_">Chaos</a></span><span> exploit), overlapping Mach ports in special ways that allow updating the target read address directly (see the </span><span class="c11"><a class="c26" href="https://www.google.com/url?q=https://siguza.github.io/v0rtex/&sa=D&ust=1608578955070000&usg=AOvVaw2uMt2TluaPinFRPf_NQAyc">v0rtex</a></span><span> exploit), or placing the fake kernel objects in user/kernel shared memory (see the </span><span class="c11"><a class="c26" href="https://www.google.com/url?q=https://bugs.chromium.org/p/project-zero/issues/detail?id%3D2035%23c4&sa=D&ust=1608578955070000&usg=AOvVaw14qDghwQl7TV6gFWPvrqML">oob_timestamp</a></span><span class="c4 c7"> exploit).</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span class="c4 c7">Finally, there will also be some complications from working around Apple's zone_require mitigation, which aims to block exactly these types of fake Mach port shenanigans. However, at least through iOS 13.3, it's possible to get around zone_require by operating with large allocations that live outside the zone_map (see the oob_timestamp exploit).</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span class="c4 c7">So, in summary, a very simplistic exploit flow for the heap overflow might look something like this:</span></p><p class="c6 c8"><span class="c4 c7"></span></p><ol class="c32 lst-kix_n34nhxg9gk9-0 start" start="1"><li class="c6 c22"><span>Spray large amounts of kernel memory with fake task ports and fake tasks, such that you can be reasonably sure that one fake object of </span><span>each </span><span class="c4 c7">type has been sprayed to a specific hardcoded address. Use the oob_timestamp trick to bypass zone_require and place these fake objects in shared memory.</span></li><li class="c6 c22"><span class="c4 c7">Spray out-of-line Mach ports allocations, and poke holes between the allocations.</span></li><li class="c6 c22"><span class="c4 c7">Trigger the out-of-bounds write out of an allocation of the same size as the out-of-line Mach ports allocations, overflowing into the subsequent array of Mach ports and inserting the hardcoded pointer to the fake ports.</span></li><li class="c6 c22"><span class="c4 c7">Receive the out-of-line Mach port handles in userspace and check if there's a new task port handle; if not, go back to step 2.</span></li><li class="c6 c22"><span>This handle is a fake task port that can be used to read kernel memory using </span><span class="c3">pid_for_task()</span><span class="c4 c7">. Use this capability to find relevant kernel objects and update the fake task into a fake kernel task object.</span></li></ol><h2 class="c25" id="h.t2f0bphekkhy"><span class="c5 c28 c7">Thought experiment: an iOS out-of-bounds addition</span></h2><p class="c6"><span class="c4 c7">What about the out-of-bounds addition?</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span class="c4 c7">None of the bugs in the survey of iOS kernel exploits seem like a good comparison point: the most similar category would be linear heap buffer overflows, but these are much more limiting due to spatial locality. Nonetheless, I eventually settled on oob_timestamp as the best reference.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>The vulnerability used by oob_timestamp is CVE-2020-3837, a linear heap out-of-bounds write of up to 8 bytes of timestamp data. However, this isn't the typical heap buffer overflow. Most heap overflows occur in small to medium sized allocations of up to 2 pages (</span><span class="c3">0x8000</span><span class="c4 c7"> bytes); these allocations occur in a submap of the kernel virtual address space called the zone_map. Meanwhile, the out-of-bounds write in oob_timestamp occurs off the end of a pageable region of shared memory outside the zone_map, in the general kernel_map used for large, multi-page allocations or allocations with special properties (like pageable and shared memory).</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span class="c4 c7">The basic flow of oob_timestamp is as follows:</span></p><p class="c6 c8"><span class="c4 c7"></span></p><ol class="c32 lst-kix_lgfdoxi035fa-0 start" start="1"><li class="c6 c22"><span>Groom the kernel_map to allocate the pageable shared memory region, an </span><span class="c3">ipc_kmsg</span><span class="c4 c7">, and an out-of-line Mach ports array contiguously. These are followed by a large number of filler allocations.</span></li><li class="c6 c22"><span>Trigger the overflow to overwrite the first few bytes of the </span><span class="c3">ipc_kmsg</span><span class="c4 c7">, increasing the size field.</span></li><li class="c6 c22"><span>Free the </span><span class="c3">ipc_kmsg</span><span class="c4 c7">, causing the subsequent out-of-line Mach ports array to also be freed.</span></li><li class="c6 c22"><span class="c4 c7">Reallocate the out-of-line Mach ports array with controlled data, effectively inserting fake Mach port pointers into the array. The pointer will be a pointer to a fake task port in the pageable shared memory region.</span></li><li class="c6 c22"><span class="c4 c7">Receive the out-of-line Mach ports in userspace, gaining a handle to the fake port.</span></li><li class="c6 c22"><span>Overwrite the fake port in shared memory to make a fake task port and then read kernel memory using </span><span class="c3">pid_for_task()</span><span class="c4 c7">. Find relevant kernel objects and update the fake task into a fake kernel task.</span></li></ol><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>My thought was that you could basically use the out-of-bounds addition primitive as a drop-in replacement for the out-of-bounds timestamp write primitive in oob_timestamp. This seemed sensible because the initial primitive in oob_timestamp is only used to increase the size of an </span><span class="c3">ipc_kmsg</span><span> so that extra memory gets freed. Since all I need to do is bump an </span><span class="c3">ipc_kmsg</span><span class="c4 c7">'s size field, I felt that an out-of-bounds addition primitive should surely be suitable for this.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>As it turns out, the constraints of the out-of-bounds addition are actually much tighter than I had imagined: the </span><span class="c3">ipc_kmsg</span><span>'s size and </span><span class="c4 c7">the ION buffer's device address would need to be very carefully chosen to meet all the requirements. In spite of this oversight, oob_timestamp still proved a useful reference point for another reason: where the ION buffers get mapped in kernel memory.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>Like iOS, Android also has multiple areas of virtual memory in which allocations can be made. </span><span class="c3">kmalloc()</span><span> is the standard allocation function used for most allocations. It's somewhat analogous to allocating using </span><span class="c3">kalloc()</span><span> on iOS: allocations can be less than a page in size and are managed via a dedicated allocation pool that's a bit like the zone_map. However, there's also a </span><span class="c3">vmalloc()</span><span> function for allocating "virtually contiguous memory". Unlike </span><span class="c3">kmalloc()</span><span>, </span><span class="c3">vmalloc()</span><span class="c4 c7"> allocates at page granularity and allocations occur in a separate region of kernel virtual memory somewhat analogous to the kernel_map on iOS.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span class="c4 c7">The reason this matters is that the ION buffer on Android is mapped into the Linux kernel's vmalloc region. Thus, the fact that the out-of-bounds addition occurs off the end of a shared ION buffer mapped in the vmalloc area closely parallels how the oob_timestamp overflow occurs off the end of a pageable shared memory region in the kernel_map.</span></p><h1 class="c21" id="h.1tzr233h10ea"><span class="c5 c17 c7">Choosing a path</span></h1><p class="c6"><span class="c4 c7">At this point I still faced many unknowns: What allocation and heap shaping primitives were available on Android? What types of objects would be useful to corrupt? What would be Android's equivalent of the kernel task port, the final read/write capability? I had no idea.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>One thing I did have was a hint at a good starting point: Both Ben and fellow Project Zero researcher </span><span class="c11"><a class="c26" href="https://www.google.com/url?q=https://twitter.com/tehjh&sa=D&ust=1608578955075000&usg=AOvVaw3Pv3Mj5IScA-OPtG0dCOwO">Jann Horn</a></span><span class="c4 c7"> had pointed out that along with the ION buffers, kernel thread stacks were also allocated in the vmalloc area, which meant that kernel stacks might be a good target for the out-of-bounds addition bug.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c33"></p> <p class="c27"><span class="c30 c7 c24"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjPxqmpHY7a9wVU9N6EaSUaZJ55fQcZGSW02pooIY5zBkFGd0byoBv6kQORU_QhaF0zsJky3K7E5XxLAmUh5rvgCVqI7CQDWQ04dgev9lh-PAA4IXEHVtkyPUzO3LXSOaI9A9_mh6EzV9fVHpqNiMN5flxkLnOStf0FlLiRF_UcJHIOKx7iIP6kK7AT/s1600/unnamed%20%284%29.png" style="display: block; padding: 1em 0; text-align: center;"><img alt="A diagram showing an ION buffer mapped directly before a userspace thread's kernel stack, with a guard page in between. The address_vector_offset is so large that it points past the end of the ION buffer and into the stack." border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjPxqmpHY7a9wVU9N6EaSUaZJ55fQcZGSW02pooIY5zBkFGd0byoBv6kQORU_QhaF0zsJky3K7E5XxLAmUh5rvgCVqI7CQDWQ04dgev9lh-PAA4IXEHVtkyPUzO3LXSOaI9A9_mh6EzV9fVHpqNiMN5flxkLnOStf0FlLiRF_UcJHIOKx7iIP6kK7AT/s1000/unnamed%20%284%29.png" style="max-height: 500pt; max-width: 400pt;" /></a></span></p><p class="c27"><span class="c30 c7 c24">If the ION buffer is mapped directly before a kernel stack for a userspace thread, then the out-of-bounds addition primitive could be used to manipulate the stack.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>Imagine that we could get a kernel stack for a thread in our process allocated at a fixed offset after the ION buffer. The out-of-bounds addition primitive would let us manipulate values on the thread's stack during a syscall by adding in the ION buffer's daddr, assuming that the initial values were sufficiently small. For example, we might make a blocking syscall which saves a certain size parameter in a variable, and once that syscall blocks and spills the size variable to the stack, we can use the out-of-bounds addition to make it very large; unblocking the syscall would then cause the original code to resume use the corrupted size value, perhaps passing it to a function like </span><span class="c3">memcpy()</span><span class="c4 c7"> to cause further memory corruption.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span class="c4 c7">Using the out-of-bounds addition primitive to target values on a thread's kernel stack was appealing for one very important reason: in theory, this technique could dramatically reduce the amount of Linux-specific knowledge I'd need to develop a full exploit.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>Most typical memory corruption exploits require finding specific objects that are interesting targets for corruption. For example, there are very few objects that could replace the </span><span class="c3">ipc_kmsg</span><span> in the oob_timestamp exploit, because the exploit relies on corrupting a size field stored at the very start of the struct; not many other objects have a size as their first field. So </span><span class="c3">ipc_kmsg</span><span class="c4 c7"> fills a very important niche in the arsenal of interesting objects to target with memory corruption, just as Mach ports fill a niche in useful objects to get a fake handle for.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span class="c4 c7">Undoubtedly, there's a similar arsenal of useful objects to target for Android kernel exploitation. But by limiting ourselves to corrupting values on the kernel stack, we transform the set of all useful target "objects" into the set of useful stack frames to manipulate. This is a much easier set of target objects to work with than the set of all structs that could be allocated after the ION buffer because it doesn't require much Linux-specific knowledge to reason about semantics: We only need to identify relevant syscalls and look at the kernel binary in a disassembler to get a handle on what "objects" we have available to us.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span class="c4 c7">Selecting the out-of-bounds addition primitive and choosing to target kernel thread stacks effectively solves the problem of finding useful kernel objects to corrupt by turning what would be a codebase-wide search into a simple enumeration of interesting stack frames in a disassembler without needing to learn much in the way of Linux internals.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span class="c4 c7">Nevertheless, we are still left with two pressing questions: What allocation and heap shaping primitives do we have on Android? And what will be our final read/write capability to parallel the kernel task port?</span></p><h1 class="c21" id="h.9wrsc1l1v2yd"><span class="c5 c17 c7">Heap shaping</span></h1><p class="c6"><span class="c4 c7">I started with the heap shaping primitive since it seemed the more tangible problem.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>As previously mentioned, the vmalloc area is a region of the kernel's virtual address space in which allocations and mappings of page-level granularity can be made, comparable to the kernel_map on iOS. And just like how iOS provides a means of inspecting virtual memory regions in the kernel_map via </span><span class="c3">vmmap</span><span>, Linux provides a way of inspecting the allocations in the vmalloc area through </span><span class="c3">/proc/vmallocinfo</span><span class="c4 c7">.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>The </span><span class="c3">/proc/vmallocinfo</span><span class="c4 c7"> interface provides a simple textual description of each allocated virtual memory region inside the vmalloc area, including the start and end addresses, the size of the region in bytes, and some information about the allocation, such as the allocation site:</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span class="c5 c3 c14">0xffffff8013bd0000-0xffffff8013bd5000 20480 _do_fork+0x88/0x398 pages=4 vmalloc</span></p><p class="c6"><span class="c5 c3 c14">0xffffff8013bd8000-0xffffff8013bdd000 20480 unpurged vm_area</span></p><p class="c6"><span class="c5 c3 c14">0xffffff8013be0000-0xffffff8013be5000 20480 unpurged vm_area</span></p><p class="c6"><span class="c5 c3 c14">0xffffff8013be8000-0xffffff8013bed000 20480 _do_fork+0x88/0x398 pages=4 vmalloc</span></p><p class="c6"><span class="c5 c3 c14">0xffffff8013bed000-0xffffff8013cec000 1044480 binder_alloc_mmap_handler+0xa4/0x1e0 vmalloc</span></p><p class="c6"><span class="c5 c3 c14">0xffffff8013cec000-0xffffff8013eed000 2101248 ion_heap_map_kernel+0x110/0x16c vmap</span></p><p class="c6"><span class="c5 c3 c14">0xffffff8013eed000-0xffffff8013fee000 1052672 ion_heap_map_kernel+0x110/0x16c vmap</span></p><p class="c6"><span class="c5 c3 c14">0xffffff8013ff0000-0xffffff8013ff5000 20480 _do_fork+0x88/0x398 pages=4 vmalloc</span></p><p class="c6"><span class="c5 c3 c14">0xffffff8013ff8000-0xffffff8013ffd000 20480 unpurged vm_area</span></p><p class="c6"><span class="c5 c3 c14">0xffffff8014000000-0xffffff80140ff000 1044480 binder_alloc_mmap_handler+0xa4/0x1e0 vmalloc</span></p><p class="c6"><span class="c5 c3 c14">0xffffff8014108000-0xffffff801410d000 20480 _do_fork+0x88/0x398 pages=4 vmalloc</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>This gives us a way to visualize the vmalloc area and ensure that our allocations are grooming the heap as we want them to.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>Our heap shaping needs to place a kernel thread stack at a fixed offset after the ION buffer mapping. By inspecting the kernel source code and the </span><span class="c3">vmallocinfo</span><span> output, I determined that kernel stacks consisted of 4 pages (</span><span class="c3">0x4000</span><span class="c4 c7"> bytes) of data but also included a guard page after the allocation that was included in the size. Thus, my initial heap shaping idea was:</span></p><p class="c6 c8"><span class="c4 c7"></span></p><ol class="c32 lst-kix_t794825wy5z-0 start" start="1"><li class="c6 c22"><span>Allocate large allocation of say </span><span class="c3">0x80000</span><span class="c4 c7"> bytes.</span></li><li class="c6 c22"><span>Spray kernel stacks by creating new threads to fill up all </span><span class="c3">0x5000</span><span>-byte holes in the vmalloc area before our large </span><span class="c3">0x80000</span><span class="c4 c7">-byte allocation.</span></li><li class="c6 c22"><span>Free the large allocation. This should make an </span><span class="c3">0x80000</span><span>-byte hole with no </span><span class="c3">0x5000</span><span class="c4 c7">-byte holes before it.</span></li><li class="c6 c22"><span class="c4 c7">Spray several more kernel stacks by creating new threads; these stacks should fall into the beginning of the hole, and also fill any earlier holes created by exiting threads from other processes.</span></li><li class="c6 c22"><span>Map the ION buffer into the kernel by invoking the </span><span class="c3">VS4L_VERTEXIOC_S_GRAPH</span><span class="c4 c7"> ioctl, but without triggering the out-of-bounds addition. The ION buffer mapping should also fall into the hole.</span></li><li class="c6 c22"><span class="c4 c7">Spray more kernel stacks by creating new threads. These should fall into the hole as well, and one of them should land directly after the ION buffer.</span></li></ol><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span class="c4 c7">Unfortunately, when I proposed this strategy to my teammates, Jann Horn pointed out a problem. In XNU, when you free a kernel_map allocation, that virtual memory region becomes immediately available for reuse. However, in Linux, freeing a vmalloc allocation usually just marks the allocation as freed without allowing you to immediately reclaim the virtual memory range; instead, the range becomes an "unpurged vm_area", and these areas are only properly freed and reclaimed once the amount of unpurged vm_area memory crosses a certain threshold. The reason Linux batches reclaiming freed virtual memory regions like this is to minimize the number of expensive global TLB flushes.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span class="c4 c7">Consequently, this approach doesn't work exactly as we'd like: it's hard to know precisely when the unpurged vm_area allocations will be purged, so we can't be sure that we're reclaiming the hole to arrange our allocations. Nevertheless, it is possible to force a vm_area purge if you allocate and free enough memory. We just can't rely on the exact timing.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>Even though the straightforward approach wouldn't work, Ben and Jann did help me identify a useful allocation site for spraying controlled-size allocations using </span><span class="c3">vmalloc()</span><span>. The function </span><span class="c3">binder_alloc_mmap_handler()</span><span>, which implements </span><span class="c3">mmap()</span><span> called on </span><span class="c3">/dev/binder</span><span> file descriptors, contains a call to </span><span class="c3">get_vm_area()</span><span> on the specific kernel version I was exploiting. Thus, the following sequence of operations would allow me to perform a </span><span class="c3">vmalloc()</span><span class="c4 c7"> of any size up to 4 MiB:</span></p><p class="c6 c8"><span class="c4 c7"></span></p><a id="t.c9d72297a9046f9d372377783d04711fef7d1d35"></a><a id="t.8"></a><table class="c10"><tbody><tr class="c15"><td class="c19" colspan="1" rowspan="1"><p class="c9"><span class="c2">int</span><span class="c1"> binder_fd </span><span class="c0">=</span><span class="c1"> open</span><span class="c0">(</span><span class="c35">"/dev/binder"</span><span class="c0">,</span><span class="c1"> O_RDONLY</span><span class="c0">);</span></p><p class="c9"><span class="c2">void</span><span class="c1"> </span><span class="c0">*</span><span class="c1">binder_map </span><span class="c0">=</span><span class="c1"> mmap</span><span class="c0">(</span><span class="c1">NULL</span><span class="c0">,</span><span class="c1"> vmalloc_size</span><span class="c0">,</span></p><p class="c9"><span class="c1"> PROT_READ</span><span class="c0">,</span><span class="c1"> MAP_PRIVATE</span><span class="c0">,</span><span class="c1"> binder_fd</span><span class="c0">,</span><span class="c1"> </span><span class="c16">0</span><span class="c0">);</span></p></td></tr></tbody></table><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span class="c4 c7">The main annoyance with this approach is that only a single allocation can be made using each binder fd; to make multiple allocations, you need to open multiple fds.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>On my rooted Samsung Galaxy S10 test device, it appeared that the vmalloc area contained a lot of unreserved space at the end, in particular between </span><span class="c3">0xffffff8050000000</span><span> and </span><span class="c3">0xffffff80f0000000</span><span class="c4 c7">:</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span class="c5 c3 c14">0xffffff804a400000-0xffffff804a800000 4194304 vm_map_ram</span></p><p class="c6"><span class="c5 c3 c14">0xffffff804a800000-0xffffff804ac00000 4194304 vm_map_ram</span></p><p class="c6"><span class="c5 c3 c14">0xffffff804ac00000-0xffffff804b000000 4194304 vm_map_ram</span></p><p class="c6"><span class="c5 c3 c14">0xffffff80f9e00000-0xffffff80faa00000 12582912 phys=0x00000009ef800000</span></p><p class="c6"><span class="c5 c3 c14">0xffffff80fafe0000-0xffffff80fede1000 65015808</span></p><p class="c6"><span class="c5 c3 c14">0xffffff80fefe0000-0xffffff80ff5e1000 6295552</span></p><p class="c6"><span class="c5 c3 c14">0xffffff8100d00000-0xffffff8100d3a000 237568 phys=0x0000000095000000</span></p><p class="c6"><span class="c5 c3 c14">0xffffffbebfdc8000-0xffffffbebfe80000 753664 pcpu_get_vm_areas+0x0/0x744 vmalloc</span></p><p class="c6"><span class="c5 c3 c14">0xffffffbebfe80000-0xffffffbebff38000 753664 pcpu_get_vm_areas+0x0/0x744 vmalloc</span></p><p class="c6"><span class="c5 c3 c14">0xffffffbebff38000-0xffffffbebfff0000 753664 pcpu_get_vm_areas+0x0/0x744 vmalloc</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span class="c4 c7">Thus, I came up with an alternative strategy to place a kernel thread stack after the ION buffer mapping:</span></p><p class="c6 c8"><span class="c4 c7"></span></p><ol class="c32 lst-kix_97v7yur38q2r-0 start" start="1"><li class="c6 c22"><span>Open a large number of </span><span class="c3">/dev/binder</span><span class="c4 c7"> fds.</span></li><li class="c6 c22"><span class="c4 c7">Spray a large amount of vmalloc memory using the binder fds and then close them, forcing a vm_area purge. We can't be sure of exactly when this purge occurs, so some unpurged vm_area regions will still be left. But forcing a purge will expose any holes at the start of the vmalloc area so that we can fill them in the next step. This prevents these holes from opening up at a later stage, messing up the groom.</span></li><li class="c6 c22"><span>Now spray many allocations to the binder fds in decreasing sizes: 4 MiB, 2 MiB, 1 MiB, etc., all the way down to </span><span class="c3">0x4000</span><span> bytes. The idea is to efficiently fill holes in the vmalloc heap so that we eventually start allocating from clean virtual address space, like the </span><span class="c3">0xffffff8050000000</span><span> region identified earlier. The first binder vmallocs will fill the large holes, then later allocs will fill smaller and smaller holes, until eventually all </span><span class="c3">0x5000</span><span>-byte holes are filled. (It is </span><span class="c3">0x5000</span><span> not </span><span class="c3">0x4000</span><span class="c4 c7"> because the allocations have a guard page at the end.)</span></li><li class="c6 c22"><span>Now create some user threads. The threads' kernel stacks will allocate their </span><span class="c3">0x4000</span><span>-byte stacks (which are </span><span class="c3">0x5000</span><span class="c4 c7">-byte reservations with the guard page) from the fresh virtual memory area at the end of the vmalloc region.</span></li><li class="c6 c22"><span>Now trigger the vulnerable ioctl to map the ION buffer into the vmalloc region. If the ION buffer is chosen to have size </span><span class="c3">0x4000</span><span class="c4 c7"> bytes, it will also be placed in the fresh virtual memory area at the end of the vmalloc region.</span></li><li class="c6 c22"><span class="c4 c7">Finally, create some more user threads. Once again, their kernel stacks will be allocated from the fresh VA space, and they will thus be placed directly after the ION buffer, achieving our goal.</span></li></ol><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>This technique actually seemed to work reasonably well. By dumping </span><span class="c3">/proc/vmallocinfo</span><span class="c4 c7"> at each step, it's possible to check that the kernel heap is being laid out as we hope. And indeed, it did appear that we were getting the ION buffer allocated directly before kernel thread stacks:</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span class="c5 c3 c14">0xffffff8083ba0000-0xffffff8083ba5000 20480 _do_fork+0x88/0x398 pages=4 vmalloc</span></p><p class="c6"><span class="c5 c3 c14">0xffffff8083ba8000-0xffffff8083bad000 20480 _do_fork+0x88/0x398 pages=4 vmalloc</span></p><p class="c6"><span class="c5 c12 c14">0xffffff8083bad000-0xffffff8083bb2000 20480 ion_heap_map_kernel+0x110/0x16c vmap</span></p><p class="c6"><span class="c5 c3 c14">0xffffff8083bb8000-0xffffff8083bbd000 20480 _do_fork+0x88/0x398 pages=4 vmalloc</span></p><p class="c6"><span class="c5 c3 c14">0xffffff8083bc0000-0xffffff8083bc5000 20480 _do_fork+0x88/0x398 pages=4 vmalloc</span></p><p class="c6"><span class="c5 c3 c14">0xffffff8083bc8000-0xffffff8083bcd000 20480 _do_fork+0x88/0x398 pages=4 vmalloc</span></p><p class="c6"><span class="c5 c3 c14">0xffffff8083bd0000-0xffffff8083bd5000 20480 _do_fork+0x88/0x398 pages=4 vmalloc</span></p><p class="c6"><span class="c5 c3 c14">0xffffff80f9e00000-0xffffff80faa00000 12582912 phys=0x00000009ef800000</span></p><p class="c6"><span class="c5 c3 c14">0xffffff80fafe0000-0xffffff80fede1000 65015808</span></p><p class="c6"><span class="c5 c3 c14">0xffffff80fefe0000-0xffffff80ff5e1000 6295552</span></p><h1 class="c21" id="h.aioe1ct79jrt"><span>Stack</span><span class="c5 c17 c7"> manipulation</span></h1><p class="c6"><span class="c4 c7">At this point we can groom the kernel heap to place the kernel stack for one of our process's threads directly after the ION buffer, giving us the ability to manipulate the thread's stack while it is blocked in a syscall. The next question is what we should manipulate?</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span class="c4 c7">In order to answer this, we really need to understand the constraints of our bug. As discussed above, our primitive is the ability to perform an out-of-bounds addition on a 32-bit unsigned value, adding in the ION buffer's device address (daddr), so long as the original value being modified is less than the ION buffer's size.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>The code in </span><span class="c3">npu-session.c</span><span class="c4 c7"> performs a copious amount of logging, so it's possible to check the address of the ION allocation from a root shell using the following command:</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span class="c4 c3">cat /proc/kmsg | grep ncp_ion_map</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>In my early tests, the ION buffer's daddr was usually between </span><span class="c3">0x80500000</span><span> and </span><span class="c3">0x80700000</span><span>, and always matched the masks </span><span class="c3">0x80xx0000</span><span>. If we use a size of </span><span class="c3">0x4000</span><span> for our ION buffer, then that means our primitive will transform a small positive value on the stack into a very large positive (unsigned) or negative (signed) value; for example, we might transform </span><span class="c3">0x1000</span><span> into </span><span class="c3">0x80611000</span><span class="c4 c7">.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>How would we use this to gain code execution? Jann suggested a very interesting trick that could block calls to the functions </span><span class="c3">copy_from_user()</span><span> and </span><span class="c3">copy_to_user()</span><span class="c4 c7"> for arbitrary amounts of time, which could greatly expand the set of block points available during system calls.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span class="c3">copy_from_user()</span><span> and </span><span class="c3">copy_to_user()</span><span> are the Linux equivalents of XNU's </span><span class="c3">copyin()</span><span> and </span><span class="c3">copyout()</span><span>: They are used to copy memory from userspace into the kernel (</span><span class="c3">copy_from_user()</span><span>) or to copy memory from the kernel into userspace (</span><span class="c3">copy_to_user()</span><span>). It's well known that these operations can be blocked by using tricks involving </span><span class="c11"><a class="c26" href="https://www.google.com/url?q=https://www.kernel.org/doc/html/latest/admin-guide/mm/userfaultfd.html&sa=D&ust=1608578955091000&usg=AOvVaw06YNTMv0NBVU6zjZNk9XNM">userfaultfd</a></span><span>, but this won't be available to us in an exploit from app context. Nevertheless, Jann had discovered that similar tricks could be done using </span><span class="c11"><a class="c26" href="https://www.google.com/url?q=https://developer.android.com/reference/android/os/ProxyFileDescriptorCallback&sa=D&ust=1608578955091000&usg=AOvVaw1Q9-r_4razsXpR0WAGVDWk">proxy file descriptors</a></span><span> (an abstraction over </span><span class="c11"><a class="c26" href="https://www.google.com/url?q=https://www.kernel.org/doc/html/latest/filesystems/fuse.html&sa=D&ust=1608578955091000&usg=AOvVaw3r-EKwzpOcxxmIIOjxyZZ8">FUSE</a></span><span> provided by </span><span class="c3">system_server</span><span> and </span><span class="c3">vold</span><span class="c4 c7">), essentially allowing page faults on userspace pages to be blocked for arbitrary amounts of time.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>One particularly interesting target while blocking a </span><span class="c3">copy_from_user()</span><span> operation would be </span><span class="c3">copy_from_user()</span><span> itself. When </span><span class="c3">copy_from_user()</span><span> performs the actual copy operation in </span><span class="c3">__arch_copy_from_user()</span><span>, the first access to the magical blocking memory region will trigger a page fault, causing an exception to be delivered and spilling all the registers of </span><span class="c3">__arch_copy_from_user()</span><span> to the kernel stack while the fault is being processed. During that time, we could come in on another thread using our out-of-bounds addition to increase the size argument to </span><span class="c3">__arch_copy_from_user()</span><span> where it is spilled onto the stack. That way, once we service the page fault via the proxy file descriptor and allow the exception to return, the modified size value will be popped back into the register and </span><span class="c3">__arch_copy_from_user()</span><span> will copy more data than expected into kernel memory. If the buffer being copied into lives on the stack, this gives us a deterministic controlled stack buffer overflow that we can use to clobber a return address and get code execution. And the same idea can be applied to </span><span class="c3">copy_to_user()</span><span class="c4 c7"> to copy extra memory out of the kernel instead, disclosing kernel stack contents and allowing us to break KASLR and leak the stack cookie.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>In order to figure out which spilled register we'd need to target, I looked at </span><span class="c3">__arch_copy_to_user()</span><span> in IDA. Unfortunately, there's one other complication to make this technique work: due to unrolling, </span><span class="c3">__arch_copy_to_user()</span><span> will only enter a loop if the size is at least 128 bytes on entry. This means that we'll need to target a syscall that calls </span><span class="c3">copy_to_user()</span><span class="c4 c7"> with a size of at least 128 bytes.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>Fortunately, it didn't take long to find several good stack disclosure candidates, such as the </span><span class="c3">newuname()</span><span class="c4 c7"> syscall:</span></p><p class="c6 c8"><span class="c4 c7"></span></p><a id="t.92d234b7a55ac5f03bc9a0fa86b56b1f0c7ab3a8"></a><a id="t.9"></a><table class="c10"><tbody><tr class="c15"><td class="c19" colspan="1" rowspan="1"><p class="c9"><span class="c1">SYSCALL_DEFINE1</span><span class="c0">(</span><span class="c1">newuname</span><span class="c0">,</span><span class="c1"> </span><span class="c2">struct</span><span class="c1"> new_utsname __user </span><span class="c0">*,</span><span class="c1"> name</span><span class="c0">)</span></p><p class="c9"><span class="c0">{</span></p><p class="c9"><span class="c1"> </span><span class="c2">struct</span><span class="c1"> new_utsname tmp</span><span class="c0">;</span></p><p class="c9 c8"><span class="c1 c5"></span></p><p class="c9"><span class="c1"> down_read</span><span class="c0">(&</span><span class="c1">uts_sem</span><span class="c0">);</span></p><p class="c9"><span class="c1"> memcpy</span><span class="c0">(&</span><span class="c1">tmp</span><span class="c0">,</span><span class="c1"> utsname</span><span class="c0">(),</span><span class="c1"> </span><span class="c2">sizeof</span><span class="c0">(</span><span class="c1">tmp</span><span class="c0">));</span></p><p class="c9"><span class="c1"> up_read</span><span class="c0">(&</span><span class="c1">uts_sem</span><span class="c0">);</span></p><p class="c9"><span class="c1"> </span><span class="c2">if</span><span class="c1"> </span><span class="c0">(</span><span class="c1">copy_to_user</span><span class="c0">(</span><span class="c1">name</span><span class="c0">,</span><span class="c1"> </span><span class="c0">&</span><span class="c1">tmp</span><span class="c0">,</span><span class="c1"> </span><span class="c2">sizeof</span><span class="c0">(</span><span class="c1">tmp</span><span class="c0">)))</span></p><p class="c9"><span class="c1"> </span><span class="c2">return</span><span class="c1"> </span><span class="c0">-</span><span class="c1">EFAULT</span><span class="c0">;</span></p><p class="c9"><span class="c0">...</span></p><p class="c9"><span class="c0">}</span></p></td></tr></tbody></table><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>The </span><span class="c3">new_utsname</span><span> struct that gets copied out is </span><span class="c3">0x186</span><span> bytes, which means we should enter the looping path in </span><span class="c3">__arch_copy_to_user()</span><span>. When that access faults, the register containing the copy size </span><span class="c3">0x186</span><span> will be spilled to the stack, and we can change it to a very large value like </span><span class="c3">0x80610186</span><span> before unblocking the fault. To prevent the copyout from running off the end of the kernel stack, we will have the next page in userspace after the magic blocking page be unmapped, causing the </span><span class="c3">__arch_copy_to_user()</span><span class="c4 c7"> to terminate early and cleanly.</span></p><h1 class="c21" id="h.sjienddsukx1"><span class="c5 c17 c7">The write path</span></h1><p class="c6"><span class="c4 c7">The above trick should work for reading past the end of a stack buffer; what about for writing?</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>Here again I ran into a new complication, and this one was significant: </span><span class="c3">copy_from_user()</span><span class="c4 c7"> will zero out any unused space in the buffer that was not successfully copied from userspace:</span></p><p class="c6 c8"><span class="c4 c7"></span></p><a id="t.60aeaa4a08657d5e20ef69c6407f7695560441df"></a><a id="t.10"></a><table class="c10"><tbody><tr class="c15"><td class="c19" colspan="1" rowspan="1"><p class="c9"><span class="c1">_copy_from_user</span><span class="c0">(</span><span class="c2">void</span><span class="c1"> </span><span class="c0">*</span><span class="c1">to</span><span class="c0">,</span><span class="c1"> </span><span class="c2">const</span><span class="c1"> </span><span class="c2">void</span><span class="c1"> __user </span><span class="c0">*</span><span class="c2">from</span><span class="c0">,</span><span class="c1"> </span><span class="c2">unsigned</span><span class="c1"> </span><span class="c2">long</span><span class="c1"> n</span><span class="c0">)</span></p><p class="c9"><span class="c0">{</span></p><p class="c9"><span class="c1"> </span><span class="c2">unsigned</span><span class="c1"> </span><span class="c2">long</span><span class="c1"> res </span><span class="c0">=</span><span class="c1"> n</span><span class="c0">;</span></p><p class="c9"><span class="c1"> might_fault</span><span class="c0">();</span></p><p class="c9"><span class="c1"> </span><span class="c2">if</span><span class="c1"> </span><span class="c0">(</span><span class="c1">likely</span><span class="c0">(</span><span class="c1">access_ok</span><span class="c0">(</span><span class="c1">VERIFY_READ</span><span class="c0">,</span><span class="c1"> </span><span class="c2">from</span><span class="c0">,</span><span class="c1"> n</span><span class="c0">)))</span><span class="c1"> </span><span class="c0">{</span></p><p class="c9"><span class="c1"> kasan_check_write</span><span class="c0">(</span><span class="c1">to</span><span class="c0">,</span><span class="c1"> n</span><span class="c0">);</span></p><p class="c9"><span class="c1"> res </span><span class="c0">=</span><span class="c1"> raw_copy_from_user</span><span class="c0">(</span><span class="c1">to</span><span class="c0">,</span><span class="c1"> </span><span class="c2">from</span><span class="c0">,</span><span class="c1"> n</span><span class="c0">);</span></p><p class="c9"><span class="c1"> </span><span class="c0">}</span></p><p class="c9"><span class="c1"> </span><span class="c2">if</span><span class="c1"> </span><span class="c0">(</span><span class="c1">unlikely</span><span class="c0">(</span><span class="c1">res</span><span class="c0">))</span></p><p class="c9"><span class="c1"> memset</span><span class="c0">(</span><span class="c1">to </span><span class="c0">+</span><span class="c1"> </span><span class="c0">(</span><span class="c1">n </span><span class="c0">-</span><span class="c1"> res</span><span class="c0">),</span><span class="c1"> </span><span class="c16">0</span><span class="c0">,</span><span class="c1"> res</span><span class="c0">);</span></p><p class="c9"><span class="c1"> </span><span class="c2">return</span><span class="c1"> res</span><span class="c0">;</span></p><p class="c9"><span class="c0">}</span></p></td></tr></tbody></table><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>This means that </span><span class="c3">copy_from_user()</span><span> won't let us truncate the write early as we did with </span><span class="c3">copy_to_user()</span><span> by having an unmapped page after the blocking region. Instead, it will try to memset the memory past the end of the buffer to zero, all the way up to the modified size of at least </span><span class="c3">0x80500000</span><span>. </span><span class="c3">copy_from_user()</span><span class="c4 c7"> is out.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>Thankfully, there are some calls to the alternative function </span><span class="c3">__copy_from_user()</span><span class="c4 c7">, which does not perform this unwanted memset. But these are much rarer, and thus we'll have a very limited selection of syscalls to target.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>In fact, pretty much the only call to </span><span class="c3">__copy_from_user()</span><span> with a large size that I could find was in </span><span class="c3">restore_fpsimd_context()</span><span>, reached via the </span><span class="c3">sys_rt_sigreturn()</span><span> syscall. This syscall seems to be related to signal handling, and in particular restoring user context after returning from a signal handler. This particular </span><span class="c3">__copy_from_user()</span><span> call is responsible for copying in the floating-point register state (registers </span><span class="c3">Q0</span><span> - </span><span class="c3">Q31</span><span class="c4 c7">) after validating the signal frame on the user's stack.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>The size parameter at this callsite is 512 (</span><span class="c3">0x200</span><span>) bytes, which is large enough to enter the looping path in </span><span class="c3">__arch_copy_from_user()</span><span class="c4 c7">. However, the address of the floating point register state that is read from during the copy-in is part of the userspace thread's signal frame on the stack, so it would be very tricky to ensure that both</span></p><p class="c6 c8"><span class="c4 c7"></span></p><ol class="c32 lst-kix_41dur2ixsyfx-0 start" start="1"><li class="c6 c22"><span>the first access to the blocking page (which must be part of the thread's stack) is in the target call to </span><span class="c3">__copy_from_user()</span><span> rather than</span><span class="c4 c7"> earlier in the signal frame parsing, and</span></li><li class="c6 c22"><span>the address passed to </span><span class="c3">__copy_from_user()</span><span class="c4 c7"> is deep enough into the blocking page that we hit a subsequent unmapped page in userspace before overflowing off the end of the kernel stack.</span></li></ol><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>These two requirements are in direct conflict: Since the signal frame before the floating-point register state will be accessed in </span><span class="c3">parse_user_sigframe()</span><span>, the first requirement effectively means we need to put the floating-point state at the very start of the blocking page, so that the values right before can be accessed without triggering the blocking fault. But at the same time, the second requirement forces us to put the floating-point state towards the end of the blocking page so that we can terminate the </span><span class="c3">__arch_copy_from_user()</span><span class="c4 c7"> before running off the end of the kernel stack and triggering a panic. It's impossible to satisfy both of these requirements.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>At this point I started looking at alternative ways to use our primitive to work around this conflict. In particular, we know that the </span><span class="c3">__arch_copy_from_user()</span><span> size parameter on entry will be </span><span class="c3">0x200</span><span>, that ION buffers have addresses like </span><span class="c3">0x80xx0000</span><span class="c4 c7">, and that our addition need not be aligned. So, what if we overlapped only the most-significant byte of the ION address with the least-significant byte of the size parameter?</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>When </span><span class="c3">__arch_copy_to_user()</span><span> faults, its registers will be spilled to memory in the order </span><span class="c3">X0</span><span>, </span><span class="c3">X1</span><span>, </span><span class="c3">X2</span><span>, and so on. Register </span><span class="c3">X2</span><span> holds the size value, which in our case will be </span><span class="c3">0x200</span><span>, and </span><span class="c3">X1</span><span> stores the userspace address being copied from. If we assume the first 3 bytes of the userspace address are zero, then we get the following layout of </span><span class="c3">X1</span><span> and </span><span class="c3">X2</span><span class="c4 c7"> in memory:</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span class="c3"> </span><span class="c12">X1</span><span class="c3"> | </span><span class="c4 c12">X2</span></p><p class="c6"><span class="c4 c3">UU UU UU UU UU 00 00 00 | 00 02 00 00 00 00 00 00</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>Then, if we come in with our unaligned addition with a daddr of </span><span class="c3">0x80610000</span><span class="c4 c7">:</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span class="c3"> </span><span class="c12">X1</span><span class="c3"> | </span><span class="c12">X2</span><span class="c3"><br>UU UU UU UU UU </span><span class="c12">00</span><span class="c3"> </span><span class="c12">00</span><span class="c3"> </span><span class="c12">61</span><span class="c3"> | </span><span class="c12">80</span><span class="c4 c3"> 02 00 00 00 00 00 00</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>Thus, we've effectively increased the value of </span><span class="c3">X2</span><span> from </span><span class="c3">0x200</span><span> to </span><span class="c3">0x280</span><span>, which should be large enough to corrupt the stack frame of </span><span class="c3">sys_rt_sigreturn()</span><span class="c4 c7"> but small enough to avoid running off the end of the kernel stack.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>The consequence of using an unaligned addition like this is that we've also corrupted the value of </span><span class="c3">X1</span><span class="c4 c7">, which stores the userspace address from which to copy in the data. Instead of the upper bytes being all 0, the top byte is now nonzero. Quite fortunately, however, this isn't a problem because the kernel was compiled with support for the Top Byte Ignore (TBI) architectural feature enabled for userspace, which means that the top byte of the pointer will be ignored for the purpose of address translation by the CPU.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span class="c4 c7">This gives us a reasonably complete exploit flow:</span></p><p class="c6 c8"><span class="c4 c7"></span></p><ol class="c32 lst-kix_s2bakucqamn4-0 start" start="1"><li class="c6 c22"><span class="c4 c7">Create 2 "blocking pages" using proxy file descriptors.</span></li><li class="c6 c22"><span>Map the first blocking page before an unmapped page, and invoke the </span><span class="c3">newuname()</span><span> syscall with a pointer towards the end of the first blocking page. The </span><span class="c3">copy_to_user()</span><span class="c4 c7"> will fault and block, waiting on the corresponding proxy fd.</span></li><li class="c6 c22"><span>Use the out-of-bounds addition to increase the spilled value of register </span><span class="c3">X2</span><span> from </span><span class="c3">__arch_copy_to_user()</span><span class="c4 c7">.</span></li><li class="c6 c22"><span>Fulfill the requested data on the first proxy fd, allowing the page fault to complete and resuming </span><span class="c3">__arch_copy_to_user()</span><span class="c4 c7">. The copy-out will proceed past the ends of the stack buffer, disclosing the stack cookie and the return address, but stopping at the unmapped page.</span></li><li class="c6 c22"><span>Create a fake signal stack frame around a mapping of the second blocking page and invoke the </span><span class="c3">sys_rt_sigreturn()</span><span class="c4 c7"> syscall.</span></li><li class="c6 c22"><span class="c3">sys_rt_sigreturn()</span><span> will call </span><span class="c3">__copy_from_user()</span><span> with size </span><span class="c3">0x200</span><span class="c4 c7"> and fault trying to access the blocking page.</span></li><li class="c6 c22"><span>Use the out-of-bounds addition to increase the spilled value of register </span><span class="c3">X2</span><span> from </span><span class="c3">__arch_copy_from_user()</span><span>, but align the addition such that only the least significant byte of </span><span class="c3">X2</span><span> is changed. This will bump the spilled value from </span><span class="c3">0x200</span><span> to </span><span class="c3">0x280</span><span class="c4 c7">.</span></li><li class="c6 c22"><span>Fulfill the requested data on the second proxy fd, allowing the page fault to complete and resuming </span><span class="c3">__arch_copy_from_user()</span><span class="c4 c7">. The copy-in will overflow the stack buffer, overwriting the return address.</span></li><li class="c6 c22"><span>When </span><span class="c3">sys_rt_sigreturn()</span><span> returns, we will gain code execution.</span></li></ol><h1 class="c21" id="h.wn60yhb83bdo"><span class="c5 c17 c7">Blocking problems</span></h1><p class="c6"><span>When I tried to implement this technique, I ran into a fatal problem: I couldn't seem to </span><span class="c3">mmap()</span><span> the proxy file descriptor in order to create the memory regions that would be used to block </span><span class="c3">copy_to/from_user()</span><span class="c4 c7">.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>I checked with Jann, and he discovered that </span><span class="c11"><a class="c26" href="https://www.google.com/url?q=https://android.googlesource.com/platform/system/sepolicy/%2B/975215578f0b10a35a36c02e7f265a063796729e%255E%2521/%23F0&sa=D&ust=1608578955110000&usg=AOvVaw1PLwg2HYA0fc46ZEWPu4xg">the SELinux policy change</a></span><span class="c4 c7"> that would allow mapping the proxy file descriptors was quite recent, and unfortunately too new to be available on my device:</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span class="c4 c3"> # For app fuse.</span></p><p class="c6"><span class="c4 c3">-allow appdomain app_fuse_file:file { getattr read append write };</span></p><p class="c6"><span class="c4 c3">+allow appdomain app_fuse_file:file { getattr read append write map };</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>This change was committed in March of 2020, and apparently would not migrate to my device until after the NPU bug I was exploiting would be fixed. Thus, I couldn't rely on blocking </span><span class="c3">copy_to/from_user()</span><span class="c4 c7"> after all, and would need to find an alternative target.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>Thankfully, due to his copious knowledge of Linux internals, Jann was quickly able to suggest a few different strategies worth investigating. Since this post is already quite long I'll avoid explaining each of them and jump directly to the one that was the most promising: </span><span class="c3">select()</span><span class="c4 c7">.</span></p><h1 class="c21" id="h.d15omm66uzt7"><span class="c5 c17 c7">Revisiting the read</span></h1><p class="c6"><span class="c4 c7">Since my previous strategy relied on the blocking memory region for both the stack disclosure (read) and stack buffer overflow (write) steps, I'd need to revisit both of them. But for right now, we'll focus on the read part.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>The </span><span class="c3">pselect()</span><span> system call is an interesting target for our out-of-bounds addition primitive because it will deterministically block before calling </span><span class="c3">copy_to_user()</span><span> to copy out the contents of a stack buffer. Not only that, but the amount of memory to copy out to userspace is controllable rather than being hardcoded. Thus, we'll have an opportunity to modify the size parameter in a call to </span><span class="c3">copy_to_user()</span><span> while </span><span class="c3">pselect()</span><span class="c4 c7"> is blocked, and increasing the size should cause the kernel to disclose out-of-bounds stack memory.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>Here's the relevant code from </span><span class="c3">core_sys_select()</span><span class="c4 c7">, which implements the core logic of the syscall:</span></p><p class="c6 c8"><span class="c4 c7"></span></p><a id="t.78437bd369a1eb6f39c639d032621c2715fac35e"></a><a id="t.11"></a><table class="c10"><tbody><tr class="c15"><td class="c19" colspan="1" rowspan="1"><p class="c9"><span class="c2">int</span><span class="c1"> core_sys_select</span><span class="c0">(</span><span class="c2">int</span><span class="c1"> n</span><span class="c0">,</span><span class="c1"> fd_set __user </span><span class="c0">*</span><span class="c1">inp</span><span class="c0">,</span><span class="c1"> fd_set __user </span><span class="c0">*</span><span class="c1">outp</span><span class="c0">,</span></p><p class="c9"><span class="c1"> fd_set __user </span><span class="c0">*</span><span class="c1">exp</span><span class="c0">,</span><span class="c1"> </span><span class="c2">struct</span><span class="c1"> timespec64 </span><span class="c0">*</span><span class="c1">end_time</span><span class="c0">)</span></p><p class="c9"><span class="c0">{</span></p><p class="c9"><span class="c0">...</span></p><p class="c9"><span class="c1"> </span><span class="c23">/* Allocate small arguments on the stack to save memory and be faster */</span></p><p class="c9"><span class="c1"> </span><span class="c2">long</span><span class="c1"> stack_fds</span><span class="c0">[</span><span class="c1">SELECT_STACK_ALLOC</span><span class="c0">/</span><span class="c2">sizeof</span><span class="c0">(</span><span class="c2">long</span><span class="c0">)];</span></p><p class="c9"><span class="c0">...</span></p><p class="c9"><span class="c1"> </span><span class="c23">/*</span></p><p class="c9"><span class="c23"> * We need 6 bitmaps (in/out/ex for both incoming and outgoing),</span></p><p class="c9"><span class="c23"> * since we used fdset we need to allocate memory in units of</span></p><p class="c9"><span class="c23"> * long-words.</span></p><p class="c9"><span class="c23"> */</span></p><p class="c9"><span class="c1"> size </span><span class="c0">=</span><span class="c1"> FDS_BYTES</span><span class="c0">(</span><span class="c1">n</span><span class="c0">);</span></p><p class="c9"><span class="c1"> bits </span><span class="c0">=</span><span class="c1"> stack_fds</span><span class="c0">;</span></p><p class="c9"><span class="c1"> </span><span class="c2">if</span><span class="c1"> </span><span class="c0">(</span><span class="c1">size </span><span class="c0">></span><span class="c1"> </span><span class="c2">sizeof</span><span class="c0">(</span><span class="c1">stack_fds</span><span class="c0">)</span><span class="c1"> </span><span class="c0">/</span><span class="c1"> </span><span class="c16">6</span><span class="c0">)</span><span class="c1"> </span><span class="c0">{</span></p><p class="c9"><span class="c1"> </span><span class="c23">/* Not enough space in on-stack array; must use kmalloc */</span></p><p class="c9"><span class="c0">...</span></p><p class="c9"><span class="c1"> bits </span><span class="c0">=</span><span class="c1"> kvmalloc</span><span class="c0">(</span><span class="c1">alloc_size</span><span class="c0">,</span><span class="c1"> GFP_KERNEL</span><span class="c0">);</span></p><p class="c9"><span class="c0">...</span></p><p class="c9"><span class="c1"> </span><span class="c0">}</span></p><p class="c9"><span class="c1"> fds</span><span class="c0">.</span><span class="c2">in</span><span class="c1"> </span><span class="c0">=</span><span class="c1"> bits</span><span class="c0">;</span></p><p class="c9"><span class="c1"> fds</span><span class="c0">.</span><span class="c2">out</span><span class="c1"> </span><span class="c0">=</span><span class="c1"> bits </span><span class="c0">+</span><span class="c1"> size</span><span class="c0">;</span></p><p class="c9"><span class="c1"> fds</span><span class="c0">.</span><span class="c1">ex </span><span class="c0">=</span><span class="c1"> bits </span><span class="c0">+</span><span class="c1"> </span><span class="c16">2</span><span class="c0">*</span><span class="c1">size</span><span class="c0">;</span></p><p class="c9"><span class="c1"> fds</span><span class="c0">.</span><span class="c1">res_in </span><span class="c0">=</span><span class="c1"> bits </span><span class="c0">+</span><span class="c1"> </span><span class="c16">3</span><span class="c0">*</span><span class="c1">size</span><span class="c0">;</span></p><p class="c9"><span class="c1"> fds</span><span class="c0">.</span><span class="c1">res_out </span><span class="c0">=</span><span class="c1"> bits </span><span class="c0">+</span><span class="c1"> </span><span class="c16">4</span><span class="c0">*</span><span class="c1">size</span><span class="c0">;</span></p><p class="c9"><span class="c1"> fds</span><span class="c0">.</span><span class="c1">res_ex </span><span class="c0">=</span><span class="c1"> bits </span><span class="c0">+</span><span class="c1"> </span><span class="c16">5</span><span class="c0">*</span><span class="c1">size</span><span class="c0">;</span></p><p class="c9 c8"><span class="c1 c5"></span></p><p class="c9"><span class="c1"> </span><span class="c23">// get_fd_set() calls copy_from_user()</span></p><p class="c9"><span class="c1"> </span><span class="c2">if</span><span class="c1"> </span><span class="c0">((</span><span class="c1">ret </span><span class="c0">=</span><span class="c1"> get_fd_set</span><span class="c0">(</span><span class="c1">n</span><span class="c0">,</span><span class="c1"> inp</span><span class="c0">,</span><span class="c1"> fds</span><span class="c0">.</span><span class="c2">in</span><span class="c0">))</span><span class="c1"> </span><span class="c0">||</span></p><p class="c9"><span class="c1"> </span><span class="c0">(</span><span class="c1">ret </span><span class="c0">=</span><span class="c1"> get_fd_set</span><span class="c0">(</span><span class="c1">n</span><span class="c0">,</span><span class="c1"> outp</span><span class="c0">,</span><span class="c1"> fds</span><span class="c0">.</span><span class="c2">out</span><span class="c0">))</span><span class="c1"> </span><span class="c0">||</span></p><p class="c9"><span class="c1"> </span><span class="c0">(</span><span class="c1">ret </span><span class="c0">=</span><span class="c1"> get_fd_set</span><span class="c0">(</span><span class="c1">n</span><span class="c0">,</span><span class="c1"> exp</span><span class="c0">,</span><span class="c1"> fds</span><span class="c0">.</span><span class="c1">ex</span><span class="c0">)))</span></p><p class="c9"><span class="c1"> </span><span class="c2">goto</span><span class="c1"> </span><span class="c2">out</span><span class="c0">;</span></p><p class="c9"><span class="c1"> zero_fd_set</span><span class="c0">(</span><span class="c1">n</span><span class="c0">,</span><span class="c1"> fds</span><span class="c0">.</span><span class="c1">res_in</span><span class="c0">);</span></p><p class="c9"><span class="c1"> zero_fd_set</span><span class="c0">(</span><span class="c1">n</span><span class="c0">,</span><span class="c1"> fds</span><span class="c0">.</span><span class="c1">res_out</span><span class="c0">);</span></p><p class="c9"><span class="c1"> zero_fd_set</span><span class="c0">(</span><span class="c1">n</span><span class="c0">,</span><span class="c1"> fds</span><span class="c0">.</span><span class="c1">res_ex</span><span class="c0">);</span></p><p class="c9 c8"><span class="c1 c5"></span></p><p class="c9"><span class="c1"> </span><span class="c23">// do_select() may block</span></p><p class="c9"><span class="c1"> ret </span><span class="c0">=</span><span class="c1"> do_select</span><span class="c0">(</span><span class="c1">n</span><span class="c0">,</span><span class="c1"> </span><span class="c0">&</span><span class="c1">fds</span><span class="c0">,</span><span class="c1"> end_time</span><span class="c0">);</span></p><p class="c9"><span class="c0">...</span></p><p class="c9"><span class="c1"> </span><span class="c23">// set_fd_set() calls __copy_to_user()</span></p><p class="c9"><span class="c1"> </span><span class="c2">if</span><span class="c1"> </span><span class="c0">(</span><span class="c1">set_fd_set</span><span class="c0">(</span><span class="c1">n</span><span class="c0">,</span><span class="c1"> inp</span><span class="c0">,</span><span class="c1"> fds</span><span class="c0">.</span><span class="c1">res_in</span><span class="c0">)</span><span class="c1"> </span><span class="c0">||</span></p><p class="c9"><span class="c1"> set_fd_set</span><span class="c0">(</span><span class="c1">n</span><span class="c0">,</span><span class="c1"> outp</span><span class="c0">,</span><span class="c1"> fds</span><span class="c0">.</span><span class="c1">res_out</span><span class="c0">)</span><span class="c1"> </span><span class="c0">||</span></p><p class="c9"><span class="c1"> set_fd_set</span><span class="c0">(</span><span class="c1">n</span><span class="c0">,</span><span class="c1"> exp</span><span class="c0">,</span><span class="c1"> fds</span><span class="c0">.</span><span class="c1">res_ex</span><span class="c0">))</span></p><p class="c9"><span class="c1"> ret </span><span class="c0">=</span><span class="c1"> </span><span class="c0">-</span><span class="c1">EFAULT</span><span class="c0">;</span></p><p class="c9"><span class="c0">...</span></p><p class="c9"><span class="c0">}</span></p></td></tr></tbody></table><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>As you can see, the local variable </span><span class="c3">n</span><span> must be saved to the stack while </span><span class="c3">do_select()</span><span> blocks, which means that it can be modified before the call to </span><span class="c3">set_fd_set()</span><span> copies out the corresponding number of bytes to userspace. Also, if </span><span class="c3">n</span><span> is small, then the 256-byte </span><span class="c3">stack_fds</span><span class="c4 c7"> buffer will be used rather than a heap-allocated buffer.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>This code actually looks a bit different when compiled in the kernel due to optimization and inlining. In particular, the variable </span><span class="c3">n</span><span> is not used during the subsequent calls to </span><span class="c3">__arch_copy_to_user()</span><span>, but rather a hidden variable with the value </span><span class="c3">8</span><span> </span><span class="c3">*</span><span> </span><span class="c3">size</span><span> allocated to register </span><span class="c3">X22</span><span>. Thus, wherever </span><span class="c3">X22</span><span> gets spilled to the stack during the prologue of </span><span class="c3">do_select()</span><span>, that's the address we need to target to change the size </span><span>passed to</span><span> </span><span class="c3">__arch_copy_to_user()</span><span>.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>But there's one other unfortunate consequence we'll have to deal with when moving from the old "blocking page" technique to this new technique: Before, we were modifying the size to be copied out during the execution of </span><span class="c3">__arch_copy_to_user()</span><span> itself, after all the sanity checks had passed on the original (unmodified) value. Now, however, we're trying to pass the corrupted value directly to </span><span class="c3">__copy_to_user()</span><span>, which means we'll need to ensure that we don't trip any of the checks. In particular, </span><span class="c3">__copy_to_user()</span><span> has a call to </span><span class="c3">check_object_size()</span><span class="c4 c7"> which will fail if the copy-out extends beyond the bounds of the stack.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>Thankfully, we've already solved this particular problem already when dealing with </span><span class="c3">sys_rt_sigreturn()</span><span>. Just like in that case, we can set the offset of our out-of-bounds addition such that only the least significant byte of the spilled </span><span class="c3">X22</span><span> is modified. But for this to be compatible with the precondition for the write, that the initial value being modified is smaller than the size of the ION buffer, this means that we need the least significant byte of </span><span class="c3">X22</span><span> to be </span><span class="c3">0</span><span class="c4 c7">.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>Because </span><span class="c3">core_sys_select()</span><span> will stop using the </span><span class="c3">stack_fds</span><span> buffer if </span><span class="c3">n</span><span> is too large, this constraint actually admits only a single solution: </span><span class="c3">n</span><span> </span><span class="c3">=</span><span> </span><span class="c3">0</span><span>. That is, we will need to call </span><span class="c3">select()</span><span> on 0 file descriptors, which will cause the value </span><span class="c3">X22</span><span> </span><span class="c3">=</span><span> </span><span class="c3">0</span><span> to be stored to the stack when </span><span class="c3">do_select()</span><span> blocks, at which point we can use our out-of-bounds addition to increase </span><span class="c3">X22</span><span> to </span><span class="c3">0x80</span><span class="c4 c7">.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>Thankfully, the logic of </span><span class="c3">core_sys_select()</span><span> functions just fine with </span><span class="c3">n</span><span> </span><span class="c3">=</span><span> </span><span class="c3">0</span><span>; even better, the </span><span class="c3">stack_fds</span><span> buffer is only zeroed for positive </span><span class="c3">n</span><span>, so bumping the copy-out size to </span><span class="c3">0x80</span><span class="c4 c7"> will allow us to read uninitialized stack buffer contents. So this technique should allow us to turn the out-of-bounds addition into a useful kernel stack disclosure.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>Unfortunately, when I began implementing this, I ran into another significant problem. When the prologue of </span><span class="c3">do_select()</span><span> saves </span><span class="c3">X22</span><span> to the stack, it places register </span><span class="c3">X23</span><span> right before </span><span class="c3">X22</span><span>, and </span><span class="c3">X23</span><span> contains a kernel pointer at this point. This messes up the precondition for our out-of-bounds addition, because the unaligned value overlapping the least significant byte of </span><span class="c3">X22</span><span class="c4 c7"> will be too large:</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span class="c3"> </span><span class="c12">X23</span><span class="c3"> | </span><span class="c4 c12">X22</span></p><p class="c6"><span class="c3">XX XX XX XX 80 </span><span class="c12">ff</span><span class="c3"> </span><span class="c12">ff</span><span class="c3"> </span><span class="c12">ff</span><span class="c3"> | </span><span class="c12">00</span><span class="c4 c3"> 00 00 00 00 00 00 00</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>In order for our precondition to be met, we'd need to have an ION buffer of size at least </span><span class="c3">0x00ffffff</span><span> (really, </span><span class="c3">0x01000000</span><span class="c4 c7"> due to granularity). My impression was that ION memory is quite scarce; is it even possible to allocate an ION buffer that large?</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>It turns out, yes! I had just assumed that such a large allocation (16 MiB) would fail, but it turns out to work just fine. So, all we have to do is increase the size of the ION buffer initially allocated to </span><span class="c3">0x1000000</span><span>, and we should be able to modify register </span><span class="c3">X22</span><span class="c4 c7">.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>What about </span><span class="c3">X23</span><span>, won't that register be corrupted? It will, but thankfully </span><span class="c3">X23</span><span> happens to be unused by </span><span class="c3">core_sys_select()</span><span> after the call to </span><span class="c3">do_select()</span><span>, so it doesn't actually matter that we clobber it.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span class="c4 c7">At last, we have a viable stack disclosure strategy!</span></p><p class="c6 c8"><span class="c4 c7"></span></p><ol class="c32 lst-kix_8ndrlnfdlj1m-0 start" start="1"><li class="c6 c22"><span class="c4 c7">Allocate a 16 MiB ION buffer.</span></li><li class="c6 c22"><span class="c4 c7">Perform the vmalloc purge using binder fds.</span></li><li class="c6 c22"><span>Spray binder vmallocs to consume all vmalloc holes down to size </span><span class="c3">0x5000</span><span>, and cause </span><span class="c3">vmalloc()</span><span class="c4 c7"> to start allocating from fresh VA space.</span></li><li class="c6 c22"><span>Spray thread stacks to fill any remaining </span><span class="c3">0x5000</span><span class="c4 c7">-byte holes leading up to the fresh VA space.</span></li><li class="c6 c22"><span class="c4 c7">Map the ION buffer into the fresh VA space by invoking the vulnerable ioctl.</span></li><li class="c6 c22"><span class="c4 c7">Spray thread stacks; these should land directly after the ION buffer mapping.</span></li><li class="c6 c22"><span>Call </span><span class="c3">pselect()</span><span> from the thread directly after the ION buffer with with </span><span class="c3">n</span><span> </span><span class="c3">=</span><span> </span><span class="c3">0</span><span class="c4 c7">; this call will block for the specified timeout.</span></li><li class="c6 c22"><span>Call </span><span class="c3">ioctl()</span><span> on the main thread to perform the out-of-bounds addition, targeting the value of </span><span class="c3">X22</span><span> from </span><span class="c3">core_sys_select()</span><span>. Align the addition to bump the value from </span><span class="c3">0</span><span> to </span><span class="c3">0x80</span><span class="c4 c7">.</span></li><li class="c6 c22"><span>When </span><span class="c3">do_select()</span><span> unblocks due to timeout expiry, the modified value of </span><span class="c3">X22</span><span> will be passed to </span><span class="c3">__copy_to_user()</span><span>, disclosing uninitialized kernel stack contents to userspace. </span></li></ol><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span class="c4 c7">And after a few false starts, this strategy turned out to work perfectly:</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span class="c4 c3">Samsung NPU driver exploit</span></p><p class="c6"><span class="c4 c3">ION 0x80850000 [0x01000000]</span></p><p class="c6"><span class="c4 c3">Allocated ION buffer</span></p><p class="c6"><span class="c4 c3">Found victim thread!</span></p><p class="c6"><span class="c4 c3">buf = 0x74ec863b30</span></p><p class="c6"><span class="c4 c3">pselect() ret = 0 Success</span></p><p class="c6"><span class="c4 c3">buf[00]: 0000000000000124</span></p><p class="c6"><span class="c4 c3">buf[08]: 0000000000000015</span></p><p class="c6"><span class="c4 c3">buf[10]: ffffffc88f303b80</span></p><p class="c6"><span class="c4 c3">buf[18]: ffffff8088adbdd0</span></p><p class="c6"><span class="c4 c3">buf[20]: ffffffc89f935e00</span></p><p class="c6"><span class="c4 c3">buf[28]: ffffff8088adbd10</span></p><p class="c6"><span class="c4 c3">buf[30]: ffffff8088adbda8</span></p><p class="c6"><span class="c4 c12">buf[38]: ffffff8088adbd90</span></p><p class="c6"><span class="c4 c12">buf[40]: ffffff8008f814e8</span></p><p class="c6"><span class="c4 c3">buf[48]: 0000000000000000</span></p><p class="c6"><span class="c4 c3">buf[50]: ffffffc800000000</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>By inspecting the leaked buffer contents, it appears that </span><span class="c3">buf[38]</span><span> is a stack frame pointer and </span><span class="c3">buf[40]</span><span class="c4 c7"> is a return address. Thus we now know both the address of the victim stack, from which we can deduce the address of the ION buffer mapping, and the address of a kernel function, which allows us to break KASLR.</span></p><h1 class="c21" id="h.e7wdwat2kt99"><span class="c5 c17 c7">Revisiting the write</span></h1><p class="c6"><span class="c4 c7">Pretty much the only thing left to do is find a way to overflow a stack buffer using our primitive. Once we do this, we should be able to build a ROP chain that will give us some as-yet-unspecified kernel read/write capability.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span class="c4 c7">For now, we won't worry about what to do after the overflow because ROP should be a sufficiently generic technique to implement whatever final read/write capability we want. Admittedly, for an iOS kernel exploit the choice of final capability would have substantial influence on the shape of the exploit flow. But this is because the typical iOS exploit achieves stable kernel read/write before kernel code execution, especially since the arrival of PAC. By contrast, if we can build a stack buffer overflow, we should be able to get ROP execution directly, which is in many ways more powerful than kernel read/write.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span class="c4 c7">Actually, thinking about PAC and iOS gave me an idea.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>When a userspace thread enters the kernel, whether due to a system call, page fault, IRQ, or any other reason, the userspace thread's register state needs to be saved in order to be able to resume its execution later. This includes the general-purpose registers </span><span class="c3">X0</span><span> - </span><span class="c3">X31</span><span> as well as </span><span class="c3">SP</span><span>, </span><span class="c3">PC</span><span>, and </span><span class="c3">SPSR</span><span> ("Saved Program Status Register", alternatively called </span><span class="c3">CPSR</span><span> or </span><span class="c3">PSTATE</span><span class="c4 c7">). These values get saved to the end of the thread's kernel stack right at the beginning of the exception vector.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>When Apple implemented their PAC-based kernel control flow integrity on iOS, they needed to take care that the saved value of </span><span class="c3">SPSR</span><span> could not be tampered with. The reason is that </span><span class="c3">SPSR</span><span> is used during exception return to specify the exception level to return to: this could be EL0 when returning to userspace, or EL1 when returning to kernel mode. If </span><span class="c3">SPSR</span><span> weren't protected, then an attacker with a kernel read/write primitive could modify the saved value of </span><span class="c3">SPSR</span><span class="c4 c7"> to cause a thread that would return to userspace (EL0) to instead return to EL1, thereby breaking the kernel's control flow integrity (CFI).</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>The Samsung Galaxy S10 does not have PAC, and hence </span><span class="c3">SPSR</span><span class="c4 c7"> is not protected. This is not a security issue because kernel CFI isn't a security boundary on this device. However, it does mean that this attack could be used to gain kernel code execution.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>The idea of targeting the saved </span><span class="c3">SPSR</span><span> was appealing because we would no longer need the buffer overflow step to execute our ROP payload: assuming we could get an ION buffer allocated with a carefully chosen device address, we could modify </span><span class="c3">SPSR</span><span class="c4 c7"> directly using our out-of-bounds addition primitive.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>Concretely, when a user thread invokes a syscall, the saved </span><span class="c3">SPSR</span><span> value might be something like </span><span class="c3">0x20000000</span><span>. The least significant 4 bits of </span><span class="c3">SPSR</span><span> specify the exception level from which the exception was taken: in this case, EL0. A normal kernel thread might have a </span><span class="c3">CPSR</span><span> value of </span><span class="c3">0x80400145</span><span>; in this case, the "</span><span class="c3">5</span><span>" means that the thread is running at EL1 and on the interrupt stack (</span><span class="c3">SP_EL1</span><span class="c4 c7">).</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>Now imagine that we could get our </span><span class="c3">0x01000000</span><span>-byte ION buffer allocated with a device address of </span><span class="c3">0x85000000</span><span>. We'll also assume that we've somehow already managed to set the user thread's saved </span><span class="c3">PC</span><span> register to a kernel pointer. The saved user </span><span class="c3">PC</span><span> and </span><span class="c3">SPSR</span><span class="c4 c7"> registers look like this on the stack:</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span class="c3"> </span><span class="c12">PC</span><span class="c3"> | </span><span class="c12">SPSR</span><span class="c4 c3"><br>XX XX XX 0X 80 FF FF FF | 00 00 00 20 00 00 00 00</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>Using our out-of-bounds addition primitive to change just the least significant byte of </span><span class="c3">SPSR</span><span class="c4 c7"> would yield the following:</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span class="c3"> </span><span class="c12">PC</span><span class="c3"> | </span><span class="c12">SPSR</span><span class="c3"><br>XX XX XX 0X 80 </span><span class="c12">FF</span><span class="c3"> </span><span class="c12">FF</span><span class="c3"> </span><span class="c12">FF</span><span class="c3"> | </span><span class="c12">85</span><span class="c4 c3"> 00 00 20 00 00 00 00</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>Hence, we would have changed </span><span class="c3">SPSR</span><span> from </span><span class="c3">0x20000000</span><span> to </span><span class="c3">0x20000085</span><span class="c4 c7">, meaning that once the syscall returns, we will start executing at EL1!</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>Of course, that still leaves a few questions open: How do we get our ION buffer allocated at the desired device address? How do we set the saved </span><span class="c3">PC</span><span class="c4 c7"> value to a kernel pointer? We'll address these in turn.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>Getting the ION buffer allocated at the desired device address seemed the most urgent, since it is quite integral to this technique. In my testing it seemed that ION buffers had decently (but not perfectly) regular allocation patterns. For example, if you had just allocated an ION buffer of size </span><span class="c3">0x2000</span><span> that had device address </span><span class="c3">0x80500000</span><span>, then the next ION buffer was usually allocated with device address </span><span class="c3">0x80610000</span><span class="c4 c7">.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>I played around with the available parameters a lot, hoping to discover an underlying logic that would allow me to deterministically predict ION daddrs. For instance, if the size of the first ION allocation was between </span><span class="c3">0x1000</span><span> and </span><span class="c3">0x10000</span><span> bytes, then the next allocation would </span><span class="c24">usually</span><span> be made at a device address </span><span class="c3">0x110000</span><span> bytes later, while if the first ION allocation was between </span><span class="c3">0x11000</span><span> and </span><span class="c3">0x20000</span><span> bytes, the next allocation would </span><span class="c24">usually</span><span> be at a device address </span><span class="c3">0x120000</span><span class="c4 c7"> bytes later. These patterns seemed to suggest that predictability was tantalizingly close; however, try as I might, I couldn't seem to eliminate the variations from the patterns I observed.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>Eventually, by sheer random luck I happened to stumble upon a technique during one of my trials that would quite reliably allocate ION buffers at addresses of my choosing. Thus, we can now assume as part of our out-of-bounds addition primitive that we have control to choose the ION buffer's device address. Furthermore, the mask of available ION daddrs using this technique is much larger than I'd initially thought: </span><span class="c3">0x[89]xxxx000</span><span class="c4 c7">.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>Now, as to the question of how we'll set the saved </span><span class="c3">PC</span><span> value to a kernel pointer: The simplest approach would just be to jump to that address from userspace; this would work to set the value, but it was not clear to me whether we'd be able to block the thread in the kernel in this state long enough to modify the </span><span class="c3">SPSR</span><span> using our addition primitive from the main thread. Instead, on recommendation from my team I used the </span><span class="c3">ptrace()</span><span> API, which via the </span><span class="c3">PTRACE_GETREGSET</span><span> command provides similar functionality to XNU's </span><span class="c3">thread_set_state()</span><span class="c4 c7">.</span></p><h1 class="c21" id="h.swuax69vmipd"><span class="c5 c17 c7">Total meltdown</span></h1><p class="c6"><span>My first test after implementing this strategy was to set </span><span class="c3">PC</span><span class="c4 c7"> to the address of an instruction that would dereference an invalid memory address. The idea was that running this test should cause a kernel panic right away, since the exception return would start running in kernel mode. Then I could check the panic log to see if all the registers were being set as I expected.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>Unfortunately, my first test didn't panic: the syscall whose </span><span class="c3">SPSR</span><span class="c4 c7"> was corrupted just seemed to hang, never returning to userspace. And after several seconds of this (during which the phone was fully responsive), the phone would eventually reboot with a message about a watchdog timeout.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>This seemed quite bizarre to me. If </span><span class="c3">SPSR</span><span> wasn't being set properly, the syscall should return to EL0, not EL1, and thus we shouldn't be able to cause any sort of kernel crash. On the other hand, I was certain that </span><span class="c3">PC</span><span> was set to the address of a faulting instruction, so if </span><span class="c3">SPSR</span><span class="c4 c7"> were being set properly, I'd expect a kernel panic, not a hard hang triggering a watchdog timeout. What was going on?</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>Eventually, Jann and I discovered that this device had the </span><span class="c3">ARM64_UNMAP_KERNEL_AT_EL0</span><span> CPU feature set, which meant that the faulting instruction I was trying to jump to was being unmapped before the syscall return. Working around this seemed more trouble than it was worth: instead, I decided to abandon </span><span class="c3">SPSR</span><span class="c4 c7"> and go back to looking for ways to trigger a stack buffer overflow.</span></p><h1 class="c21" id="h.2rczj1ttbun8"><span class="c5 c17 c7">Revisiting the write (again)</span></h1><p class="c6"><span class="c4 c7">So, once again I was back to finding a way to use the out-of-bounds addition to create a stack buffer overflow.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span class="c4 c7">This was the part of the exploit development process that I was least enthusiastic about: searching through the Linux code to find specific patterns that would give me the primitives I needed. Choosing to focus on stack frames rather than heap objects certainly helped narrow the search space, but it's still tedious. Thus, I decided to focus on the parts of the kernel I'd already grown familiar with.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>The pattern that I was looking for was any place that blocks before copying data to the stack, where the amount of data to be copied was stored in a variable that would be saved during the block. Ideally, this would be a call to </span><span class="c3">copy_from_user()</span><span>, but I failed to find any useful instances of </span><span class="c3">copy_from_user()</span><span class="c4 c7"> being called after a blocking operation.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>However, a horrible, horrible idea occurred to me while looking once again at the </span><span class="c3">pselect()</span><span> syscall, and in particular at the implementation of </span><span class="c3">do_select()</span><span class="c4 c7">:</span></p><p class="c6 c8"><span class="c4 c7"></span></p><a id="t.daf9a3200bdf349d50149de11a803d231f246dd9"></a><a id="t.12"></a><table class="c10"><tbody><tr class="c15"><td class="c19" colspan="1" rowspan="1"><p class="c9"><span class="c2">static</span><span class="c1"> </span><span class="c2">int</span><span class="c1"> do_select</span><span class="c0">(</span><span class="c2">int</span><span class="c1"> n</span><span class="c0">,</span><span class="c1"> fd_set_bits </span><span class="c0">*</span><span class="c1">fds</span><span class="c0">,</span><span class="c1"> </span><span class="c2">struct</span><span class="c1"> timespec64 </span><span class="c0">*</span><span class="c1">end_time</span><span class="c0">)</span></p><p class="c9"><span class="c0">{</span></p><p class="c9"><span class="c0">...</span></p><p class="c9"><span class="c1"> retval </span><span class="c0">=</span><span class="c1"> </span><span class="c16">0</span><span class="c0">;</span></p><p class="c9"><span class="c1"> </span><span class="c2">for</span><span class="c1"> </span><span class="c0">(;;)</span><span class="c1"> </span><span class="c0">{</span></p><p class="c9"><span class="c0">...</span></p><p class="c9"><span class="c1"> inp </span><span class="c0">=</span><span class="c1"> fds</span><span class="c0">-></span><span class="c2">in</span><span class="c0">;</span><span class="c1"> outp </span><span class="c0">=</span><span class="c1"> fds</span><span class="c0">-></span><span class="c2">out</span><span class="c0">;</span><span class="c1"> exp </span><span class="c0">=</span><span class="c1"> fds</span><span class="c0">-></span><span class="c1">ex</span><span class="c0">;</span></p><p class="c9"><span class="c1"> rinp </span><span class="c0">=</span><span class="c1"> fds</span><span class="c0">-></span><span class="c1">res_in</span><span class="c0">;</span><span class="c1"> routp </span><span class="c0">=</span><span class="c1"> fds</span><span class="c0">-></span><span class="c1">res_out</span><span class="c0">;</span><span class="c1"> rexp </span><span class="c0">=</span><span class="c1"> fds</span><span class="c0">-></span><span class="c1">res_ex</span><span class="c0">;</span></p><p class="c9 c8"><span class="c1 c5"></span></p><p class="c9"><span class="c1"> </span><span class="c2">for</span><span class="c1"> </span><span class="c0">(</span><span class="c1">i </span><span class="c0">=</span><span class="c1"> </span><span class="c16">0</span><span class="c0">;</span><span class="c1"> i </span><span class="c0"><</span><span class="c1"> n</span><span class="c0">;</span><span class="c1"> </span><span class="c0">++</span><span class="c1">rinp</span><span class="c0">,</span><span class="c1"> </span><span class="c0">++</span><span class="c1">routp</span><span class="c0">,</span><span class="c1"> </span><span class="c0">++</span><span class="c1">rexp</span><span class="c0">)</span><span class="c1"> </span><span class="c0">{</span></p><p class="c9"><span class="c0">...</span></p><p class="c9"><span class="c1"> </span><span class="c2">in</span><span class="c1"> </span><span class="c0">=</span><span class="c1"> </span><span class="c0">*</span><span class="c1">inp</span><span class="c0">++;</span><span class="c1"> </span><span class="c2">out</span><span class="c1"> </span><span class="c0">=</span><span class="c1"> </span><span class="c0">*</span><span class="c1">outp</span><span class="c0">++;</span><span class="c1"> ex </span><span class="c0">=</span><span class="c1"> </span><span class="c0">*</span><span class="c1">exp</span><span class="c0">++;</span></p><p class="c9"><span class="c1"> all_bits </span><span class="c0">=</span><span class="c1"> </span><span class="c2">in</span><span class="c1"> </span><span class="c0">|</span><span class="c1"> </span><span class="c2">out</span><span class="c1"> </span><span class="c0">|</span><span class="c1"> ex</span><span class="c0">;</span></p><p class="c9"><span class="c1"> </span><span class="c2">if</span><span class="c1"> </span><span class="c0">(</span><span class="c1">all_bits </span><span class="c0">==</span><span class="c1"> </span><span class="c16">0</span><span class="c0">)</span><span class="c1"> </span><span class="c0">{</span></p><p class="c9"><span class="c1"> i </span><span class="c0">+=</span><span class="c1"> BITS_PER_LONG</span><span class="c0">;</span></p><p class="c9"><span class="c1"> </span><span class="c2">continue</span><span class="c0">;</span></p><p class="c9"><span class="c1"> </span><span class="c0">}</span></p><p class="c9 c8"><span class="c1 c5"></span></p><p class="c9"><span class="c1"> </span><span class="c2">for</span><span class="c1"> </span><span class="c0">(</span><span class="c1">j </span><span class="c0">=</span><span class="c1"> </span><span class="c16">0</span><span class="c0">;</span><span class="c1"> j </span><span class="c0"><</span><span class="c1"> BITS_PER_LONG</span><span class="c0">;</span><span class="c1"> </span><span class="c0">++</span><span class="c1">j</span><span class="c0">,</span><span class="c1"> </span><span class="c0">++</span><span class="c1">i</span><span class="c0">,</span><span class="c1"> bit </span><span class="c0"><<=</span><span class="c1"> </span><span class="c16">1</span><span class="c0">)</span><span class="c1"> </span><span class="c0">{</span></p><p class="c9"><span class="c1"> </span><span class="c2">struct</span><span class="c1"> fd f</span><span class="c0">;</span></p><p class="c9"><span class="c1"> </span><span class="c2">if</span><span class="c1"> </span><span class="c0">(</span><span class="c1">i </span><span class="c0">>=</span><span class="c1"> n</span><span class="c0">)</span></p><p class="c9"><span class="c1"> </span><span class="c2">break</span><span class="c0">;</span></p><p class="c9"><span class="c1"> </span><span class="c2">if</span><span class="c1"> </span><span class="c0">(!(</span><span class="c1">bit </span><span class="c0">&</span><span class="c1"> all_bits</span><span class="c0">))</span></p><p class="c9"><span class="c1"> </span><span class="c2">continue</span><span class="c0">;</span></p><p class="c9"><span class="c1"> f </span><span class="c0">=</span><span class="c1"> fdget</span><span class="c0">(</span><span class="c1">i</span><span class="c0">);</span></p><p class="c9"><span class="c1"> </span><span class="c2">if</span><span class="c1"> </span><span class="c0">(</span><span class="c1">f</span><span class="c0">.</span><span class="c1">file</span><span class="c0">)</span><span class="c1"> </span><span class="c0">{</span></p><p class="c9"><span class="c0">...</span></p><p class="c9"><span class="c1"> </span><span class="c2">if</span><span class="c1"> </span><span class="c0">(</span><span class="c1">f_op</span><span class="c0">-></span><span class="c1">poll</span><span class="c0">)</span><span class="c1"> </span><span class="c0">{</span></p><p class="c9"><span class="c0">...</span></p><p class="c9"><span class="c1"> mask </span><span class="c0">=</span><span class="c1"> </span><span class="c0">(*</span><span class="c1">f_op</span><span class="c0">-></span><span class="c1">poll</span><span class="c0">)(</span><span class="c1">f</span><span class="c0">.</span><span class="c1">file</span><span class="c0">,</span><span class="c1"> wait</span><span class="c0">);</span></p><p class="c9"><span class="c1"> </span><span class="c0">}</span></p><p class="c9"><span class="c1"> fdput</span><span class="c0">(</span><span class="c1">f</span><span class="c0">);</span></p><p class="c9"><span class="c1"> </span><span class="c2">if</span><span class="c1"> </span><span class="c0">((</span><span class="c1">mask </span><span class="c0">&</span><span class="c1"> POLLIN_SET</span><span class="c0">)</span><span class="c1"> </span><span class="c0">&&</span><span class="c1"> </span><span class="c0">(</span><span class="c2">in</span><span class="c1"> </span><span class="c0">&</span><span class="c1"> bit</span><span class="c0">))</span><span class="c1"> </span><span class="c0">{</span></p><p class="c9"><span class="c1"> res_in </span><span class="c0">|=</span><span class="c1"> bit</span><span class="c0">;</span></p><p class="c9"><span class="c1"> retval</span><span class="c0">++;</span></p><p class="c9"><span class="c0">...</span></p><p class="c9"><span class="c1"> </span><span class="c0">}</span></p><p class="c9"><span class="c0">...</span></p><p class="c9"><span class="c1"> </span><span class="c0">}</span></p><p class="c9"><span class="c1"> </span><span class="c0">}</span></p><p class="c9"><span class="c1"> </span><span class="c2">if</span><span class="c1"> </span><span class="c0">(</span><span class="c1">res_in</span><span class="c0">)</span></p><p class="c9"><span class="c1"> </span><span class="c0">*</span><span class="c1">rinp </span><span class="c0">=</span><span class="c1"> res_in</span><span class="c0">;</span></p><p class="c9"><span class="c1"> </span><span class="c2">if</span><span class="c1"> </span><span class="c0">(</span><span class="c1">res_out</span><span class="c0">)</span></p><p class="c9"><span class="c1"> </span><span class="c0">*</span><span class="c1">routp </span><span class="c0">=</span><span class="c1"> res_out</span><span class="c0">;</span></p><p class="c9"><span class="c1"> </span><span class="c2">if</span><span class="c1"> </span><span class="c0">(</span><span class="c1">res_ex</span><span class="c0">)</span></p><p class="c9"><span class="c1"> </span><span class="c0">*</span><span class="c1">rexp </span><span class="c0">=</span><span class="c1"> res_ex</span><span class="c0">;</span></p><p class="c9"><span class="c0">...</span></p><p class="c9"><span class="c1"> </span><span class="c0">}</span></p><p class="c9"><span class="c0">...</span></p><p class="c9"><span class="c1"> </span><span class="c2">if</span><span class="c1"> </span><span class="c0">(</span><span class="c1">retval </span><span class="c0">||</span><span class="c1"> timed_out </span><span class="c0">||</span><span class="c1"> signal_pending</span><span class="c0">(</span><span class="c1">current</span><span class="c0">))</span></p><p class="c9"><span class="c1"> </span><span class="c2">break</span><span class="c0">;</span></p><p class="c9"><span class="c0">...</span></p><p class="c9"><span class="c1"> </span><span class="c2">if</span><span class="c1"> </span><span class="c0">(!</span><span class="c1">poll_schedule_timeout</span><span class="c0">(&</span><span class="c1">table</span><span class="c0">,</span><span class="c1"> TASK_INTERRUPTIBLE</span><span class="c0">,</span></p><p class="c9"><span class="c1"> to</span><span class="c0">,</span><span class="c1"> slack</span><span class="c0">))</span></p><p class="c9"><span class="c1"> timed_out </span><span class="c0">=</span><span class="c1"> </span><span class="c16">1</span><span class="c0">;</span></p><p class="c9"><span class="c1"> </span><span class="c0">}</span></p><p class="c9"><span class="c0">...</span></p><p class="c9"><span class="c1"> </span><span class="c2">return</span><span class="c1"> retval</span><span class="c0">;</span></p><p class="c9"><span class="c0">}</span></p></td></tr></tbody></table><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span class="c3">pselect()</span><span> is able to check file descriptors for 3 types of readiness: reading (</span><span class="c3">in</span><span> fds), writing (</span><span class="c3">out</span><span> fds), and exceptional conditions (</span><span class="c3">ex</span><span> fds). If none of the file descriptors in the 3 fdsets are ready for the corresponding operations, then </span><span class="c3">do_select()</span><span> will block in </span><span class="c3">poll_schedule_timeout()</span><span class="c4 c7"> until either the timeout expires or the status of one of the selected fds changes.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>Imagine what happens if, while </span><span class="c3">do_select()</span><span> is blocked in </span><span class="c3">poll_schedule_timeout()</span><span>, we use our out-of-bounds addition primitive to change the value of </span><span class="c3">n</span><span>. We already know from our analysis of </span><span class="c3">core_sys_select()</span><span> that if </span><span class="c3">n</span><span> was sufficiently small to begin with, then the </span><span class="c3">stack_fds</span><span> array will be used instead of allocating a buffer on the heap. Thus, </span><span class="c3">in</span><span>, </span><span class="c3">out</span><span>, and </span><span class="c3">ex</span><span> may reside on the stack. Once </span><span class="c3">poll_schedule_timeout()</span><span> unblocks, </span><span class="c3">do_select()</span><span> will iterate over all file descriptor numbers from 0 to (the corrupted value of) </span><span class="c3">n</span><span>. Towards the end of this loop, </span><span class="c3">inp</span><span>, </span><span class="c3">outp</span><span>, and </span><span class="c3">exp</span><span> will be read out-of-bounds, while </span><span class="c3">rinp</span><span>, </span><span class="c3">routp</span><span>, and </span><span class="c3">rexp</span><span> will be written out of bounds. Thus, this is actually a stack buffer overflow. And since the values written to </span><span class="c3">rinp</span><span>, </span><span class="c3">routp</span><span>, and </span><span class="c3">rexp</span><span class="c4 c7"> are determined based on the readiness of file descriptors, we have at least some hope of controlling the data that gets written.</span></p><h1 class="c21" id="h.cskq4ouvdhjk"><span class="c5 c17 c7">Sizing the overflow</span></h1><p class="c6"><span>So, in theory, we could target </span><span class="c3">pselect()</span><span class="c4 c7"> to create a stack buffer overflow using our out-of-bounds addition. There are still a lot of steps between that general idea and a working exploit.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>Let's begin just by trying to understand the situation we're in a bit more precisely. Looking at the function prologues in IDA, we can determine the stack layout after the </span><span class="c3">stack_fds</span><span class="c4 c7"> buffer:</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c33"></p> <p class="c27"><span class="c30 c7 c24"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiiIgmC-Qv2zwSW7EvS6nTic9UgsoLGxMFoJOtBcchDwaIJRS5BW6enz6gVpDrsAvqk8ASOuahJsZAyO4eKFlosqDoLvjczQaOvLS-5HyRHmNC9PIs2XqLOG3taijzBi1xGRXmx8Lc6VjR3hb0De4mTF0pZOJYvW-koAPdRUzZQ5loFGonGFgxWVrUA/s1600/unnamed%20%283%29.png" style="display: block; padding: 1em 0; text-align: center;"><img alt="A diagram showing the call stack of core_sys_select(). The topmost stack frame is core_sys_select(), followed by SyS_pselect6(), followed by el0_sync_64." border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiiIgmC-Qv2zwSW7EvS6nTic9UgsoLGxMFoJOtBcchDwaIJRS5BW6enz6gVpDrsAvqk8ASOuahJsZAyO4eKFlosqDoLvjczQaOvLS-5HyRHmNC9PIs2XqLOG3taijzBi1xGRXmx8Lc6VjR3hb0De4mTF0pZOJYvW-koAPdRUzZQ5loFGonGFgxWVrUA/s1600/unnamed%20%283%29.png" style="max-height: 500pt; max-width: 400pt;" /></a></span></p><p class="c27"><span class="c30 c7 c24">The stack layout of core_sys_select() and all earlier frames in the call stack. The 256-byte stack_fds buffer out of which we will overflow is 0x348 bytes from the end of the stack and followed by a stack guard and return address.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span class="c4 c7">There are two major constraints we're going to run into with this buffer overflow: controlling the length of the overflow and controlling the contents of the overflow. We'll start with the length.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>Based on the depth of the </span><span class="c3">stack_fds</span><span> buffer in the kernel stack, we can only write </span><span class="c3">0x348</span><span> bytes into the buffer before running off the end of the stack and triggering a kernel panic. If we assume a maximal value of </span><span class="c3">n</span><span> </span><span class="c3">=</span><span> </span><span class="c3">0x140</span><span> (which will be important when controlling the contents of the overflow), then that means we need to stop processing at </span><span class="c3">inp</span><span> </span><span class="c3">=</span><span> </span><span class="c3">0x348</span><span> </span><span class="c3">-</span><span> </span><span class="c3">5</span><span> </span><span class="c3">*</span><span> </span><span class="c3">0x28</span><span> </span><span class="c3">=</span><span> </span><span class="c3">0x280</span><span> bytes into the buffer, which will position </span><span class="c3">rexp</span><span> just off the end of the stack. This corresponds to a maximum corrupted value of </span><span class="c3">n</span><span> of </span><span class="c3">8</span><span> </span><span class="c3">*</span><span> </span><span class="c3">0x280</span><span> </span><span class="c3">=</span><span> </span><span class="c3">0x1400</span><span class="c4 c7">.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>So, how do we choose an ION buffer daddr such that adding the daddr into </span><span class="c3">n</span><span> </span><span class="c3">=</span><span> </span><span class="c3">0x140</span><span> at some offset will yield a value significantly greater than </span><span class="c3">0x140</span><span> but less than </span><span class="c3">0x1400</span><span class="c4 c7">?</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>Unfortunately, the math on this doesn't work out. Even if we choose from the expanded range of ION device addresses </span><span class="c3">0x[89]xxxx000</span><span>, there is no address that can be added to </span><span class="c3">0x140</span><span> at a particular offset to produce a 32-bit value between </span><span class="c3">0x400</span><span> and </span><span class="c3">0x1400</span><span class="c4 c7">.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>Nevertheless, we do still have another option available to us: 2 ION buffers! If we can choose the device address of a single ION buffer, theoretically we should be able to repeat the feat to choose the device addresses of 2 ION buffers together; can we find a pair of daddrs that can be added to </span><span class="c3">0x140</span><span> at particular offsets to produce a 32-bit value between </span><span class="c3">0x400</span><span> and </span><span class="c3">0x1400</span><span class="c4 c7">?</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>It turns out that adding in a second independent daddr now gives us enough degrees of freedom to find a solution. For example, consider the daddrs </span><span class="c3">0x8qrst000</span><span> and </span><span class="c3">0x8wxyz000</span><span> being added into </span><span class="c3">0x140</span><span> at offsets 0 and +1, respectively. Looking at how the bytes align, it's clear that the only sum less than </span><span class="c3">0x1400</span><span> will be </span><span class="c3">0x1140</span><span class="c4 c7">. Thus, we can derive a series of relations between the digits:</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span class="c4 c3">carry 11 1</span></p><p class="c6"><span class="c4 c3">+ 00 z0 xy 8w</span></p><p class="c6"><span class="c4 c3">+ 00 t0 rs 8q</span></p><p class="c6"><span class="c4 c3"> 40 01 00 00 00 00 00 00</span></p><p class="c6"><span class="c4 c3">---------------------------</span></p><p class="c6"><span class="c4 c3"> 40 11 00 00 ??</span></p><p class="c6 c8"><span class="c4 c3"></span></p><p class="c6"><span class="c4 c3">t = 1</span></p><p class="c6"><span class="c4 c3">s = 0</span></p><p class="c6"><span class="c3 c4">z+r = 0 OR z+r = 10</span></p><p class="c6"><span class="c4 c3">ASSUME z+r = 10</span></p><p class="c6"><span class="c4 c3">1+y+q = 10 => y+q = f</span></p><p class="c6"><span class="c4 c3">1+x+8 = 10 => x = 7</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>In this case, we discover that daddrs </span><span class="c3">0x8qr01000</span><span> and </span><span class="c3">0x8w7yz000</span><span> will be a solution so long as </span><span class="c3">q</span><span> </span><span class="c3">+</span><span> </span><span class="c3">y</span><span> </span><span class="c3">=</span><span> </span><span class="c3">0xf</span><span> and </span><span class="c3">r</span><span> </span><span class="c3">+</span><span> </span><span class="c3">z</span><span> </span><span class="c3">=</span><span> </span><span class="c3">0x10</span><span>. For example, </span><span class="c3">0x81801000</span><span> and </span><span class="c3">0x827e8000</span><span class="c4 c7"> are a solution:</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span class="c4 c3">carry 11 1</span></p><p class="c6"><span class="c4 c3">+ 00 80 7e 82</span></p><p class="c6"><span class="c4 c3">+ 00 10 80 81</span></p><p class="c6"><span class="c4 c3"> 40 01 00 00 00 00 00 00</span></p><p class="c6"><span class="c4 c3">---------------------------</span></p><p class="c6"><span class="c4 c3"> 40 11 00 00 83</span></p><p class="c6 c8"><span class="c4 c3"></span></p><p class="c6"><span class="c4 c3">corrupted 32-bit n is 0x1140</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span class="c4 c7">So, we'll need to tweak our original heap groom slightly to map both ION buffers back-to-back before spraying the kernel thread stacks.</span></p><h1 class="c21" id="h.t5zxv0k896g5"><span class="c5 c17 c7">Controlling the contents</span></h1><p class="c6"><span>Using 2 ION buffers with carefully chosen device addresses allows us to control the size of the stack buffer overflow. </span><span>Meanwhile, the contents of the overflow are the output of the </span><span class="c3">pselect()</span><span> syscall, which we can control because we can control which file descriptors in our process are ready for various types of operations. But once we overflow past the original value of </span><span class="c3">n</span><span class="c4 c7">, we run into a problem: we start reading our input data from the data previously written to the output buffer.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c33"></p> <p class="c27"><span class="c30 c7 c24"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjYhlsjJykY2C6s3trE-k6fDcNR5CarwUo-cKh-atLHE6gZbSOk-X4SnqoxfejpJpsQb0TEvf5GeVWSnYHPDEjRMZhIfC8ZnNuM6bhCUh_hjaSFqiNZGHp3U9CHkKKRswVqb2AugUyw0zO2CGw66u7Hv3jQbXU5JN45e8wcRVByD8Rspa_Brk9EIuZC/s1056/unnamed.gif" style="display: block; padding: 1em 0; text-align: center;"><img alt="A diagram showing the progression of the stack buffer overflow out of the stack_fds buffer and down the stack." border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjYhlsjJykY2C6s3trE-k6fDcNR5CarwUo-cKh-atLHE6gZbSOk-X4SnqoxfejpJpsQb0TEvf5GeVWSnYHPDEjRMZhIfC8ZnNuM6bhCUh_hjaSFqiNZGHp3U9CHkKKRswVqb2AugUyw0zO2CGw66u7Hv3jQbXU5JN45e8wcRVByD8Rspa_Brk9EIuZC/s1056/unnamed.gif" style="max-height: 500pt; max-width: 400pt;" /></a></span></p><p class="c27"><span class="c30 c7 c24">Corrupting the value of n will cause do_select() to keep processing inp, outp, and exp past their original bounds. Eventually the output cursor rinp will start overflowing out of the stack_fds buffer entirely, overwriting the return address. But before that happens, inp will start consuming the previous output of rinp.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>To make the analysis simpler, we can ignore </span><span class="c3">out</span><span>, </span><span class="c3">ex</span><span>, </span><span class="c3">rout</span><span>, and </span><span class="c3">rex</span><span> to focus exclusively on </span><span class="c3">in</span><span> and </span><span class="c3">rin</span><span>. The reason for this is that </span><span class="c3">rout</span><span> and </span><span class="c3">rex</span><span> will eventually be overwritten by </span><span class="c3">rin</span><span>, so it doesn't really matter what they write; if the overflow continues for a sufficient distance, only the output of </span><span class="c3">rin</span><span class="c4 c7"> matters.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>Using the assumed value of </span><span class="c3">n</span><span> </span><span class="c3">=</span><span> </span><span class="c3">0x140</span><span>, each of the </span><span class="c3">in</span><span>, </span><span class="c3">out</span><span>, </span><span class="c3">ex</span><span>, </span><span class="c3">rin</span><span>, </span><span class="c3">rout</span><span>, </span><span class="c3">rex</span><span> buffers is </span><span class="c3">0x28</span><span> bytes, so after processing </span><span class="c3">3</span><span> </span><span class="c3">*</span><span> </span><span class="c3">n</span><span> </span><span class="c3">=</span><span> </span><span class="c3">0x3c0</span><span> file descriptors, </span><span class="c3">inp</span><span> (the input pointer) will start reading from </span><span class="c3">rin</span><span> (the output buffer), and this will continue as the out-of-bounds write progresses down the stack. By the semantics of </span><span class="c3">select()</span><span>, each bit in </span><span class="c3">rinp</span><span> is only written with a 1 if both the corresponding bit in </span><span class="c3">inp</span><span> was a 1 and the corresponding file descriptor was readable. Thus, once we've written a 0 bit at any location, every bit at a multiple of </span><span class="c3">0x3c0</span><span class="c4 c7"> bits later will also be 0. This introduces a natural cycle in our overflow that constrains the output.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>It's also worth noting that if you look back at </span><span class="c3">do_select()</span><span>, </span><span class="c3">rinp</span><span class="c4 c7"> is only written if the full 64-bit value to write is nonzero. Thus, if any 64-bit output value is 0 (i.e., all 64 fds in the long are either not selected on or not readable), then the corresponding stack location will be left intact.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c33"></p> <p class="c6"><span class="c4 c7"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhgpEr1Wq6qWLDGR8A3VOyblVEjZUKF9wO6KHV59ZhGE0eSqJys53NM_jA0Hou5wQ5lzQ-X8Bu00TFeVt1BHiQEAw6q4oISNmxd-k0trc7JBob6M7tYU0LdgSfTFOqjOGb9WKRQfHEO09UB-qiomTByEkgZuXsM561NGNRgHvwRAD4VNUJjVfPWW-fJ/s1600/unnamed%20%282%29.png" style="display: block; padding: 1em 0; text-align: center;"><img alt="A diagram showing the cyclical dependency of bit values that can be written using this buffer overflow. In order to set bit X to 1, we need to ensure that bit X - 0x3c0 is also 1 and that file descriptor X - 0x3c0 is readable. If X - 0x3c0 is 0, then do_select() will not select on file descriptor X - 0x3c0, and the output at X will be 0; however, if all 64 output bits in X's long are 0, then no output is written and the value of X will be unchanged." border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhgpEr1Wq6qWLDGR8A3VOyblVEjZUKF9wO6KHV59ZhGE0eSqJys53NM_jA0Hou5wQ5lzQ-X8Bu00TFeVt1BHiQEAw6q4oISNmxd-k0trc7JBob6M7tYU0LdgSfTFOqjOGb9WKRQfHEO09UB-qiomTByEkgZuXsM561NGNRgHvwRAD4VNUJjVfPWW-fJ/s1200/unnamed%20%282%29.png" style="max-height: 500pt; max-width: 400pt;" /></a></span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>Because of the </span><span class="c3">3</span><span> </span><span class="c3">*</span><span> </span><span class="c3">0x28</span><span> </span><span class="c3">=</span><span> </span><span class="c3">0x78</span><span>-byte cycle length, we can treat this primitive as the ability to write up to 15 controlled 64-bit values into the stack, each at a unique offset modulo </span><span class="c3">0x78</span><span>, while leaving stack values at the remaining offsets modulo </span><span class="c3">0x78</span><span class="c4 c7"> intact. This is a conservative simplification, since our primitive is somewhat more flexible than this, but it's useful to visualize our primitive this way.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>Under this simplification, we can describe the out-of-bounds write as 15 (</span><span class="c3 c24">offset</span><span>, </span><span class="c3 c24">value</span><span>) pairs, where </span><span class="c3 c24">offset</span><span> is the offset from the start of the </span><span class="c3">stack_fds</span><span> array in bytes (which must be a multiple of 8 and must be unique mod </span><span class="c3">0x78</span><span>) and </span><span class="c3 c24">value</span><span> is the 64-bit value to be written at that offset. For each (</span><span class="c3 c24">offset</span><span>, </span><span class="c3 c24">value</span><span>) pair, we need to set to 1 every bit in each "preimage" of </span><span class="c3 c24">value</span><span class="c4 c7"> from an earlier cycle.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>To simplify things even further, let's regard the concatenation of the input fdsets </span><span class="c3">in</span><span>, </span><span class="c3">out</span><span>, and </span><span class="c3">ex</span><span> as a single input fdset, just as it appears on the kernel stack in the </span><span class="c3">stack_fds</span><span> buffer once we've corrupted </span><span class="c3">n</span><span class="c4 c7">.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>The simplest solution is to have the "preimage" of each </span><span class="c3 c24">value</span><span> be </span><span class="c3 c24">value</span><span> itself, which can be done by setting the portion of the input fdset at </span><span class="c3 c24">offset</span><span> </span><span class="c3">%</span><span> </span><span class="c3">0x78</span><span> to </span><span class="c3 c24">value</span><span> for each (</span><span class="c3 c24">offset</span><span>, </span><span class="c3 c24">value</span><span>) pair and by making every file descriptor from 0 to </span><span class="c3">0x1140</span><span> be readable. Since we'll only be selecting on the fds that correspond to 1 bits in </span><span class="c3 c24">value</span><span> and since every fd is readable, this will mirror the entire input buffer (consisting of the 15 values at their corresponding offsets) down the kernel stack repeatedly. This will trivially ensure that </span><span class="c3 c24">value</span><span> gets placed at </span><span class="c3 c24">offset</span><span> because </span><span class="c3 c24">value</span><span> will be written to </span><span class="c24">every</span><span> offset in the kernel stack that is the same as </span><span class="c3 c24">offset</span><span> mod </span><span class="c3">0x78</span><span class="c4 c7">.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c33"></p> <p class="c27"><span class="c30 c7 c24"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjX4tVe31D9ACayK6HTdRXQoC6n5-kOcdHd0D8y2omYYIIjIDoLDLWbSjfglEfQwcRNxJOd6zsiYyQUilpD91A9A4CCQELEBU0yQnbl_AeoNecrLZSrQbSvJrMuQM_ibLxUHXXH-M1UEvUsAKy5dt6rRNTC7hYBgFmFKablEKPdy04tGg7-tvi6678_/s1600/unnamed%20%281%29.png" style="display: block; padding: 1em 0; text-align: center;"><img alt="A diagram showing the input buffer being repeatedly mirrored down the stack." border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjX4tVe31D9ACayK6HTdRXQoC6n5-kOcdHd0D8y2omYYIIjIDoLDLWbSjfglEfQwcRNxJOd6zsiYyQUilpD91A9A4CCQELEBU0yQnbl_AeoNecrLZSrQbSvJrMuQM_ibLxUHXXH-M1UEvUsAKy5dt6rRNTC7hYBgFmFKablEKPdy04tGg7-tvi6678_/s1200/unnamed%20%281%29.png" style="max-height: 500pt; max-width: 400pt;" /></a></span></p><p class="c27"><span class="c7 c24 c30">By making all of the first 0x1140 file descriptors readable, the input buffer's 15 longs will be mirrored down the stack, repeating every 0x3c0 bits.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>Using a slightly more complicated solution, however, we can limit the depth of the stack corruption. Instead, we'll have the preimage of each value be </span><span class="c3">~0</span><span class="c4 c7"> (i.e., all 64 bits set), and use the readability of each file descriptor to control the bits that get written. That way, once we've written the value at the correct offset, the fds corresponding to subsequent cycles can be made non-readable, thereby preventing the corruption from continuing down the stack past the desired depth of each write.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>This still leaves one more question about the mechanics of the stack buffer overflow: </span><span class="c3">poll_schedule_timeout()</span><span> will return as soon as any file descriptor becomes readable, so how can we make sure that all the fds for all the 1 bits become readable at the same time? Fortunately this has an easy solution: create 2 pipes, one for all 0 bits and one for all 1 bits, and use </span><span class="c3">dup2()</span><span> to make all the fds we'll be selecting on be duplicates of the read ends of these pipes. Writing data to the "1 bits" pipe will cause </span><span class="c3">poll_schedule_timeout()</span><span> to unblock and all the "1 bits" fds will be readable (since they're all </span><span class="c3">dup</span><span>s of the same underlying file). The only thing we need to be careful about is to ensure that at least one of the original </span><span class="c3">in</span><span> file descriptors (fds 0-</span><span class="c3">0x13f</span><span>) is a "1 bit" </span><span class="c3">dup</span><span>, or else </span><span class="c3">poll_schedule_timeout()</span><span class="c4 c7"> won't notice the status change when we write to the "1 bits" pipe.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span class="c4 c7">Combining all of these ideas together, we can finally control the contents of the buffer overflow, clobbering the return address (while leaving the stack guard intact!) to execute a small ROP payload.</span></p><h1 class="c21" id="h.pxzfm8g614we"><span>The ultimate </span><span class="c5 c17 c7">ROP</span></h1><p class="c6"><span class="c4 c7">Finally, it's time to consider our ROP payload. Because we can write at most 15 distinct 64-bit values into the stack via our overflow (2 of which we've already used), we'll need to be careful about keeping the ROP payload small.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>When I mentioned this progress to Jann, he suggested that I check the function </span><span class="c3">___bpf_prog_run()</span><span class="c4 c7">, which he described as the ultimate ROP gadget. And indeed, if your kernel has it compiled in, it does appear to be the ultimate ROP gadget!</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span class="c3">___bpf_prog_run()</span><span class="c4 c7"> is responsible for interpreting eBPF bytecode that has already been deemed safe by the eBPF verifier. As such, it provides a number very powerful primitives, including:</span></p><p class="c6 c8"><span class="c4 c7"></span></p><ol class="c32 lst-kix_2atftb74ca0r-0 start" start="1"><li class="c6 c22"><span class="c4 c7">arbitrary control flow in the eBPF program;</span></li><li class="c6 c22"><span class="c4 c7">arbitrary memory load;</span></li><li class="c6 c22"><span class="c4 c7">arbitrary memory store;</span></li><li class="c6 c22"><span class="c4 c7">arbitrary kernel function calls with up to 5 arguments and a 64-bit return value.</span></li></ol><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span class="c4 c7">So how can we start executing this function with a controlled instruction array as quickly as possible?</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>First, it's slightly easier to enter </span><span class="c3">___bpf_prog_run()</span><span> through one of its wrappers, such as </span><span class="c3">__bpf_prog_run32()</span><span class="c4 c7">. The wrapper takes just 2 arguments rather than 3, and of those, we only need to control the second:</span></p><p class="c6 c8"><span class="c4 c7"></span></p><a id="t.d39a4d63357c41580035c48a158cebf3c3399de5"></a><a id="t.13"></a><table class="c10"><tbody><tr class="c15"><td class="c19" colspan="1" rowspan="1"><p class="c9"><span class="c2">unsigned</span><span class="c1"> </span><span class="c2">int</span><span class="c1"> __bpf_prog_run32</span><span class="c0">(</span><span class="c2">const</span><span class="c1"> </span><span class="c2">void</span><span class="c1"> </span><span class="c0">*</span><span class="c1">ctx</span><span class="c0">,</span><span class="c1"> </span><span class="c2">const</span><span class="c1"> bpf_insn </span><span class="c0">*</span><span class="c1">insn</span><span class="c0">)</span></p></td></tr></tbody></table><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>The </span><span class="c3">insn</span><span> argument is passed in register </span><span class="c3">X1</span><span>, which means we'll need to find a ROP gadget that will pop a value off the stack and move it into </span><span class="c3">X1</span><span>. Fortunately, the following gadget from the end of the </span><span class="c3">current_time()</span><span> function gives us control of </span><span class="c3">X1</span><span> indirectly via </span><span class="c3">X20</span><span class="c4 c7">:</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span class="c12">FFFFFF8008294C20 </span><span class="c12 c29">LDP X29, X30, [SP,#</span><span class="c12 c31">0x10</span><span class="c12 c29">+</span><span class="c12 c31">var_s0</span><span class="c5 c12 c34 c29">]</span></p><p class="c6"><span class="c12">FFFFFF8008294C24 </span><span class="c5 c12 c34 c29">MOV X0, X19</span></p><p class="c6"><span class="c12">FFFFFF8008294C28 </span><span class="c5 c12 c29 c34">MOV X1, X20</span></p><p class="c6"><span class="c12">FFFFFF8008294C2C </span><span class="c12 c29">LDP X20, X19, [SP+</span><span class="c12 c31">0x10</span><span class="c12 c29">+</span><span class="c12 c31">var_10</span><span class="c12 c29">],#</span><span class="c5 c12 c13">0x20</span></p><p class="c6"><span class="c12">FFFFFF8008294C30 </span><span class="c5 c12 c34 c29">RET</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>The reason this works is that </span><span class="c3">X20</span><span> was actually popped off the stack during the epilogue of </span><span class="c3">core_sys_select()</span><span>, which means that we already have control over </span><span class="c3">X20</span><span> by the time we execute the first hijacked return. So executing this gadget will move the value we popped into </span><span class="c3">X20</span><span> over to register </span><span class="c3">X1</span><span>. And since this gadget reads its return address from the stack, we'll also have control over where we return to next, meaning that we can jump right into </span><span class="c3">__bpf_prog_run32()</span><span> with our controlled </span><span class="c3">X1</span><span class="c4 c7">.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>The only remaining question is what value to place in </span><span class="c3">X1</span><span>. Fortunately, this also has a very simple answer: the ION buffers are mapped shared between userspace and the kernel, and we already disclosed their location when we leaked the uninitialized </span><span class="c3">stack_fds</span><span> buffer contents before. Thus, we can just write our eBPF program into the ION buffer and pass that address to </span><span class="c3">__bpf_prog_run32()</span><span class="c4 c7">!</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span class="c4 c7">For simplicity, I had the eBPF implement a busy-poll of the shared memory to execute commands on behalf of the userspace process. The eBPF program would repeatedly read a value from the ION buffer and perform either a 64-bit read, 64-bit write, or 5-argument function call based on the value that was read. This gives us an incredibly simple and reliable kernel read/write/execute primitive.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span class="c4 c7">Even though the primitive was hacky and the system was not yet stable, I decided to stop developing the exploit at this point because my read/write/execute primitive was sufficiently powerful that subsequent exploitation steps would be fully independent of the original bug. I felt that I had achieved the goal of exploring the differences between kernel exploitation on Android and iOS.</span></p><h1 class="c21" id="h.g01683pvu49z"><span class="c5 c7 c17">Conclusion</span></h1><p class="c6"><span class="c4 c7">So, what are my takeaways from developing this Android exploit?</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>Overall, the quality of the hardware mitigations on the Samsung Galaxy S10 was much stronger than I had expected. My uninformed expectation about the state of hardware mitigation adoption on Android devices was that it lagged significantly behind iOS. And in broad strokes that's true: the iPhone XS's A12 SOC supports ARMv8.3-A compared to the Qualcomm Snapdragon 855's ARMv8.2-A, and the A12 includes a few Apple-custom mitigations (KTRR, APRR+PPL) not present in the Snapdragon SOC. However, the mitigation gap was substantially smaller than I had been expecting, and there were even a few ways that the Galaxy S10 supported stronger mitigations than the iPhone XS, for instance by using unprivileged load/store operations during </span><span class="c3">copy_from/to_user()</span><span class="c4 c7">. And as interesting as Apple's custom mitigations are on iOS, they would not have blocked this exploit from obtaining kernel read/write.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span class="c4 c7">In terms of the software, I had expected Android to provide significantly weaker and more limited kernel manipulation primitives (heap shaping, target objects to corrupt, etc) than what's provided by the Mach microkernel portion of XNU, and this also seems largely true. I suspect that part of the reason that public iOS exploits all seem to follow very similar exploit flows is that the pre- and post-exploitation primitives available to manipulate the kernel are exceptionally powerful and flexible. Having powerful manipulation primitives allows you to obtain powerful exploitation primitives more quickly, with less of the exploit flow specific to the exact constraints of the bug. In the case of this Android exploit, it's hard for me to speak generally given my lack of familiarity with the platform. I did manage to find good heap manipulation primitives, so the heap shaping part of the exploit was straightforward and generic. On the other hand, I struggled to find stack frames amenable to manipulation, which forced me to dig through lots of technical constraints to find strategies that would just barely work. As a result, the exploit flow to get kernel read/write/execute is highly specific to the underlying vulnerability until the last step. I'm thus left feeling that there are almost certainly much more elegant ways to exploit the NPU bugs than the strategy I chose.</span></p><p class="c6 c8"><span class="c4 c7"></span></p><p class="c6"><span>Despite all these differences between the two platforms, I was overall quite surprised with the similarities and parallels that did emerge. Even though the final exploit flow for this NPU bug ended up being quite different, there were many echoes of the oob_timestamp exploit along the way. Thus my past experience developing iOS kernel exploits did in fact help me come up with ideas worth trying on Android, even if most of those ideas didn't pan out.</span></p> <div style='clear: both;'></div> </div> <div class='post-footer'> <div class='post-footer-line post-footer-line-1'> <span class='post-author vcard'> Posted by <span class='fn' itemprop='author' itemscope='itemscope' itemtype='http://schema.org/Person'> <span itemprop='name'>Anonymous</span> </span> </span> <span class='post-timestamp'> at <meta content='https://googleprojectzero.blogspot.com/2020/12/an-ios-hacker-tries-android.html' itemprop='url'/> <a class='timestamp-link' href='https://googleprojectzero.blogspot.com/2020/12/an-ios-hacker-tries-android.html' rel='bookmark' title='permanent link'><abbr class='published' itemprop='datePublished' title='2020-12-21T12:10:00-08:00'>12:10 PM</abbr></a> </span> <span class='post-comment-link'> <a class='comment-link' href='https://googleprojectzero.blogspot.com/2020/12/an-ios-hacker-tries-android.html#comment-form' onclick=''> No comments: </a> </span> <span class='post-icons'> <span class='item-control blog-admin pid-145400864'> <a href='https://www.blogger.com/post-edit.g?blogID=4838136820032157985&postID=4113191367925981084&from=pencil' title='Edit Post'> <img alt='' class='icon-action' height='18' src='https://resources.blogblog.com/img/icon18_edit_allbkg.gif' width='18'/> </a> </span> </span> <div class='post-share-buttons goog-inline-block'> <a class='goog-inline-block share-button sb-email' href='https://www.blogger.com/share-post.g?blogID=4838136820032157985&postID=4113191367925981084&target=email' target='_blank' title='Email This'><span class='share-button-link-text'>Email This</span></a><a class='goog-inline-block share-button sb-blog' href='https://www.blogger.com/share-post.g?blogID=4838136820032157985&postID=4113191367925981084&target=blog' onclick='window.open(this.href, "_blank", "height=270,width=475"); return false;' target='_blank' title='BlogThis!'><span class='share-button-link-text'>BlogThis!</span></a><a class='goog-inline-block share-button sb-twitter' href='https://www.blogger.com/share-post.g?blogID=4838136820032157985&postID=4113191367925981084&target=twitter' target='_blank' title='Share to X'><span class='share-button-link-text'>Share to X</span></a><a class='goog-inline-block share-button sb-facebook' href='https://www.blogger.com/share-post.g?blogID=4838136820032157985&postID=4113191367925981084&target=facebook' onclick='window.open(this.href, "_blank", "height=430,width=640"); return false;' target='_blank' title='Share to Facebook'><span class='share-button-link-text'>Share to Facebook</span></a><a class='goog-inline-block share-button sb-pinterest' href='https://www.blogger.com/share-post.g?blogID=4838136820032157985&postID=4113191367925981084&target=pinterest' target='_blank' title='Share to Pinterest'><span class='share-button-link-text'>Share to Pinterest</span></a> </div> </div> <div class='post-footer-line post-footer-line-2'> <span class='post-labels'> </span> </div> <div class='post-footer-line post-footer-line-3'> <span class='post-location'> </span> </div> </div> </div> </div> </div></div> </div> <div class='blog-pager' id='blog-pager'> <span id='blog-pager-newer-link'> <a class='blog-pager-newer-link' href='https://googleprojectzero.blogspot.com/search?updated-max=2021-01-12T09:36:00-08:00&max-results=1&reverse-paginate=true' id='Blog1_blog-pager-newer-link' title='Newer Posts'>Newer Posts</a> </span> <span id='blog-pager-older-link'> <a class='blog-pager-older-link' href='https://googleprojectzero.blogspot.com/search?updated-max=2020-12-21T12:10:00-08:00&max-results=1' id='Blog1_blog-pager-older-link' title='Older Posts'>Older Posts</a> </span> <a class='home-link' href='https://googleprojectzero.blogspot.com/'>Home</a> </div> <div class='clear'></div> <div class='blog-feeds'> <div class='feed-links'> Subscribe to: <a class='feed-link' href='https://googleprojectzero.blogspot.com/feeds/posts/default' target='_blank' type='application/atom+xml'>Posts (Atom)</a> </div> </div> </div></div> </div> </div> <div class='column-left-outer'> <div class='column-left-inner'> <aside> </aside> </div> </div> <div class='column-right-outer'> <div class='column-right-inner'> <aside> <div class='sidebar section' id='sidebar-right-1'><div class='widget BlogSearch' data-version='1' id='BlogSearch1'> <h2 class='title'>Search This Blog</h2> <div class='widget-content'> <div id='BlogSearch1_form'> <form action='https://googleprojectzero.blogspot.com/search' class='gsc-search-box' target='_top'> <table cellpadding='0' cellspacing='0' class='gsc-search-box'> <tbody> <tr> <td class='gsc-input'> <input autocomplete='off' class='gsc-input' name='q' size='10' title='search' type='text' value=''/> </td> <td class='gsc-search-button'> <input class='gsc-search-button' title='search' type='submit' value='Search'/> </td> </tr> </tbody> </table> </form> </div> </div> <div class='clear'></div> </div><div class='widget PageList' data-version='1' id='PageList1'> <h2>Pages</h2> <div class='widget-content'> <ul> <li> <a href='https://googleprojectzero.blogspot.com/p/about-project-zero.html'>About Project Zero</a> </li> <li> <a href='https://googleprojectzero.blogspot.com/p/working-at-project-zero.html'>Working at Project Zero</a> </li> <li> <a href='https://googleprojectzero.blogspot.com/p/0day.html'>0day "In the Wild"</a> </li> <li> <a href='https://googleprojectzero.github.io/0days-in-the-wild/rca.html'>0day Exploit Root Cause Analyses</a> </li> <li> <a href='https://googleprojectzero.blogspot.com/p/vulnerability-disclosure-faq.html'>Vulnerability Disclosure FAQ</a> </li> </ul> <div class='clear'></div> </div> </div><div class='widget BlogArchive' data-version='1' id='BlogArchive1'> <h2>Archives</h2> <div class='widget-content'> <div id='ArchiveList'> <div id='BlogArchive1_ArchiveList'> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2024/'> 2024 </a> <span class='post-count' dir='ltr'>(9)</span> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2024/11/'> November </a> <span class='post-count' dir='ltr'>(2)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2024/10/'> October </a> <span class='post-count' dir='ltr'>(2)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2024/06/'> June </a> <span class='post-count' dir='ltr'>(3)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2024/04/'> April </a> <span class='post-count' dir='ltr'>(2)</span> </li> </ul> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2023/'> 2023 </a> <span class='post-count' dir='ltr'>(11)</span> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2023/11/'> November </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2023/10/'> October </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2023/09/'> September </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2023/08/'> August </a> <span class='post-count' dir='ltr'>(4)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2023/04/'> April </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2023/03/'> March </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2023/01/'> January </a> <span class='post-count' dir='ltr'>(2)</span> </li> </ul> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2022/'> 2022 </a> <span class='post-count' dir='ltr'>(17)</span> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2022/12/'> December </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2022/11/'> November </a> <span class='post-count' dir='ltr'>(3)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2022/10/'> October </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2022/08/'> August </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2022/06/'> June </a> <span class='post-count' dir='ltr'>(3)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2022/05/'> May </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2022/04/'> April </a> <span class='post-count' dir='ltr'>(3)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2022/03/'> March </a> <span class='post-count' dir='ltr'>(2)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2022/02/'> February </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2022/01/'> January </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2021/'> 2021 </a> <span class='post-count' dir='ltr'>(24)</span> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2021/12/'> December </a> <span class='post-count' dir='ltr'>(2)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2021/10/'> October </a> <span class='post-count' dir='ltr'>(3)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2021/09/'> September </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2021/08/'> August </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2021/06/'> June </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2021/05/'> May </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2021/04/'> April </a> <span class='post-count' dir='ltr'>(3)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2021/03/'> March </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2021/02/'> February </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2021/01/'> January </a> <span class='post-count' dir='ltr'>(10)</span> </li> </ul> </li> </ul> <ul class='hierarchy'> <li class='archivedate expanded'> <a class='toggle' href='javascript:void(0)'> <span class='zippy toggle-open'> ▼  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2020/'> 2020 </a> <span class='post-count' dir='ltr'>(36)</span> <ul class='hierarchy'> <li class='archivedate expanded'> <a class='toggle' href='javascript:void(0)'> <span class='zippy toggle-open'> ▼  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2020/12/'> December </a> <span class='post-count' dir='ltr'>(2)</span> <ul class='posts'> <li><a href='https://googleprojectzero.blogspot.com/2020/12/an-ios-hacker-tries-android.html'>An iOS hacker tries Android</a></li> <li><a href='https://googleprojectzero.blogspot.com/2020/12/an-ios-zero-click-radio-proximity.html'>An iOS zero-click radio proximity exploit odyssey</a></li> </ul> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2020/11/'> November </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2020/10/'> October </a> <span class='post-count' dir='ltr'>(2)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2020/09/'> September </a> <span class='post-count' dir='ltr'>(4)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2020/08/'> August </a> <span class='post-count' dir='ltr'>(5)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2020/07/'> July </a> <span class='post-count' dir='ltr'>(8)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2020/06/'> June </a> <span class='post-count' dir='ltr'>(2)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2020/04/'> April </a> <span class='post-count' dir='ltr'>(3)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2020/02/'> February </a> <span class='post-count' dir='ltr'>(4)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2020/01/'> January </a> <span class='post-count' dir='ltr'>(5)</span> </li> </ul> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2019/'> 2019 </a> <span class='post-count' dir='ltr'>(27)</span> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2019/12/'> December </a> <span class='post-count' dir='ltr'>(2)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2019/11/'> November </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2019/10/'> October </a> <span class='post-count' dir='ltr'>(2)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2019/09/'> September </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2019/08/'> August </a> <span class='post-count' dir='ltr'>(11)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2019/05/'> May </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2019/04/'> April </a> <span class='post-count' dir='ltr'>(3)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2019/03/'> March </a> <span class='post-count' dir='ltr'>(2)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2019/02/'> February </a> <span class='post-count' dir='ltr'>(2)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2019/01/'> January </a> <span class='post-count' dir='ltr'>(2)</span> </li> </ul> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2018/'> 2018 </a> <span class='post-count' dir='ltr'>(22)</span> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2018/12/'> December </a> <span class='post-count' dir='ltr'>(7)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2018/11/'> November </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2018/10/'> October </a> <span class='post-count' dir='ltr'>(4)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2018/09/'> September </a> <span class='post-count' dir='ltr'>(2)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2018/08/'> August </a> <span class='post-count' dir='ltr'>(3)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2018/07/'> July </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2018/06/'> June </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2018/05/'> May </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2018/04/'> April </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2018/01/'> January </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2017/'> 2017 </a> <span class='post-count' dir='ltr'>(19)</span> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2017/12/'> December </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2017/10/'> October </a> <span class='post-count' dir='ltr'>(3)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2017/09/'> September </a> <span class='post-count' dir='ltr'>(2)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2017/08/'> August </a> <span class='post-count' dir='ltr'>(2)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2017/07/'> July </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2017/05/'> May </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2017/04/'> April </a> <span class='post-count' dir='ltr'>(6)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2017/03/'> March </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2017/02/'> February </a> <span class='post-count' dir='ltr'>(2)</span> </li> </ul> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2016/'> 2016 </a> <span class='post-count' dir='ltr'>(17)</span> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2016/12/'> December </a> <span class='post-count' dir='ltr'>(2)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2016/11/'> November </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2016/10/'> October </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2016/09/'> September </a> <span class='post-count' dir='ltr'>(2)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2016/08/'> August </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2016/07/'> July </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2016/06/'> June </a> <span class='post-count' dir='ltr'>(3)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2016/03/'> March </a> <span class='post-count' dir='ltr'>(3)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2016/02/'> February </a> <span class='post-count' dir='ltr'>(2)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2016/01/'> January </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2015/'> 2015 </a> <span class='post-count' dir='ltr'>(33)</span> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2015/12/'> December </a> <span class='post-count' dir='ltr'>(2)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2015/11/'> November </a> <span class='post-count' dir='ltr'>(2)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2015/10/'> October </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2015/09/'> September </a> <span class='post-count' dir='ltr'>(4)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2015/08/'> August </a> <span class='post-count' dir='ltr'>(6)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2015/07/'> July </a> <span class='post-count' dir='ltr'>(5)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2015/06/'> June </a> <span class='post-count' dir='ltr'>(4)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2015/05/'> May </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2015/04/'> April </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2015/03/'> March </a> <span class='post-count' dir='ltr'>(2)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2015/02/'> February </a> <span class='post-count' dir='ltr'>(3)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2015/01/'> January </a> <span class='post-count' dir='ltr'>(2)</span> </li> </ul> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2014/'> 2014 </a> <span class='post-count' dir='ltr'>(11)</span> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2014/12/'> December </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2014/11/'> November </a> <span class='post-count' dir='ltr'>(2)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2014/10/'> October </a> <span class='post-count' dir='ltr'>(2)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2014/09/'> September </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2014/08/'> August </a> <span class='post-count' dir='ltr'>(2)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2014/07/'> July </a> <span class='post-count' dir='ltr'>(3)</span> </li> </ul> </li> </ul> </div> </div> <script type='text/javascript'> //<![CDATA[ (function(){ let archive_list = document.getElementById('ArchiveList'); if (archive_list == null) return; let cur_year = archive_list.querySelector('.post-count-link').innerText.trim() - 0; let last_year = 2014; let elements = []; const MONTHS = ',Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec'.split(','); let parent = document.getElementById('ArchiveList'); while (parent.childNodes.length) parent.removeChild(parent.childNodes[0]); function fetch_next_year() { let url = 'https://googleprojectzero.blogspot.com/?action=getTitles&widgetId=BlogArchive1&widgetType=BlogArchive&responseType=js&path=https%3A%2F%2Fgoogleprojectzero.blogspot.com%2F'+cur_year; fetch(url).then(resp => { if (!resp.ok) { console.log('http error'); return; } resp.text().then(text => { let scope = { _WidgetManager: { _HandleControllerResult: (name, method, results) => { elements.push(document.createElement('hr')); let year_header = document.createElement('div'); year_header.appendChild(document.createTextNode(cur_year)); year_header.style.fontSize = 'large'; elements.push(year_header); let list = document.createElement('ul'); elements.push(list); for (let obj of results.posts) { let link_parts = obj.url.split('/'); let year = link_parts[3]; let month = link_parts[4]; let el = document.createElement(/*'div'*/'li'); el.style.listStyleType = 'square'; el.style.listStylePosition = 'inside'; let link = document.createElement('a'); el.appendChild(link); link.appendChild(document.createTextNode(obj.title)); link.href = obj.url; let date_trailer = document.createElement('span'); el.appendChild(date_trailer); //date_trailer.appendChild(document.createTextNode(' ('+year+'-'+month+')')); date_trailer.appendChild(document.createTextNode(' ('+MONTHS[parseInt(month, 10)]+')')); //date_trailer.style.textAlign = 'right'; //elements.push(el); list.appendChild(el); } } } }; with (scope) { eval(text); } if (cur_year == last_year) { finish(); } else { cur_year--; fetch_next_year(); } }); }); } fetch_next_year(); function finish() { for (let obj of elements) { parent.appendChild(obj); } console.log(elements); } })(); //]]> </script> <div class='clear'></div> </div> </div></div> <table border='0' cellpadding='0' cellspacing='0' class='section-columns columns-2'> <tbody> <tr> <td class='first columns-cell'> <div class='sidebar no-items section' id='sidebar-right-2-1'></div> </td> <td class='columns-cell'> <div class='sidebar no-items section' id='sidebar-right-2-2'></div> </td> </tr> </tbody> </table> <div class='sidebar no-items section' id='sidebar-right-3'></div> </aside> </div> </div> </div> <div style='clear: both'></div> <!-- columns --> </div> <!-- main --> </div> </div> <div class='main-cap-bottom cap-bottom'> <div class='cap-left'></div> <div class='cap-right'></div> </div> </div> <footer> <div class='footer-outer'> <div class='footer-cap-top cap-top'> <div class='cap-left'></div> <div class='cap-right'></div> </div> <div class='fauxborder-left footer-fauxborder-left'> <div class='fauxborder-right footer-fauxborder-right'></div> <div class='region-inner footer-inner'> <div class='foot no-items section' id='footer-1'></div> <table border='0' cellpadding='0' cellspacing='0' class='section-columns columns-2'> <tbody> <tr> <td class='first columns-cell'> <div class='foot no-items section' id='footer-2-1'></div> </td> <td class='columns-cell'> <div class='foot no-items section' id='footer-2-2'></div> </td> </tr> </tbody> </table> <!-- outside of the include in order to lock Attribution widget --> <div class='foot section' id='footer-3' name='Footer'><div class='widget Attribution' data-version='1' id='Attribution1'> <div class='widget-content' style='text-align: center;'> Powered by <a href='https://www.blogger.com' target='_blank'>Blogger</a>. </div> <div class='clear'></div> </div></div> </div> </div> <div class='footer-cap-bottom cap-bottom'> <div class='cap-left'></div> <div class='cap-right'></div> </div> </div> </footer> <!-- content --> </div> </div> <div class='content-cap-bottom cap-bottom'> <div class='cap-left'></div> <div class='cap-right'></div> </div> </div> </div> <script type='text/javascript'> window.setTimeout(function() { document.body.className = document.body.className.replace('loading', ''); }, 10); </script> <script type="text/javascript" src="https://www.blogger.com/static/v1/widgets/984859869-widgets.js"></script> <script type='text/javascript'> window['__wavt'] = 'AOuZoY7Q5-EuxSUtpLLvaBdSZsnKc1CWPw:1732553283844';_WidgetManager._Init('//www.blogger.com/rearrange?blogID\x3d4838136820032157985','//googleprojectzero.blogspot.com/2020/12/','4838136820032157985'); _WidgetManager._SetDataContext([{'name': 'blog', 'data': {'blogId': '4838136820032157985', 'title': 'Project Zero', 'url': 'https://googleprojectzero.blogspot.com/2020/12/', 'canonicalUrl': 'https://googleprojectzero.blogspot.com/2020/12/', 'homepageUrl': 'https://googleprojectzero.blogspot.com/', 'searchUrl': 'https://googleprojectzero.blogspot.com/search', 'canonicalHomepageUrl': 'https://googleprojectzero.blogspot.com/', 'blogspotFaviconUrl': 'https://googleprojectzero.blogspot.com/favicon.ico', 'bloggerUrl': 'https://www.blogger.com', 'hasCustomDomain': false, 'httpsEnabled': true, 'enabledCommentProfileImages': true, 'gPlusViewType': 'FILTERED_POSTMOD', 'adultContent': false, 'analyticsAccountNumber': 'UA-240546891-1', 'encoding': 'UTF-8', 'locale': 'en', 'localeUnderscoreDelimited': 'en', 'languageDirection': 'ltr', 'isPrivate': false, 'isMobile': false, 'isMobileRequest': false, 'mobileClass': '', 'isPrivateBlog': false, 'isDynamicViewsAvailable': true, 'feedLinks': '\x3clink rel\x3d\x22alternate\x22 type\x3d\x22application/atom+xml\x22 title\x3d\x22Project Zero - Atom\x22 href\x3d\x22https://googleprojectzero.blogspot.com/feeds/posts/default\x22 /\x3e\n\x3clink rel\x3d\x22alternate\x22 type\x3d\x22application/rss+xml\x22 title\x3d\x22Project Zero - RSS\x22 href\x3d\x22https://googleprojectzero.blogspot.com/feeds/posts/default?alt\x3drss\x22 /\x3e\n\x3clink rel\x3d\x22service.post\x22 type\x3d\x22application/atom+xml\x22 title\x3d\x22Project Zero - Atom\x22 href\x3d\x22https://www.blogger.com/feeds/4838136820032157985/posts/default\x22 /\x3e\n', 'meTag': '', 'adsenseHostId': 'ca-host-pub-1556223355139109', 'adsenseHasAds': false, 'adsenseAutoAds': false, 'boqCommentIframeForm': true, 'loginRedirectParam': '', 'view': '', 'dynamicViewsCommentsSrc': '//www.blogblog.com/dynamicviews/4224c15c4e7c9321/js/comments.js', 'dynamicViewsScriptSrc': '//www.blogblog.com/dynamicviews/da8f33dd880cc4f1', 'plusOneApiSrc': 'https://apis.google.com/js/platform.js', 'disableGComments': true, 'interstitialAccepted': false, 'sharing': {'platforms': [{'name': 'Get link', 'key': 'link', 'shareMessage': 'Get link', 'target': ''}, {'name': 'Facebook', 'key': 'facebook', 'shareMessage': 'Share to Facebook', 'target': 'facebook'}, {'name': 'BlogThis!', 'key': 'blogThis', 'shareMessage': 'BlogThis!', 'target': 'blog'}, {'name': 'X', 'key': 'twitter', 'shareMessage': 'Share to X', 'target': 'twitter'}, {'name': 'Pinterest', 'key': 'pinterest', 'shareMessage': 'Share to Pinterest', 'target': 'pinterest'}, {'name': 'Email', 'key': 'email', 'shareMessage': 'Email', 'target': 'email'}], 'disableGooglePlus': true, 'googlePlusShareButtonWidth': 0, 'googlePlusBootstrap': '\x3cscript type\x3d\x22text/javascript\x22\x3ewindow.___gcfg \x3d {\x27lang\x27: \x27en\x27};\x3c/script\x3e'}, 'hasCustomJumpLinkMessage': false, 'jumpLinkMessage': 'Read more', 'pageType': 'archive', 'pageName': 'December 2020', 'pageTitle': 'Project Zero: December 2020'}}, {'name': 'features', 'data': {}}, {'name': 'messages', 'data': {'edit': 'Edit', 'linkCopiedToClipboard': 'Link copied to clipboard!', 'ok': 'Ok', 'postLink': 'Post Link'}}, {'name': 'template', 'data': {'name': 'custom', 'localizedName': 'Custom', 'isResponsive': false, 'isAlternateRendering': false, 'isCustom': true}}, {'name': 'view', 'data': {'classic': {'name': 'classic', 'url': '?view\x3dclassic'}, 'flipcard': {'name': 'flipcard', 'url': '?view\x3dflipcard'}, 'magazine': {'name': 'magazine', 'url': '?view\x3dmagazine'}, 'mosaic': {'name': 'mosaic', 'url': '?view\x3dmosaic'}, 'sidebar': {'name': 'sidebar', 'url': '?view\x3dsidebar'}, 'snapshot': {'name': 'snapshot', 'url': '?view\x3dsnapshot'}, 'timeslide': {'name': 'timeslide', 'url': '?view\x3dtimeslide'}, 'isMobile': false, 'title': 'Project Zero', 'description': 'News and updates from the Project Zero team at Google', 'url': 'https://googleprojectzero.blogspot.com/2020/12/', 'type': 'feed', 'isSingleItem': false, 'isMultipleItems': true, 'isError': false, 'isPage': false, 'isPost': false, 'isHomepage': false, 'isArchive': true, 'isLabelSearch': false, 'archive': {'year': 2020, 'month': 12, 'rangeMessage': 'Showing posts from December, 2020'}}}]); _WidgetManager._RegisterWidget('_NavbarView', new _WidgetInfo('Navbar1', 'navbar', document.getElementById('Navbar1'), {}, 'displayModeFull')); _WidgetManager._RegisterWidget('_HeaderView', new _WidgetInfo('Header1', 'header', document.getElementById('Header1'), {}, 'displayModeFull')); _WidgetManager._RegisterWidget('_BlogView', new _WidgetInfo('Blog1', 'main', document.getElementById('Blog1'), {'cmtInteractionsEnabled': false, 'lightboxEnabled': true, 'lightboxModuleUrl': 'https://www.blogger.com/static/v1/jsbin/2646514562-lbx.js', 'lightboxCssUrl': 'https://www.blogger.com/static/v1/v-css/1964470060-lightbox_bundle.css'}, 'displayModeFull')); _WidgetManager._RegisterWidget('_BlogSearchView', new _WidgetInfo('BlogSearch1', 'sidebar-right-1', document.getElementById('BlogSearch1'), {}, 'displayModeFull')); _WidgetManager._RegisterWidget('_PageListView', new _WidgetInfo('PageList1', 'sidebar-right-1', document.getElementById('PageList1'), {'title': 'Pages', 'links': [{'isCurrentPage': false, 'href': 'https://googleprojectzero.blogspot.com/p/about-project-zero.html', 'id': '4384467920505278144', 'title': 'About Project Zero'}, {'isCurrentPage': false, 'href': 'https://googleprojectzero.blogspot.com/p/working-at-project-zero.html', 'id': '2459334498880008057', 'title': 'Working at Project Zero'}, {'isCurrentPage': false, 'href': 'https://googleprojectzero.blogspot.com/p/0day.html', 'id': '3414239791814532209', 'title': '0day \x22In the Wild\x22'}, {'isCurrentPage': false, 'href': 'https://googleprojectzero.github.io/0days-in-the-wild/rca.html', 'title': '0day Exploit Root Cause Analyses'}, {'isCurrentPage': false, 'href': 'https://googleprojectzero.blogspot.com/p/vulnerability-disclosure-faq.html', 'id': '2935252455704572784', 'title': 'Vulnerability Disclosure FAQ'}], 'mobile': false, 'showPlaceholder': true, 'hasCurrentPage': false}, 'displayModeFull')); _WidgetManager._RegisterWidget('_BlogArchiveView', new _WidgetInfo('BlogArchive1', 'sidebar-right-1', document.getElementById('BlogArchive1'), {'languageDirection': 'ltr', 'loadingMessage': 'Loading\x26hellip;'}, 'displayModeFull')); _WidgetManager._RegisterWidget('_AttributionView', new _WidgetInfo('Attribution1', 'footer-3', document.getElementById('Attribution1'), {}, 'displayModeFull')); </script> </body> </html>