<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Pro bear</title>
    <description>The latest articles on DEV Community by Pro bear (@pro_bear1).</description>
    <link>https://dev.to/pro_bear1</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4227622%2Febe7b37d-09bf-4e7d-8a04-b44f22d36a99.png</url>
      <title>DEV Community: Pro bear</title>
      <link>https://dev.to/pro_bear1</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pro_bear1"/>
    <language>en</language>
    <item>
      <title>CVE-2026-58868: A HEIF Grid Mismatch in Android's Media Decoder</title>
      <dc:creator>Pro bear</dc:creator>
      <pubDate>Thu, 05 Nov 2026 06:03:24 +0000</pubDate>
      <link>https://dev.to/pro_bear1/cve-2026-58868-a-heif-grid-mismatch-in-androids-media-decoder-3bhc</link>
      <guid>https://dev.to/pro_bear1/cve-2026-58868-a-heif-grid-mismatch-in-androids-media-decoder-3bhc</guid>
      <description>&lt;p&gt;Hey everyone, this is my first post, so if you spot any mistakes, please let me know. I don't usually write about my CVEs, but with Harry Kane finally winning the Ballon d'Or and extending his contract with Bayern this felt like a good excuse to do something special. Before getting into the bug, here's how I ended up looking at Android in the first place.&lt;/p&gt;

&lt;p&gt;Most of my time goes into watching football and researching Windows. One day, there were no matches to watch, and I was bored of staring at Windows disassembly. Then my old Oneplus android phone caught my eye, and I thought, might as well tinker with it.&lt;/p&gt;

&lt;p&gt;I wasn't very familiar with Android, so I used an LLM to help me understand how things fit together. I ended up liking it for a pretty obvious reason: I could just read the source code. After poking around for a while, I found a zero click Dos bug in &lt;code&gt;system_server&lt;/code&gt; involving mDNS.&lt;/p&gt;

&lt;p&gt;That made me want to keep digging. Media parsers seemed like an interesting place to look, especially given how often they show up for zero click exploit. So I started reading the media code, with some help from AI to make sense of the unfamiliar parts. I found the bug through static analysis, which is how I do my Windows research.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;CVE-2026-58868 is an out-of-bounds write in Android's HEIF image-decoding path. A crafted image can make the decoder place tiles outside the allocated output image, causing &lt;code&gt;mediaserver&lt;/code&gt; to write converted pixels out of bounds. Google classifies it as a &lt;strong&gt;Critical RCE&lt;/strong&gt; affecting &lt;strong&gt;Android 14, 15, 16, 16 QPR2, and 17&lt;/strong&gt;. (&lt;a href="https://source.android.com/docs/security/bulletin/2026/2026-11-01" rel="noopener noreferrer"&gt;Android bulletin&lt;/a&gt;)&lt;br&gt;
&lt;strong&gt;But I have reproduced this also in an Android 12 device (moto g31).&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In Gmail, opening the email was enough: thumbnail generation decoded the image without me tapping it or manually downloading the attachment. I also reproduced the issue through Google Files and Google Photos. The crash below is from Gmail on my OnePlus  CPH2467.&lt;/p&gt;
&lt;h2&gt;
  
  
  Root cause
&lt;/h2&gt;

&lt;p&gt;A HEIF grid image is assembled from smaller encoded tiles. The output buffer is sized for the overall image, while decoded tile dimensions determine where each tile is placed. In the reproducer, the tiles decode to 1024 x 1024, but the declared output dimensions do not accommodate that tile layout.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://android.googlesource.com/platform/frameworks/av/+/1863cb0ea5a9e1340f11a435618ff863e017da29/media/libmediaplayerservice/StagefrightMetadataRetriever.cpp#269" rel="noopener noreferrer"&gt;StagefrightMetadataRetriever&lt;/a&gt; reads the tile dimensions and uses them to select a suitable codec:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;thumbnail&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;isHeif&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;format&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;int32_t&lt;/span&gt; &lt;span class="n"&gt;tileWidth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tileHeight&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trackMeta&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;findInt32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kKeyTileWidth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;tileWidth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;tileWidth&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
            &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;trackMeta&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;findInt32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kKeyTileHeight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;tileHeight&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;tileHeight&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;setInt32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"height"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tileHeight&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;setInt32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"width"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tileWidth&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, &lt;code&gt;thumbnail&lt;/code&gt; means a thumbnail stored inside the file; an app can generate its preview by decoding the main image.&lt;/p&gt;

&lt;p&gt;The bug is in &lt;a href="https://android.googlesource.com/platform/frameworks/av/+/1863cb0ea5a9e1340f11a435618ff863e017da29/media/libstagefright/FrameDecoder.cpp#1036" rel="noopener noreferrer"&gt;&lt;code&gt;MediaImageDecoder::onOutputReceived()&lt;/code&gt;&lt;/a&gt;. It calculates each tile's destination using the decoded crop dimensions. It clips the right and bottom edges to the output size, but does not first reject a tile whose starting position is already outside the image. A tile starting below the canvas can therefore produce a negative crop height.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://android.googlesource.com/platform/frameworks/av/+/1863cb0ea5a9e1340f11a435618ff863e017da29/media/libstagefright/colorconversion/ColorConverter.cpp#660" rel="noopener noreferrer"&gt;ColorConverter&lt;/a&gt; checks that the source and destination crop dimensions agree:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cropWidth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;dst&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cropWidth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cropHeight&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;dst&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cropHeight&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both rectangles can be invalid in the same way and still pass these comparisons. The other stride and alignment checks do not establish that the complete rectangles are within bounds.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://android.googlesource.com/platform/frameworks/av/+/1863cb0ea5a9e1340f11a435618ff863e017da29/media/libstagefright/colorconversion/ColorConverter.cpp#948" rel="noopener noreferrer"&gt;semiplanar conversion wrapper&lt;/a&gt; then passes the crop dimensions to libyuv. Abbreviating the chroma and matrix selection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;libyuvFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src_y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;src_stride_y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;src_uv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;src_stride_uv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="n"&gt;dst_ptr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dst&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mStride&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cropWidth&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cropHeight&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;libyuv takes the height as a signed integer. The dump is consistent with an invalid crop reaching this call as height &lt;code&gt;-1096&lt;/code&gt;. In &lt;a href="https://android.googlesource.com/platform/external/libyuv/+/0dbb8351b69b62539acfd5b05f5630fba662ad00/source/convert_argb.cc#4078" rel="noopener noreferrer"&gt;&lt;code&gt;NV21ToARGBMatrix&lt;/code&gt;&lt;/a&gt;, a negative height requests vertically inverted output, so libyuv adjusts the destination pointer before converting pixels. An invalid crop has now become a write outside the output buffer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Gmail crash
&lt;/h2&gt;

&lt;p&gt;The crashing process was the 32-bit ARM &lt;code&gt;mediaserver&lt;/code&gt;, in the &lt;code&gt;ImageDecoderOut&lt;/code&gt; thread.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Process: /system/bin/mediaserver (pid 18621, uid 1013)
Thread:  ImageDecoderOut (tid 27553)
Signal:  SIGSEGV (SEGV_MAPERR)
Fault:   0xe246e5c0

#00 NV21ToARGBRow_NEON.cfi +142
#01 NV21ToARGBMatrix.cfi +170
#02 std::function::operator() +82
#03 ColorConverter::convertYUV420SemiPlanarUseLibYUV +808
#04 ColorConverter::convertYUVMediaImage +236
#05 ColorConverter::convert +780
#06 MediaImageDecoder::onOutputReceived +1668
#07 MediaImageDecoder::outputLoop +1794

r2   e246e5c0
r10  00000400
r11  00000400
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The instruction bytes at the fault decode to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0xf03c60e8:  vst4.8  {d0, d2, d4, d6}, [r2]!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This attempts to store 32 bytes of converted pixel data. &lt;code&gt;r2&lt;/code&gt; contains the exact fault address, which falls in an unmapped gap. The immediate failure is a destination write.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;r10&lt;/code&gt; and &lt;code&gt;r11&lt;/code&gt; still contain &lt;code&gt;0x400&lt;/code&gt; (1024), matching the controlled tile dimension. &lt;/p&gt;

&lt;h2&gt;
  
  
  Impact
&lt;/h2&gt;

&lt;p&gt;The result is an attacker influenced out of bounds write through a remotely supplied image The Gmail reproduction required opening the email, after which thumbnail generation triggered the bug.&lt;/p&gt;

&lt;p&gt;The vulnerability is in Android’s HEIF decoding path and isn’t specific to Gmail. I also reproduced it during preview generation in Google Photos and google Files. Other apps using the same vulnerable decoding path may also be affected, although I haven’t tested them.&lt;/p&gt;

&lt;p&gt;There was not a flaw in the code itself everypart worked as intended the issue was missing validation.&lt;/p&gt;

&lt;p&gt;My apologies for not releasing the poc or sharing much more details for now as the CVE was released just a few days back and some Android versions still are not getting an update.&lt;/p&gt;

&lt;h2&gt;
  
  
  Timeline
&lt;/h2&gt;

&lt;p&gt;Reported - Feb 22, 2026&lt;br&gt;
Validated by Android Security Team - Mar 12, 2026&lt;br&gt;
Some back and forth&lt;br&gt;
Final Severity rating - May 7, 2026&lt;br&gt;
Bounty - May 14, 2026&lt;br&gt;
Patch Release - Nov 2, 2026&lt;/p&gt;

</description>
      <category>security</category>
      <category>android</category>
      <category>cve</category>
    </item>
  </channel>
</rss>
