From e65122281e7cdc41f27e9a73821a8d6473dd6851 Mon Sep 17 00:00:00 2001
From: David Gow <davidgow@google.com>
Date: Mon, 7 Mar 2016 13:55:32 -0800
Subject: [PATCH] Fix a buffer overrun in extension_based_table_discovery()
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This function uses my_strnncoll() to search for the table-name part of
filenames (len is set to exclude the file extension and anything after '#' to
suport partitioned tables properly). However, len is passed as the length of
both input strings, and from->name might be smaller than len. This leads to a
buffer overflow in my_strnncoll().

To fix this, we calculate the length of the table-name part of from->name, and
pass that length (from_len) to my_strnncoll(). This also obviates the need for
the comparison of from->name[len] to '#' or '.', as my_strnncoll() — with the
correct length — can now correctly handle the case where cur->name is a prefix
of from->name correctly.

Note that there is a small change in behavior where from->name has neither a
'#' nor a '.': previously this would (probably incorrectly) result in otherwise
identical strings being considered different. We do, however, have to handle
this case specially in the calculation of from_len.

ASAN normally doesn't detect this, as the filenames (such as from->name) are
allocated with alloc_root() (via strmake_root), which will attempt to reuse
memory blocks, which are often larger than the string in question. It therefore
only shows up if either the block size matches or a new block is created.

(Note that the documentation for extension_based_table_discovery() does not
match the implementation. This is something best left for another change.)
---
 sql/discover.cc | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)

diff --git a/sql/discover.cc b/sql/discover.cc
index 82648e9..d3c4a9f 100644
--- a/sql/discover.cc
+++ b/sql/discover.cc
@@ -202,12 +202,20 @@ int extension_based_table_discovery(MY_DIR *dirp, const char *ext_meta,
     char *octothorp= strrchr(cur->name + 1, '#');
     char *ext= strchr(octothorp ? octothorp : cur->name, FN_EXTCHAR);
 
+    char *from_octothorp= strrchr(from->name + 1, '#');
+    char *from_ext= strchr(from_octothorp ? from_octothorp : from->name, FN_EXTCHAR);
+    // If from->name has no file extension, from_ext should point to the null
+    // terminator at the end of the buffer so we can calculate from_len.
+    from_ext = from_ext ? from_ext : from->name + strlen(from->name);
+
     if (ext)
     {
       size_t len= (octothorp ? octothorp : ext) - cur->name;
+      size_t from_len= (from_octothorp ? from_octothorp : from_ext) - from->name;
+
+      // Check if from/cur are equal up to the octothorpe / extension.
       if (from != cur &&
-          (my_strnncoll(cs, (uchar*)from->name, len, (uchar*)cur->name, len) ||
-           (from->name[len] != FN_EXTCHAR && from->name[len] != '#')))
+          (my_strnncoll(cs, (uchar*)from->name, from_len, (uchar*)cur->name, len)))
         advance(from, to, cur, skip);
 
       if (my_strnncoll(cs, (uchar*)ext, strlen(ext),
-- 
2.7.0.rc3.207.g0ac5344

