CVE-2026-10985
Overview
Files Changed
src/sksl/generated/sksl_rt_shader.minified.skslsrc/sksl/generated/sksl_rt_shader.unoptimized.sksl
Patch
From bd3d88bbbcfef52f755833215bcc1d1ce695bc26 Mon Sep 17 00:00:00 2001
From: Jorge Betancourt <jmbetancourt@google.com>
Date: Thu, 21 May 2026 11:25:35 -0400
Subject: [PATCH] Clamp displacement values before offsetting sampling coordinates
Bug: b/514082801
Change-Id: I4662eb2d969bc6003cd7178306e82b6d3580758a
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/1239498
Auto-Submit: Jorge Betancourt <jmbetancourt@google.com>
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
---
diff --git a/src/sksl/generated/sksl_rt_shader.minified.sksl b/src/sksl/generated/sksl_rt_shader.minified.sksl
index c15e6a1..50a39b7 100644
--- a/src/sksl/generated/sksl_rt_shader.minified.sksl
+++ b/src/sksl/generated/sksl_rt_shader.minified.sksl
@@ -3,44 +3,44 @@
",0.,0.,saturate(dot(half3(.2126,.7152,.0722),a)));}half4 sk_decal(shader a,"
"float2 b,float4 c){half4 e=half4(c-b.xyxy)*half4(-1.,-1.,1.,1.);e=saturate("
"e+.5);return(((e.x*e.y)*e.z)*e.w)*a.eval(b);}half4 sk_displacement(shader a"
-",shader b,float2 c,half2 d,half4 e,half4 f){half4 g=unpremul(a.eval(c));half2"
-" h=half2(dot(g,e),dot(g,f));h=d*(h-.5);return b.eval(c+float2(h));}half4 sk_magnifier"
-"(shader a,float2 b,float4 c,float4 d,float2 e){float2 f=d.xy+d.zw*b;float2 g"
-"=min(b-c.xy,c.zw-b)*e;float h=all(lessThan(g,2..xx))?2.-length(2.-g):min(g."
-"x,g.y);h=saturate(h);return a.eval(mix(b,f,h*h));}$pure half3 $b(half3 a){half"
-" b=max(max(a.x,a.y),a.z);half e=min(min(a.x,a.y),a.z);half f=b-e;half g=1./"
-"f;half i=half(a.y<a.z?6.:0.);half j=.166666672*(b==e?0.:(a.x>=a.y&&a.x>=a.z"
-"?g*(a.y-a.z)+i:(a.y>=a.z?g*(a.z-a.x)+2.:g*(a.x-a.y)+4.)));half k=b+e;half m"
-"=k*.5;half n=b==e?0.:f/(m>.5?2.-k:k);return half3(j,n,m);}half3 sk_high_contrast"
-"(half3 a,half b,half c,half d){if(b==1.)a=dot(half3(.2126,.7152,.0722),a).xxx"
-";if(c==1.)a=1.-a;else if(c==2.){a=$b(a);a.z=1.-a.z;a=$hsl_to_rgb(a);}return"
-" saturate(mix(half3(.5),a,d));}$pure half3 $c(half3 a,half3 b,half3 c,half d"
-"){const half3 e=half3(.25,.5,.25);half3 f=half3(a.x,b.x,c.x);half3 g=half3("
-"a.z,b.z,c.z);half h=dot(e,c)-dot(e,a);half i=dot(e,g)-dot(e,f);return normalize"
-"(half3(d*half2(h,i),1.));}half4 sk_normal(shader a,float2 b,float4 c,half d"
-"){half3 e=half3(a.eval(clamp(b+ -1..xx,c.xy,c.zw)).w,a.eval(clamp(b+float2("
-"-1.,0.),c.xy,c.zw)).w,a.eval(clamp(b+float2(-1.,1.),c.xy,c.zw)).w);half3 f="
-"half3(a.eval(clamp(b+float2(0.,-1.),c.xy,c.zw)).w,a.eval(clamp(b,c.xy,c.zw)"
-").w,a.eval(clamp(b+float2(0.,1.),c.xy,c.zw)).w);half3 g=half3(a.eval(clamp("
-"b+float2(1.,-1.),c.xy,c.zw)).w,a.eval(clamp(b+float2(1.,0.),c.xy,c.zw)).w,a"
-".eval(clamp(b+1..xx,c.xy,c.zw)).w);half h=f.y;return half4($c(e,f,g,d),h);}"
-"$pure half3 $d(half a,half3 b,half3 c,half3 d){return a>=0.?normalize(b-d):"
-"c;}$pure half $e(half3 a,half3 b,half c,half d){half g=-dot(b,a);if(g<c)return"
-" 0.;else{half h=pow(g,d);return g<c+.016?(h*(g-c))*62.4999962:h;}}$pure half4"
-" $f(half3 a,half b,half c,half d,half3 e,half3 f,half3 g,half h,half i){if("
-"d>0.)a*=$e(f,g,h,i);if(c==0.){half j=dot(e,g);a=saturate(j*a);return half4("
-"a,1.);}else if(c==1.){half3 j=normalize(g+half3(0.,0.,1.));half k=pow(dot(e"
-",j),b);a=saturate(k*a);return half4(a,max(max(a.x,a.y),a.z));}else{half j=("
-"2.*dot(e,g)-g.z)*g.z;half k=pow(j,b);a=saturate(k*a);return half4(a,max(max"
-"(a.x,a.y),a.z));}}half4 sk_lighting(shader a,float2 b,half c,half d,half e,"
-"half f,half3 g,half h,half3 i,half j,half3 k){half4 l=a.eval(b);half3 m=$d("
-"f,g,i,half3(half2(b),c*l.w));return $f(k,d,e,f,l.xyz,i,m,j,h);}half4 sk_arithmetic_blend"
-"(half4 a,half4 b,half4 c,half d){half4 e=saturate((((c.x*a)*b+c.y*a)+c.z*b)"
-"+c.w);e.xyz=min(e.xyz,max(e.w,d));return e;}half4 sk_sparse_morphology(shader"
-" a,float2 b,half2 c,half d){half4 e=max(d*a.eval(b+float2(c)),d*a.eval(b-float2"
-"(c)));return d*e;}half4 sk_linear_morphology(shader a,float2 b,half2 c,half"
-" d,int e){half4 g=d*a.eval(b);half2 h=c;for(int j=1;j<=14;++j){if(j>e)break"
-";g=max(g,max(d*a.eval(b+float2(h)),d*a.eval(b-float2(h))));h+=c;}return d*g"
-";}half4 sk_overdraw(half a,half4 b,half4 c,half4 d,half4 e,half4 f,half4 g)"
-"{return a<.00196078443?b:(a<.005882353?c:(a<.009803922?d:(a<.01372549?e:(a<"
-".01764706?f:g))));}";
+",shader b,float2 c,half2 d,half4 e,half4 f){half4 g=saturate(unpremul(a.eval"
+"(c)));half2 h=half2(dot(g,e),dot(g,f));h=d*(h-.5);return b.eval(c+float2(h)"
+");}half4 sk_magnifier(shader a,float2 b,float4 c,float4 d,float2 e){float2 f"
+"=d.xy+d.zw*b;float2 g=min(b-c.xy,c.zw-b)*e;float h=all(lessThan(g,2..xx))?2."
+"-length(2.-g):min(g.x,g.y);h=saturate(h);return a.eval(mix(b,f,h*h));}$pure"
+" half3 $b(half3 a){half b=max(max(a.x,a.y),a.z);half e=min(min(a.x,a.y),a.z"
+");half f=b-e;half g=1./f;half i=half(a.y<a.z?6.:0.);half j=.166666672*(b==e"
+"?0.:(a.x>=a.y&&a.x>=a.z?g*(a.y-a.z)+i:(a.y>=a.z?g*(a.z-a.x)+2.:g*(a.x-a.y)+"
+"4.)));half k=b+e;half m=k*.5;half n=b==e?0.:f/(m>.5?2.-k:k);return half3(j,"
+"n,m);}half3 sk_high_contrast(half3 a,half b,half c,half d){if(b==1.)a=dot(half3"
+"(.2126,.7152,.0722),a).xxx;if(c==1.)a=1.-a;else if(c==2.){a=$b(a);a.z=1.-a."
+"z;a=$hsl_to_rgb(a);}return saturate(mix(half3(.5),a,d));}$pure half3 $c(half3"
+" a,half3 b,half3 c,half d){const half3 e=half3(.25,.5,.25);half3 f=half3(a."
+"x,b.x,c.x);half3 g=half3(a.z,b.z,c.z);half h=dot(e,c)-dot(e,a);half i=dot(e"
+",g)-dot(e,f);return normalize(half3(d*half2(h,i),1.));}half4 sk_normal(shader"
+" a,float2 b,float4 c,half d){half3 e=half3(a.eval(clamp(b+ -1..xx,c.xy,c.zw"
+")).w,a.eval(clamp(b+float2(-1.,0.),c.xy,c.zw)).w,a.eval(clamp(b+float2(-1.,"
+"1.),c.xy,c.zw)).w);half3 f=half3(a.eval(clamp(b+float2(0.,-1.),c.xy,c.zw))."
+"w,a.eval(clamp(b,c.xy,c.zw)).w,a.eval(clamp(b+float2(0.,1.),c.xy,c.zw)).w);"
+"half3 g=half3(a.eval(clamp(b+float2(1.,-1.),c.xy,c.zw)).w,a.eval(clamp(b+float2"
+"(1.,0.),c.xy,c.zw)).w,a.eval(clamp(b+1..xx,c.xy,c.zw)).w);half h=f.y;return"
+" half4($c(e,f,g,d),h);}$pure half3 $d(half a,half3 b,half3 c,half3 d){return"
+" a>=0.?normalize(b-d):c;}$pure half $e(half3 a,half3 b,half c,half d){half g"
+"=-dot(b,a);if(g<c)return 0.;else{half h=pow(g,d);return g<c+.016?(h*(g-c))*"
+"62.4999962:h;}}$pure half4 $f(half3 a,half b,half c,half d,half3 e,half3 f,"
+"half3 g,half h,half i){if(d>0.)a*=$e(f,g,h,i);if(c==0.){half j=dot(e,g);a=saturate"
+"(j*a);return half4(a,1.);}else if(c==1.){half3 j=normalize(g+half3(0.,0.,1."
+"));half k=pow(dot(e,j),b);a=saturate(k*a);return half4(a,max(max(a.x,a.y),a"
+".z));}else{half j=(2.*dot(e,g)-g.z)*g.z;half k=pow(j,b);a=saturate(k*a);return"
+" half4(a,max(max(a.x,a.y),a.z));}}half4 sk_lighting(shader a,float2 b,half c"
+",half d,half e,half f,half3 g,half h,half3 i,half j,half3 k){half4 l=a.eval"
+"(b);half3 m=$d(f,g,i,half3(half2(b),c*l.w));return $f(k,d,e,f,l.xyz,i,m,j,h"
+");}half4 sk_arithmetic_blend(half4 a,half4 b,half4 c,half d){half4 e=saturate"
+"((((c.x*a)*b+c.y*a)+c.z*b)+c.w);e.xyz=min(e.xyz,max(e.w,d));return e;}half4"
+" sk_sparse_morphology(shader a,float2 b,half2 c,half d){half4 e=max(d*a.eval"
+"(b+float2(c)),d*a.eval(b-float2(c)));return d*e;}half4 sk_linear_morphology"
+"(shader a,float2 b,half2 c,half d,int e){half4 g=d*a.eval(b);half2 h=c;for("
+"int j=1;j<=14;++j){if(j>e)break;g=max(g,max(d*a.eval(b+float2(h)),d*a.eval("
+"b-float2(h))));h+=c;}return d*g;}half4 sk_overdraw(half a,half4 b,half4 c,half4"
+" d,half4 e,half4 f,half4 g){return a<.00196078443?b:(a<.005882353?c:(a<.009803922"
+"?d:(a<.01372549?e:(a<.01764706?f:g))));}";
diff --git a/src/sksl/generated/sksl_rt_shader.unoptimized.sksl b/src/sksl/generated/sksl_rt_shader.unoptimized.sksl
index b0c764e..b3c09b8 100644
--- a/src/sksl/generated/sksl_rt_shader.unoptimized.sksl
+++ b/src/sksl/generated/sksl_rt_shader.unoptimized.sksl
@@ -4,72 +4,73 @@
" image,float2 coord,float4 decalBounds){half4 d=half4(decalBounds-coord.xyxy"
")*half4(-1.,-1.,1.,1.);d=saturate(d+.5);return(((d.x*d.y)*d.z)*d.w)*image.eval"
"(coord);}half4 sk_displacement(shader displMap,shader colorMap,float2 coord"
-",half2 scale,half4 xSelect,half4 ySelect){half4 displColor=unpremul(displMap"
-".eval(coord));half2 displ=half2(dot(displColor,xSelect),dot(displColor,ySelect"
-"));displ=scale*(displ-.5);return colorMap.eval(coord+float2(displ));}half4 sk_magnifier"
-"(shader src,float2 coord,float4 lensBounds,float4 zoomXform,float2 invInset"
-"){float2 zoomCoord=zoomXform.xy+zoomXform.zw*coord;float2 edgeInset=min(coord"
-"-lensBounds.xy,lensBounds.zw-coord)*invInset;float weight=all(lessThan(edgeInset"
-",2..xx))?2.-length(2.-edgeInset):min(edgeInset.x,edgeInset.y);weight=saturate"
-"(weight);return src.eval(mix(coord,zoomCoord,weight*weight));}$pure half3 $high_contrast_rgb_to_hsl"
-"(half3 c){half mx=max(max(c.x,c.y),c.z);half mn=min(min(c.x,c.y),c.z);half d"
-"=mx-mn;half invd=1./d;half g_lt_b=half(c.y<c.z?6.:0.);half h=.166666672*(mx"
-"==mn?0.:(c.x>=c.y&&c.x>=c.z?invd*(c.y-c.z)+g_lt_b:(c.y>=c.z?invd*(c.z-c.x)+"
-"2.:invd*(c.x-c.y)+4.)));half sum=mx+mn;half l=sum*.5;half s=mx==mn?0.:d/(l>"
-".5?2.-sum:sum);return half3(h,s,l);}half3 sk_high_contrast(half3 color,half"
-" grayscale,half invertStyle,half contrast){if(grayscale==1.)color=dot(half3"
-"(.2126,.7152,.0722),color).xxx;if(invertStyle==1.)color=1.-color;else if(invertStyle"
-"==2.){color=$high_contrast_rgb_to_hsl(color);color.z=1.-color.z;color=$hsl_to_rgb"
-"(color);}return saturate(mix(half3(.5),color,contrast));}$pure half3 $normal_filter"
-"(half3 alphaC0,half3 alphaC1,half3 alphaC2,half negSurfaceDepth){const half3"
-" kSobel=half3(.25,.5,.25);half3 alphaR0=half3(alphaC0.x,alphaC1.x,alphaC2.x"
-");half3 alphaR2=half3(alphaC0.z,alphaC1.z,alphaC2.z);half nx=dot(kSobel,alphaC2"
-")-dot(kSobel,alphaC0);half ny=dot(kSobel,alphaR2)-dot(kSobel,alphaR0);return"
-" normalize(half3(negSurfaceDepth*half2(nx,ny),1.));}half4 sk_normal(shader alphaMap"
-",float2 coord,float4 edgeBounds,half negSurfaceDepth){half3 alphaC0=half3(alphaMap"
-".eval(clamp(coord+ -1..xx,edgeBounds.xy,edgeBounds.zw)).w,alphaMap.eval(clamp"
-"(coord+float2(-1.,0.),edgeBounds.xy,edgeBounds.zw)).w,alphaMap.eval(clamp(coord"
-"+float2(-1.,1.),edgeBounds.xy,edgeBounds.zw)).w);half3 alphaC1=half3(alphaMap"
-".eval(clamp(coord+float2(0.,-1.),edgeBounds.xy,edgeBounds.zw)).w,alphaMap.eval"
-"(clamp(coord,edgeBounds.xy,edgeBounds.zw)).w,alphaMap.eval(clamp(coord+float2"
-"(0.,1.),edgeBounds.xy,edgeBounds.zw)).w);half3 alphaC2=half3(alphaMap.eval("
-"clamp(coord+float2(1.,-1.),edgeBounds.xy,edgeBounds.zw)).w,alphaMap.eval(clamp"
-"(coord+float2(1.,0.),edgeBounds.xy,edgeBounds.zw)).w,alphaMap.eval(clamp(coord"
-"+1..xx,edgeBounds.xy,edgeBounds.zw)).w);half mainAlpha=alphaC1.y;return half4"
-"($normal_filter(alphaC0,alphaC1,alphaC2,negSurfaceDepth),mainAlpha);}$pure half3"
-" $surface_to_light(half lightType,half3 lightPos,half3 lightDir,half3 coord"
-"){return lightType>=0.?normalize(lightPos-coord):lightDir;}$pure half $spotlight_scale"
-"(half3 lightDir,half3 surfaceToLight,half cosCutoffAngle,half spotFalloff){"
-"const half kConeAAThreshold=.016;const half kConeScale=62.4999962;half cosAngle"
-"=-dot(surfaceToLight,lightDir);if(cosAngle<cosCutoffAngle)return 0.;else{half"
-" scale=pow(cosAngle,spotFalloff);return cosAngle<cosCutoffAngle+kConeAAThreshold"
-"?(scale*(cosAngle-cosCutoffAngle))*kConeScale:scale;}}$pure half4 $compute_lighting"
-"(half3 color,half shininess,half materialType,half lightType,half3 normal,half3"
-" lightDir,half3 surfaceToLight,half cosCutoffAngle,half spotFalloff){if(lightType"
-">0.)color*=$spotlight_scale(lightDir,surfaceToLight,cosCutoffAngle,spotFalloff"
-");if(materialType==0.){half coeff=dot(normal,surfaceToLight);color=saturate"
-"(coeff*color);return half4(color,1.);}else if(materialType==1.){half3 halfDir"
-"=normalize(surfaceToLight+half3(0.,0.,1.));half coeff=pow(dot(normal,halfDir"
-"),shininess);color=saturate(coeff*color);return half4(color,max(max(color.x"
-",color.y),color.z));}else{half hilite=(2.*dot(normal,surfaceToLight)-surfaceToLight"
-".z)*surfaceToLight.z;half coeff=pow(hilite,shininess);color=saturate(coeff*"
-"color);return half4(color,max(max(color.x,color.y),color.z));}}half4 sk_lighting"
-"(shader normalMap,float2 coord,half depth,half shininess,half materialType,"
-"half lightType,half3 lightPos,half spotFalloff,half3 lightDir,half cosCutoffAngle"
-",half3 lightColor){half4 normalAndA=normalMap.eval(coord);half3 surfaceToLight"
-"=$surface_to_light(lightType,lightPos,lightDir,half3(half2(coord),depth*normalAndA"
-".w));return $compute_lighting(lightColor,shininess,materialType,lightType,normalAndA"
-".xyz,lightDir,surfaceToLight,cosCutoffAngle,spotFalloff);}half4 sk_arithmetic_blend"
-"(half4 src,half4 dst,half4 k,half pmClamp){half4 color=saturate((((k.x*src)"
-"*dst+k.y*src)+k.z*dst)+k.w);color.xyz=min(color.xyz,max(color.w,pmClamp));return"
-" color;}half4 sk_sparse_morphology(shader child,float2 coord,half2 offset,half"
-" flip){half4 aggregate=max(flip*child.eval(coord+float2(offset)),flip*child"
-".eval(coord-float2(offset)));return flip*aggregate;}half4 sk_linear_morphology"
-"(shader child,float2 coord,half2 offset,half flip,int radius){const int kMaxLinearRadius"
-"=14;half4 aggregate=flip*child.eval(coord);half2 delta=offset;for(int i=1;i"
-"<=kMaxLinearRadius;++i){if(i>radius)break;aggregate=max(aggregate,max(flip*"
-"child.eval(coord+float2(delta)),flip*child.eval(coord-float2(delta))));delta"
-"+=offset;}return flip*aggregate;}half4 sk_overdraw(half alpha,half4 color0,"
-"half4 color1,half4 color2,half4 color3,half4 color4,half4 color5){return alpha"
-"<.00196078443?color0:(alpha<.005882353?color1:(alpha<.009803922?color2:(alpha"
-"<.01372549?color3:(alpha<.01764706?color4:color5))));}";
+",half2 scale,half4 xSelect,half4 ySelect){half4 displColor=saturate(unpremul"
+"(displMap.eval(coord)));half2 displ=half2(dot(displColor,xSelect),dot(displColor"
+",ySelect));displ=scale*(displ-.5);return colorMap.eval(coord+float2(displ))"
+";}half4 sk_magnifier(shader src,float2 coord,float4 lensBounds,float4 zoomXform"
+",float2 invInset){float2 zoomCoord=zoomXform.xy+zoomXform.zw*coord;float2 edgeInset"
+"=min(coord-lensBounds.xy,lensBounds.zw-coord)*invInset;float weight=all(lessThan"
+"(edgeInset,2..xx))?2.-length(2.-edgeInset):min(edgeInset.x,edgeInset.y);weight"
+"=saturate(weight);return src.eval(mix(coord,zoomCoord,weight*weight));}$pure"
+" half3 $high_contrast_rgb_to_hsl(half3 c){half mx=max(max(c.x,c.y),c.z);half"
+" mn=min(min(c.x,c.y),c.z);half d=mx-mn;half invd=1./d;half g_lt_b=half(c.y<"
+"c.z?6.:0.);half h=.166666672*(mx==mn?0.:(c.x>=c.y&&c.x>=c.z?invd*(c.y-c.z)+"
+"g_lt_b:(c.y>=c.z?invd*(c.z-c.x)+2.:invd*(c.x-c.y)+4.)));half sum=mx+mn;half"
+" l=sum*.5;half s=mx==mn?0.:d/(l>.5?2.-sum:sum);return half3(h,s,l);}half3 sk_high_contrast"
+"(half3 color,half grayscale,half invertStyle,half contrast){if(grayscale==1."
+")color=dot(half3(.2126,.7152,.0722),color).xxx;if(invertStyle==1.)color=1.-"
+"color;else if(invertStyle==2.){color=$high_contrast_rgb_to_hsl(color);color"
+".z=1.-color.z;color=$hsl_to_rgb(color);}return saturate(mix(half3(.5),color"
+",contrast));}$pure half3 $normal_filter(half3 alphaC0,half3 alphaC1,half3 alphaC2"
+",half negSurfaceDepth){const half3 kSobel=half3(.25,.5,.25);half3 alphaR0=half3"
+"(alphaC0.x,alphaC1.x,alphaC2.x);half3 alphaR2=half3(alphaC0.z,alphaC1.z,alphaC2"
+".z);half nx=dot(kSobel,alphaC2)-dot(kSobel,alphaC0);half ny=dot(kSobel,alphaR2"
Original Bug Report
Cross-origin GPU memory disclosure via SkDisplacementMapImageFilter
Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.
Overview: A compromised renderer can exploit Skia’s displacement map implementation to leak cross-origin GPU scratch memory. By crafting ‘super-luminescent’ colors, an attacker can trigger a massive displacement that bypasses Skia’s bounds checks, leading to the sampling of uninitialized GPU memory recycled from other contexts.
Affected files:
third_party/skia/src/sksl/sksl_rt_shader.skslthird_party/skia/src/effects/imagefilters/SkDisplacementMapImageFilter.cppthird_party/skia/src/core/SkImageFilterTypes.cppcc/paint/paint_op_reader.ccthird_party/skia/src/gpu/ganesh/image/SkSpecialImage_Ganesh.cppthird_party/skia/src/gpu/ganesh/GrSurfaceProxy.cpp
Estimated timestamp from git blame: 2023-06-09
Summary
A potential vulnerability in Skia’s displacement map implementation (used in Chrome’s GPU process for Out-of-Process Rasterization) allows a compromised renderer to disclose cross-origin GPU memory. The issue stems from an amplification effect when unpremultiplying displacement map inputs, combined with a failure in Skia’s bounds analysis to account for these amplified offsets.
Potential Root Cause
The vulnerability is located in the sk_displacement SkSL function used by SkDisplacementMapImageFilter.
// third_party/skia/src/sksl/sksl_rt_shader.sksl
half4 sk_displacement(shader displMap, shader colorMap, float2 coord,
half2 scale, half4 xSelect, half4 ySelect) {
half4 displColor = unpremul(displMap.eval(coord)); // <--- No clamping
half2 displ = half2(dot(displColor, xSelect), dot(displColor, ySelect));
displ = scale * (displ - 0.5);
return colorMap.eval(coord + displ);
}
The unpremul function divides the RGB components by the alpha channel. In third_party/skia/src/sksl/sksl_shared.sksl, it is defined as color.rgb / max(color.a, 0.0001). If the alpha channel is near zero but the RGB components are non-zero (a ‘super-luminescent’ color), the result is extremely large (up to 10,000x).
Normally, Skia filters operate on valid premultiplied colors where RGB <= Alpha. However, a compromised renderer can use a cc::ArithmeticPaintFilter with the enforce_pm_color flag set to false to generate such invalid colors. During serialization, cc/paint/paint_op_reader.cc blindly trusts this renderer-provided flag.
Exploitation Mechanism
- Renderer Compromise: The attacker gains control of a renderer and serializes a malicious
PaintFiltergraph containing acc::DisplacementMapEffectPaintFilter. - Arithmetic Filter Manipulation: By setting
enforce_pm_color = falsein anArithmeticPaintFilter, the attacker produces an intermediate texture containing colors like(1.0, 1.0, 1.0, 0.0). - Bounds Analysis Bypass: Skia’s
analyzeBoundslogic (inthird_party/skia/src/core/SkImageFilterTypes.cpp) determines whether strict shader-based tiling (clamping to the logical image subset) is required. For approximately-sized (kApprox) scratch textures used in the Ganesh backend, this logic can be fooled into using hardwareCLAMP_TO_EDGEinstead of strict shader-clamping if it believes the sample coordinates will remain within the texture’s bounds. Theexactify()mechanism inGrSurfaceProxyexacerbates this by expanding the perceived image dimensions to the full backing-store dimensions. - Massive Displacement: The
sk_displacementshader calculates a displacement of nearly10,000 * scale. This displacement moves the sampling coordinate far outside the intended logical image bounds and into the uninitialized padding of the scratch texture. - Memory Disclosure: In OOP-R, the GPU process uses a shared context where scratch textures are recycled across different renderers and origins. The padding of these textures contains stale data from other tabs. Hardware
CLAMP_TO_EDGEon the backing texture allows sampling this recycled data.
Suggested Reproduction Steps (Potential)
- From a compromised renderer, create a
cc::PaintOpBufferwith acc::DisplacementMapEffectPaintFilter. - Set the
displacementinput to acc::ArithmeticPaintFilterwithenforce_pm_color=falseand coefficients designed to produce super-luminescent output (e.g.,k={0, -1, 0, 1}). - Target a Ganesh-backed canvas. Use a large
scaleto ensure displacement reaches the texture boundaries. - Dispatch a
RasterCHROMIUMcommand. - After rendering, use
canvas.getContext('2d').getImageData()to read back the canvas content and inspect for leaked GPU memory patterns (e.g., fragments of other tabs’ content).
Suggested Fix
Ensure that the displacement values are clamped to a safe range before being used to offset sampling coordinates. Applying saturate() to the result of unpremul() in the displacement shader would restrict the displacement to the range [-0.5, 0.5] (relative to scale) as assumed by Skia’s bounds analysis.
In third_party/skia/src/sksl/sksl_rt_shader.sksl, modify sk_displacement as follows:
half4 displColor = saturate(unpremul(displMap.eval(coord)));
Evaluated with Chrome root at commit: 1a8d40fc44df2088d5945c0bf53584038aa1614a
Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:
- If you are familiar with the severity guidelines, you may adjust the severity.
- If this is a false positive, and there’s no work to be done, please close as WAI.
- If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.
Data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.