Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactOut of bounds read in Skia
DescriptionOut of bounds read in Skia
ComponentSkia
Bug ClassOOB
Tracker514082801
Fix commitbd3d88bbbcfe (skia) +112/-111
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-02

Files Changed

  • src/sksl/generated/sksl_rt_shader.minified.sksl
  • src/sksl/generated/sksl_rt_shader.unoptimized.sksl
From bd3d88bbbcfef52f755833215bcc1d1ce695bc26 Mon Sep 17 00:00:00 2001
From: Jorge Betancourt <[email protected]>
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 <[email protected]>
Reviewed-by: Michael Ludwig <[email protected]>
---

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"
Loading diff…

Original Bug Report

reported by [email protected]

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.sksl
  • third_party/skia/src/effects/imagefilters/SkDisplacementMapImageFilter.cpp
  • third_party/skia/src/core/SkImageFilterTypes.cpp
  • cc/paint/paint_op_reader.cc
  • third_party/skia/src/gpu/ganesh/image/SkSpecialImage_Ganesh.cpp
  • third_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

  1. Renderer Compromise: The attacker gains control of a renderer and serializes a malicious PaintFilter graph containing a cc::DisplacementMapEffectPaintFilter.
  2. Arithmetic Filter Manipulation: By setting enforce_pm_color = false in an ArithmeticPaintFilter, the attacker produces an intermediate texture containing colors like (1.0, 1.0, 1.0, 0.0).
  3. Bounds Analysis Bypass: Skia’s analyzeBounds logic (in third_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 hardware CLAMP_TO_EDGE instead of strict shader-clamping if it believes the sample coordinates will remain within the texture’s bounds. The exactify() mechanism in GrSurfaceProxy exacerbates this by expanding the perceived image dimensions to the full backing-store dimensions.
  4. Massive Displacement: The sk_displacement shader calculates a displacement of nearly 10,000 * scale. This displacement moves the sampling coordinate far outside the intended logical image bounds and into the uninitialized padding of the scratch texture.
  5. 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_EDGE on the backing texture allows sampling this recycled data.

Suggested Reproduction Steps (Potential)

  1. From a compromised renderer, create a cc::PaintOpBuffer with a cc::DisplacementMapEffectPaintFilter.
  2. Set the displacement input to a cc::ArithmeticPaintFilter with enforce_pm_color=false and coefficients designed to produce super-luminescent output (e.g., k={0, -1, 0, 1}).
  3. Target a Ganesh-backed canvas. Use a large scale to ensure displacement reaches the texture boundaries.
  4. Dispatch a RasterCHROMIUM command.
  5. 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.

View on issue tracker