MySQL offers native support for “spatial data”, extending SQL with a set of geometric types, such as points and polygons, and some functions to query and manipulate them.

Unfortunately, the implementation of this support in early 2013 contained many bugs when I reviewed it, including some serious vulnerabilities. They were all caused by insufficient validation of the WKB (Well-Known Binary) representaton of the geometry objects, which was also the form in which MySQL natively represented the data.

The first vulnerability I found was caused by several missing sanity checks on the length/size of geometry objects. As an example, I’ll discuss the following query, which crashed MySQL 5.6 (as well as earlier versions):

select astext(0x0100000000030000000100000000000010)

This creates a binary string representing a raw geometry object, and tries to convert it to a textual representation. The object is a Polygon (3) which claims to have a single ring with MAX_UINT/16 points. This overflows the length checks in mysql’s spatial functions which multiply the number of points by 16, resulting in 0; in this example, the check in Gis_polygon::get_data_as_wkt. MySQL then calls append_points which tries to read MAX_UINT/16 doubles from the provided buffer, resulting in a guaranteed crash of the entire server process.

There are sanity checks against a ‘max_n_points’ variable to avoid this overflow problem in the init_from_wkb functions for Gis_line_string and Gis_multi_point, but (to my surprise) apparently you could circumvent going via that function by just passing the direct binary representation, as I do above. There were also other potential ways to cause similar overflows, such as in Item_func_spatial_collection::val_str.

I first reported this to Red Hat’s security team, who replied almost immediately. After a short argument about whether it was actually a crash bug (MySQL supplies a “mysqld_safe” script which automatically restarts myqld after it crashes), they suggested I talk to MariaDB and let them handle reporting it upstream to Oracle. MariaDB also responded very quickly, and although they classed it as low priority (since it was just a crash), they proposed a patch within a few days, and applied it to their next release.

Unfortunately, I had to come back and point out in their bug tracker that they had missed some potential crashes (in particular, in the minimum bounding box functions). These DoS vulnerabilities were assigned CVE-2013-1861.

Worse, it turned out there was a much more serious vulnerability lurking in this code. The attacker could control the result of the internal size function for a geometry object in some cases; a simplified snippet from Gis_polygon::get_data_size shows how an attacker could control the return value (note that in practice, there were also other sanity checks which need to be avoided):

   const char *data = m_data;

   n_linear_rings= uint4korr(data); // read size from geometry data
   data += 4;

   while (n_linear_rings--)
   {
     // check whether we have any bytes left to read
     if (no_data(data, 4))
       return GET_SIZE_ERROR;

     // read the number of points from the stream
     data += 4 + uint4korr(data)*POINT_DATA_SIZE;
   }

   // return the calculated size without sanity-checking
   return (uint32) (data - m_data);

The above problem made it possible to make geometry queries read after the end of their buffers, exposing the contents of the heap memory stored after the temporary buffer being used by the query. The following (simplified) code illustrates the problem (from Gis_geometry_collection::geometry_n):

  do
  {
    // this checks whether there are enough bytes left..
    if (no_data(data, WKB_HEADER_SIZE))
      return 1; // and sets an error otherwise
    data += WKB_HEADER_SIZE;

    if ((length = geom->get_data_size()) == GET_SIZE_ERROR)
      return 1;
    // but there is no check here, so we can add any length we want
    data += length;
  } while (--num);

  // this reads length bytes, starting at the original value of data
  result->q_append(data-length, length);

This is a serious issue; heap contents can almost always be used to leak database contents (potentially belonging to other users), and can in some circumstances expose enough information for the server to be entirely compromised by an attacker. This is particularly relevant in this vulnerability, where an attacker could repeat the exploit until they obtain a heap allocation which gives them the information they need, since they can crash the server if neecessary to obtain a different heap layout. The following query is an example of this exploit, using a geometry collection containing a polygon (each point is 16 bytes, so specifying 64k points gives us 1mb of overread):

select geometryn(0x0100000000070000000100000001030000000200000000000000ffff0000, 1)

I reported this issue a few days after the DoS issues were patched. Oracle’s response was limited to a single short e-mail thanking me for keeping them informed and asking me to keep quiet about the vulnerability until they fixed it in a future CPU; unfortunately MariaDB promptly leaked my example queries in their bug tracker (and their second patch), but as far as I’m aware no-one noticed that they exposed a far more serious problem than a simple DoS.

Oracle’s eventual fix (released months later, in July) for these issues was a simple one; they just rewrote the code entirely, replacing it with a more maintainable version which wasn’t vulnerable to any of these problems.