Friday, February 5, 2016

Vulnerability Spotlight: Libgraphite Font Processing Vulnerabilities


Vulnerabilities Discovered by Yves Younan of Cisco Talos.

Talos is releasing an advisory for four vulnerabilities that have been found within the Libgraphite library, which is used for font processing in Linux, Firefox, LibreOffice, and other major applications. The most severe vulnerability results from an out-of-bounds read which the attacker can use to achieve arbitrary code execution. A second vulnerability is an exploitable heap overflow. Finally, the last two vulnerabilities result in denial of service situations. To exploit these vulnerabilities, an attacker simply needs the user to run a Graphite-enabled application that renders a page using a specially crafted font that triggers one of these vulnerabilities. Since Mozilla Firefox versions 11-42 directly support Graphite, the attacker could easily compromise a server and then serve the specially crafted font when the user renders a page from the server (since Graphite supports both local and server-based fonts). 

In this post, we will discuss the following vulnerabilities:
  • CVE-2016-1521 
  • CVE-2016-1522 
  • CVE-2016-1523
  • CVE-2016-1526


Overview


Graphite is a package that can be used to create “smart fonts” capable of displaying writing systems with various complex behaviors. Basically Graphite’s smart fonts are just TrueType Fonts (TTF) with added extensions. The issues that Talos identified include the following:


  • An exploitable denial of service vulnerability exists in the font handling of Libgraphite. A specially crafted font can cause an out-of-bounds read potentially resulting in an information leak or denial of service. 
  • A specially crafted font can cause a buffer overflow resulting in potential code execution. 
  • An exploitable NULL pointer dereference exists in the bidirectional font handling functionality of Libgraphite. A specially crafted font can cause a NULL pointer dereference resulting in a crash. 

In each of the situations an attacker can provide a malicious font to trigger the specified vulnerability.

Out-of-Bounds Read Details


If a malicious font is provided, an out of bounds read can occur while interpreting the opcodes in a font.

The problem occurs when executing the various opcodes in the function directrun in the file directmachine.cpp. At line 85, the interpreter for the opcodes will be executed by performing a goto instruction.

          goto **ip;

If the opcode in particular is a cntxt_item, then ip variable will be advanced by iskip bytes to find the next opcode and continue interpreting the opcodes. This is done at line 369 of opcodes.h.

          ip += iskip;

However there are no checks to ensure that ip remains within the bounds of the memory allocated for the memory to be interpreted (the program variable). In the case of s specially crafted malicious font, the value of iskip can be manipulated to cause an out-of-bound read when the program performs a jump to **ip. This memory is memory that was previously allocated for data. This is a serious vulnerability because the out of bounds read causes the TTF virtual machine to execute code under control of the attacker. In an environment like a browser, javascript can place arbitrary bytecode in memory. Joshua "jduck" Drake showed how to use the TTF virtual machine to create generic memory read/write primitives in his pwn2own java exploit last year. 

Heap Overflow Details


If a malicious font is provided then an arbitrary length buffer overflow can occur when handling context items.

In Code.cpp at lines 158-161, memory is allocated for _code and _data to be the expected maximum size of the code and data loaded from the bytecode. However, the actual size read from the file can be larger than the expected size.

          158:     _code = static_cast(malloc((bytecode_end - bytecode_begin)
          159:                                          * sizeof(instr)));
          160:     _data = static_cast(malloc((bytecode_end - bytecode_begin)
          161:                                            * sizeof(byte)));

At line 181 the function dec.load() is called with pointers to the beginning and end of the bytecode. This function, at lines 230-240, will then load opcodes from the bytecode until the end of the bytecode is reached. It will call emitopcode() at line 238 with the current value of the bytecode that is being handled.

          230:      while (bc < bcend)
          231:      {
          232:         const opcode opc = fetchopcode(bc++);
          233:         if (opc == vm::MAXOPCODE)
          234:             return false;
          235:
          236:         analyse_opcode(opc, reinterpret_cast<const int8 *>(bc));
          237:
          238:         if (!emit_opcode(opc, bc))
          239:             return false;
          240:      }

If the opcode being handled by emitopcode refers to a context item (CNTXTITEM). Then the load function will be called recursively at line 500, with a new bytecodeend that is specified by the bytecode in instrskip.

           if (load(bc, bc + instr_skip))

The size checks to ensure that data isn’t read from outside of the buffer, occur in the validateopcode function at line 548 where it is checked that the new opcode being read will be less than _max.bytecode (which contains the value of bytecodeend).

However, when recursively handling multiple CNTXTITEMs, this means that while data is being copied to _data, it can be read past the new bytecodeend that is specified by the recursive call to load, this will terminate the loop once it reaches there, but in the meantime more data can be read than specified by instrskip, because when validateopcode is called, the current value of bytecode will still be lower than the end of the bytecode. Doing this multiple times, results in the same memory being handled multiple times and allowing data to become larger than bytecode allowing an out-of-bounds write. The same can occur with the code buffer. A proposed fix is to ensure that when returning from a CNTXTITEM, the current value of bytecode minus the begin value of bytecode is larger or equal to datasize and instrcount. A similar check to what is performed via an assert at lines 210 and 211.

Denial of Service Details


The first denial of service issue results from a NULL pointer deference. When the font is loaded via grmakefile_face, the function readFeats will be called at line 190 in the file FeatureMap.cpp in the function SillMap::readFace.
    
          if (!m_FeatureMap.readFeats(face)) return false;

At line 110, in the function readFeats, mnumFeats will be assigned the value 0. This results in a return from the readFeats function at line 115. However the return will return the value true. This results in none of the variables in mFeatureMap being initialized even though the font will load without any errors being returned. If the function grmakeseg is later called on this font, the call to SillMap::cloneFeatures will fail at line 241 because it tries to dereference mFeatureMap.mdefaultFeatures, which is set to 0.

          return new Features (*m_FeatureMap.m_defaultFeatures);

A similar error can occur at line 103 in the same function if the constructor for Table fails.

The second denial of service issue results from an out of bounds read that can not only cause a DoS, but it can also cause a leak of information. When reading an invalid font where the local table size is set to 0, an out of bounds read will occur.

At line 187 in GlyphCache.cpp, the function Loader is defined which loads a number of tables from the font, including the loca table. At line 206 it will then call the function TtfUtil:LocaLookup with arguments that provide the number of glyphs, the loca table and its size:

          if (TtfUtil::LocaLookup(numglyphsgraphics-1, _loca, _loca.size(), _head) == sizet(-1))

At line 1164 in this function (in file TtfUtil.cpp), the loca table will be accessed using the number of glyphs: 

          return be::peek(pLongTable + nGlyphId);

A size check is performed at line 1161, but since the size is set to 0, this will always pass.

          if (nGlyphId < (lLocaSize >> 2) - 1)

This will read out of the bounds of the table by an arbitrary 16-byte number, which will cause a denial of service and could potentially lead to an information leak.

Known Vulnerable Versions:


Libgraphite 2-1.2.4
Firefox 31-42

Conclusion


Finding and disclosing zero-day vulnerabilities responsibly helps improve the overall security of the devices and software people use on a day-to-day basis.  Talos is committed to this effort via developing programmatic ways to identify problems or flaws that could be otherwise exploited by malicious attackers. These developments help secure the platforms and software customers use and also help provide insight into how Cisco can improve its own processes to develop better products. To continually enhance their security posture, customers should always update the software that they use to the most recent versions to take advantage of the latest security updates.

In addition, Talos has released rules that detect attempts to exploit these vulnerabilities to protect our customers. Please note that additional rules may be released at a future date and current rules are subject to change pending additional vulnerability information. For the most current rule information, please refer to your Defense Center, FireSIGHT Management Center or Snort.org.

Snort Rules: 36216,36213, 36217 36225-36228  36385-88

For further zero day or vulnerability reports and information visit:

3 comments:

  1. OpenOffice uses a much older version silgraphite-2.3.1, before the rewrite for the version 2 API. It is not clear if it is vulnerable or not.

    ReplyDelete
  2. Hi Pedro, in our investigation we determined the legacy version of Graphite 2.3.1 does expose similar flaws but it has been out of support since 2009. OpenOffice does not support embedded fonts so we're unaware of an attack vector at this time. LibreOffice does use Graphite 2-1.2.4 and supports embedded fonts

    ReplyDelete
  3. The vuln is fixed in Firefox now: https://bugzilla.mozilla.org/show_bug.cgi?id=1243832

    ReplyDelete

Post a Comment