CVE-2017-5465 Firefox ConvolvePixel 函数内越界读

基本信息

  • 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); // 默认设置为(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

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) {
// According to spec, A negative or zero value is an error. See link below for details.
// https://www.w3.org/TR/SVG/filters.html#feConvolveMatrixElementKernelUnitLengthAttribute
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); // eConvolveMatrixKernelUnitLength = 0x18
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, // aSourceStride=4d0
int32_t aX, int32_t aY, // 0x12d, 0x83
const int32_t *aKernel,
int32_t aBias, int32_t shiftL, int32_t shiftR,
bool aPreserveAlpha,
int32_t aOrderX, int32_t aOrderY, // 3, 3
int32_t aTargetX, int32_t aTargetY, // 1, 1
CoordType aKernelUnitLengthX, // 1
CoordType aKernelUnitLengthY) // -1
{
int32_t sum[4] = {0, 0, 0, 0};
int32_t offsets[4] = { B8G8R8A8_COMPONENT_BYTEOFFSET_R, // 2
B8G8R8A8_COMPONENT_BYTEOFFSET_G, // 1
B8G8R8A8_COMPONENT_BYTEOFFSET_B, // 0
B8G8R8A8_COMPONENT_BYTEOFFSET_A }; // 3
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; // -1
for (int32_t x = 0; x < aOrderX; x++) {
CoordType sampleX = aX + (x - aTargetX) * aKernelUnitLengthX; // 1
for (int32_t i = 0; i < channels; i++) {
sum[i] += aKernel[aOrderX * y + x] *
ColorComponentAtPoint(aSourceData, aSourceStride, // 4d0
sampleX, sampleY, 4, offsets[i]); // sampleY * aSourceStride + sampleX * 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); // 卷积计算 mKernelSize 猜测是 kernelMatrix 属性

首先是最后循环的一个卷积计算,传入的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); // 构建source map
......
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");

// AlignedArray uses cmalloc to zero mem for a fast path.
mArray.Realloc(/* actually an object count */ 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 或者是因为排序24
link

内存分配
0x4b0 * 0x96 (1, 1)

0x4b0 * 0x84 (1, -1)

(10, -10)
0x500 * 0x500

Reference

LTE RRCConnectionRelease Redirect问题分析 科学和伪科学
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×