Bug #39277 Creation of table with data and/or index files in data home directory succeeds
Submitted: 5 Sep 2008 18:07 Modified: 7 Mar 2010 18:33
Reporter: Ingo Strüwing Email Updates:
Status: Closed Impact on me:
None 
Category:MySQL Server: DDL Severity:S3 (Non-critical)
Version:5.1.30, 6.0.7 OS:Any (Unix like systems)
Assigned to: Ingo Strüwing CPU Architecture:Any
Tags: Contribution

[5 Sep 2008 18:07] Ingo Strüwing
Description:
I do not know, why this doesn't fail in pushbuild.
But to be cautious, I added my exact system specification.

main.symlink                   [ fail ]

mysqltest: At line 199: query 'CREATE TABLE t1(a INT)
INDEX DIRECTORY='$MYSQLTEST_VARDIR/master-data/mysql'' succeeded - should have failed with errno 1210...

The result from queries just before the failure was:
< snip >
select * from t1;
a
9
select * from t1;
a
99
select * from t1;
a
42
drop table t1;
End of 4.1 tests
SET SESSION keep_files_on_create = TRUE;
CREATE TABLE t1 (a INT) ENGINE MYISAM;
Got one of the listed errors
SET SESSION keep_files_on_create = FALSE;
CREATE TABLE t1 (a INT) ENGINE MYISAM;
DROP TABLE t1;
End of 5.0 tests
CREATE TABLE t1(a INT)
INDEX DIRECTORY='TEST_DIR/master-data/mysql';

How to repeat:
OS: Debian GNU/Linux/x86_64
OS: Debian Sid kernel 2.6.26  SMP PREEMPT
gcc (Debian 4.3.1-8) 4.3.1

bzr branch bzr+ssh://bk-internal.mysql.com/bzrroot/server/mysql-5.1/ mysql-5.1-main
cd mysql-5.1-main
BUILD/compile-pentium64-debug-max --with-debug=full
cd mysql-test
./mysql-test-run.pl symlink

Suggested fix:
On my system, realpath(3) returns ENOENT on $MYSQLTEST_VARDIR/master-data/mysql/t1. The table is to be created and does not exist yet. realpath(3) copies nothing in the result buffer. Hence is doesn't compare equal to the mysqld home directory.

For me this fixed it:

--- sql/sql_table.cc    2008-09-04 08:47:10 +0000
+++ sql/sql_table.cc    2008-09-05 16:24:17 +0000
@@ -3606,15 +3606,28 @@ bool mysql_create_table_no_lock(THD *thd
   create_info->table_existed= 0;               // Mark that table is created
 
 #ifdef HAVE_READLINK
-  if (test_if_data_home_dir(create_info->data_file_name))
   {
-    my_error(ER_WRONG_ARGUMENTS, MYF(0), "DATA DIRECTORY");
-    goto unlock_and_end;
-  }
-  if (test_if_data_home_dir(create_info->index_file_name))
-  {
-    my_error(ER_WRONG_ARGUMENTS, MYF(0), "INDEX DIRECTORY");
-    goto unlock_and_end;
+    size_t dirlen;
+    char   dirpath[FN_REFLEN];
+
+    if (create_info->data_file_name)
+    {
+      dirname_part(dirpath, create_info->data_file_name, &dirlen);
+      if (test_if_data_home_dir(dirpath))
+      {
+        my_error(ER_WRONG_ARGUMENTS, MYF(0), "DATA DIRECTORY");
+        goto unlock_and_end;
+      }
+    }
+    if (create_info->index_file_name)
+    {
+      dirname_part(dirpath, create_info->index_file_name, &dirlen);
+      if (test_if_data_home_dir(dirpath))
+      {
+        my_error(ER_WRONG_ARGUMENTS, MYF(0), "INDEX DIRECTORY");
+        goto unlock_and_end;
+      }
+    }
   }
 
 #ifdef WITH_PARTITION_STORAGE_ENGINE
[8 Sep 2008 8:17] Valeriy Kravchuk
Can't repeat with 5.1.30 on 32-bit SuSE with 2.6.11.4-20a non-SMP kernel, so this is definitely a platfrom-specific problem.
[15 Sep 2008 15:27] Ingo Strüwing
I did a fresh branch from mysql-5.1. It still happens on my machine.

The system specification is correct as far as I can tell. I am using Debian unstable, which is called Sid AFAIK. About once a month I use to dist-upgrade. Last time on 7. September. Uname -a is:
Linux stella 2.6.26 #1 SMP PREEMPT Sun Sep 7 16:15:18 CEST 2008 x86_64 GNU/Linux
After the mentioned dist-upgrade, the compiler version is now gcc version 4.3.2 (Debian 4.3.2-1).

Since I seem to be the only one with the problem, you may set it back to "need feedback" and let it close automatically.

If I find some time, I'll dig deeper and compare with another system on the debug level.
[16 Sep 2008 11:19] Ingo Strüwing
Since nobody but me could repeat it, I dug deeper and found the probable cause:

On my machine, I have a tmpfs on /tmp. I want to speed the test by running it there. But I don't want to copy the whole work tree there.

So I create a directory under /tmp and link it symbolically to mysql-test/var.

mysqld correctly evaluates it's real data home dir as /tmp/...
The create table statement tries to figure out if the requested path for the table file (including the table base name, but incomprehensibly no extension) lies inside the data home dir. On my machine realpath(3) does not find the path and gives back ENOENT (no such file). my_realpath() then copies the requested path verbatim. test_if_data_home_dir() does then conclude that the path starting with $HOME/... (where the test runs) does not match the real data home dir (/tmp/...). Hence it happily succeeds and creates the table. The expected error does not happen, so the test case fails.

My suggested fix, to strip off the table base name, makes realpath(3) find the file (the directory in this case) and return the real path, starting as /tmp/. The following compare in test_if_data_home_dir() does then match positively with the real data home dir. Error 1210 is reported as expected. The test case then succeeds.
[25 Oct 2008 18:44] Sergey Vojtovich
Fails constantly on Fedora 9/Core 2 Duo: mysql-test/mtr --mem symlink.

Just wonder why this potential (=probably needs specific setup) security hole was triaged to have negligible impact.
[30 Oct 2008 8:39] Mattias Jonsson
I can repeat it on Mac OS X 10.5 too
./mtr symlink succeeds
./mtr --mem symlink fails

Ingo's analysis seems correct.
[14 Nov 2008 8:15] Ingo Strüwing
Taking this as it is very annoying that it makes every test run fail in 5.1 and 6.0 on my system.
[14 Nov 2008 12:05] Bugs System
A patch for this bug has been committed. After review, it may
be pushed to the relevant source trees for release in the next
version. You can access the patch from:

  http://lists.mysql.com/commits/58775

2712 Ingo Struewing	2008-11-14
      Bug#39277 - symlink.test fails on Debian
      
      When the data directory contained a symbolic link to another
      file system, and the DATA or INDEX DIRECTORY clause of a
      CREATE TABLE statement referred to a subdirectory of the data
      directory, this was accepted.
      
      The problem was the use of a table file path name, which included
      the table name without an extension, for the comparison against
      the data directory path name. This was almost always a
      non-existent file. The internal algorithm failed to resolve
      symbolic links for non-existent files. So we compared unrelated
      path names.
      
      Fixed by truncating the table name from the path before resolving
      symlinks.
[14 Nov 2008 14:54] Mattias Jonsson
OK to push by my, patch verified to work on osx.
[19 Nov 2008 14:01] Bugs System
A patch for this bug has been committed. After review, it may
be pushed to the relevant source trees for release in the next
version. You can access the patch from:

  http://lists.mysql.com/commits/59231

2712 Ingo Struewing	2008-11-19
      Bug#39277 - symlink.test fails on Debian
      
      When the data directory contained a symbolic link to another
      file system, and the DATA or INDEX DIRECTORY clause of a
      CREATE TABLE statement referred to a subdirectory of the data
      directory by using another path, this was accepted.
      
      The problem was the use of a table file path name, which included
      the table name without an extension, for the comparison against
      the data directory path name. This was almost always a
      non-existent file. The internal algorithm failed to resolve
      symbolic links for non-existent files. So we compared unrelated
      path names.
      
      Fixed by doing a second comparison with a path name that has
      stripped off the last path element. In the case above it makes the
      directory for the table. If this is also a non-existent path, the
      creation of the table will fail anyway.
[19 Nov 2008 15:04] Ingo Strüwing
Please re-triage this for version 5.1. This bug is very annoying in every development environment where one has the test directory symlinked to a memory file system. It makes every run of the test suite fail.

Also one can suspect that there could be a security risk connected to the problem.

Regards, Ingo
[21 Nov 2008 8:20] Ingo Strüwing
Set back to verified to get it off my list as it shouldn't be fixed now.
[22 Nov 2008 15:23] Ingo Strüwing
I have been told that this is not a security issue. I agree that I did
not find a way to get at other user's data or to modify them. However,
it might be arguable if denial of service is a security issue.

I am pretty sad that I am not allowed to fix it.

Problems with symbolic links
============================

Nomenclature
------------

'file' in this document means engine files, in particular .MYD and
       .MYI, but not .frm files.

'datadir' is the MySQL server's data home directory, in which the
          databases reside in form of directories.

'in datadir' is in one of the database directories in datadir.
             When/if we do checks, we also include the data home
             directory itself and arbitrarily deep stacked
             subdirectories of it.

'with[out] symlinks' tables that are created or altered with a
                     {DATA|INDEX} DIRECTORY clause use symbolic
                     links to point at their files.
                     If datadir itself is a symlinked path,
                     some of the below problems could apply to
                     all tables that have their files in datadir.
                     But this is not the main topic here.

Summary
-------

Using symbolic links you cannot:

- Open tables, which files are symlinked into datadir.
  This prevents access to data of tables in datadir,
  other than through the normal path with privilege checking.
  No read or write of other user's data is possible.

- Create tables that use existing files.

- Prevent creation of a table that does not use symlinks.
  Create table without symlink does not care about existing files.

Using symbolic links you can:

- Create tables with their files in datadir.
  But you cannot open them. See above.

- Drop tables with their files in datadir.
  This can make a DoS attack if the table was created with
  symlinks before a table without symlinks uses the same files.

- Drop every existing table in datadir with just CREATE and DROP
  privilege in an arbitrary database, filesystem access
  somewhere on the server machine, and read access to the data
  home directory itself.
  This can also make a DoS attack. After creation of a directory
  somewhere outside of datadir, and creation of a table with the
  same name as the "target" table, one could remove the files and
  directory, and replace the directory with a symbolic link to
  the "target" database. Dropping the table would remove the files
  of the "target" table.

Comments
--------

The initial bug report was about a failing test case. It turned out to
happen due to the problem that symlinked tables can have their files
in datadir, while the test case assumes they cannot.

This is not a big deal as this fact in itself does not hurt. A such
created table cannot be opened and cannot prevent another table with the
same name to be created. So we could even change the test case so that
it accepts success for symlinked tables as well as failure for
non-symlinked tables. But I fear that I won't be allowed to push even
this fix.

Since this doesn't seem to be the real problem any more, and as it
doesn't happen on Debian only, I changed the synopsis.

The big problem for me is that every run of a test suite fails in my
environment due to this problem. This prevents me to combine several
builds and tests in a script as I cannot continue automatically with a
failure. Detecting and excluding the symlink test case seems difficult
and error prone. The only feasible way is to abstain from symlinking
mysql-test/var into a temporary file system. Unfortunately this
increases the testing time from 1h40min to 2h20min on my machine. :-(

Anyway, during my investigation, I stumbled over a, say, unpleasant
behavior of DROP TABLE. It does not seem to check if the table is
symlinked into datadir. So it happily removes the table files, even from
other tables in datadir.

For the curious I'll attach a test file that supports my findings.
[22 Nov 2008 15:24] Ingo Strüwing
test file

Attachment: bug39277-2.test (application/octet-stream, text), 5.50 KiB.

[22 Nov 2008 15:24] Ingo Strüwing
test result

Attachment: bug39277-2.result (application/octet-stream, text), 3.97 KiB.

[24 Nov 2008 8:18] Ingo Strüwing
I was in error regarding the requirements for dropping any table. It is not required to have read access to the data home directory of the server to set a symbolic link to a database directory. One can create a symbolic link with arbitrary contents. One just needs to know the path. But one does not need to have any access to it. The server process, which has access to the full path to the database directories can use that link, if the path is correct.

So the fixed capability statement for symlinked tables is this:

- Drop every (any) existing table in datadir with just CREATE and DROP
  privilege in an arbitrary database and filesystem write access
  somewhere on the server machine, e.g. in /tmp.
[24 Nov 2008 14:56] Ingo Strüwing
I received approval from Lars Thalmann to push it into 6.0.

The reviewers agreed that the first fix, from 14 Nov, is correct and should be used. So I set the bug report back to "Patch approved".

This fixes the problem that was detected initially. I changed the synopsis again to reflect better, what this fix is about. It does also implicitly fix the problem that one can remove another table's files if one created a like named table with its data/index directory in that table's database. One can now no longer create such table. The drop problem with externally modified symbolic links is not fixed with this patch. I split it out to the new bug report Bug#40980 (Drop table can remove another MyISAM table's data and index files).

Note that this will be fixed in 6.0 as it was rated too much of a "corner case" and too less of a security problem to be included into 5.1.
[24 Nov 2008 19:02] Bugs System
A patch for this bug has been committed. After review, it may
be pushed to the relevant source trees for release in the next
version. You can access the patch from:

  http://lists.mysql.com/commits/59711

2744 Ingo Struewing	2008-11-24
      Bug#39277 - symlink.test fails on Debian
      
      When the data directory contained a symbolic link to another
      file system, and the DATA or INDEX DIRECTORY clause of a
      CREATE TABLE statement referred to a subdirectory of the data
      directory, this was accepted.
      
      The problem was the use of a table file path name, which included
      the table name without an extension, for the comparison against
      the data directory path name. This was almost always a
      non-existent file. The internal algorithm failed to resolve
      symbolic links for non-existent files. So we compared unrelated
      path names.
      
      Fixed by truncating the table name from the path before resolving
      symlinks. If this is also a non-existent path, the creation of
      the table will fail anyway.
[25 Nov 2008 12:14] Ingo Strüwing
Queued to mysql-6.0-bugteam.
[8 Dec 2008 11:33] Bugs System
Pushed into 6.0.9-alpha  (revid:ingo.struewing@sun.com-20081124190154-cg4t4ewa6cgr4hsx) (version source revid:satya.bn@sun.com-20081126062231-h6os2axygjw27wb4) (pib:5)
[11 Dec 2008 14:23] Paul DuBois
Noted in 6.0.9 changelog.

When the data directory contained a symbolic link to another file
system, and the DATA DIRECTORY or INDEX DIRECTORY clause of a CREATE
TABLE statement referred to a subdirectory of the data directory,
this was accepted, when for security reasons it should be rejected.
[26 Nov 2009 19:24] Bugs System
A patch for this bug has been committed. After review, it may
be pushed to the relevant source trees for release in the next
version. You can access the patch from:

  http://lists.mysql.com/commits/91835

2947 Magne Mahre	2009-11-26
      Bug#39277 - symlink.test fails on Debian
            
      When the data directory contained a symbolic link to another
      file system, and the DATA or INDEX DIRECTORY clause of a
      CREATE TABLE statement referred to a subdirectory of the data
      directory, this was accepted.
            
      The problem was the use of a table file path name, which included
      the table name without an extension, for the comparison against
      the data directory path name. This was almost always a
      non-existent file. The internal algorithm failed to resolve
      symbolic links for non-existent files. So we compared unrelated
      path names.
            
      Fixed by truncating the table name from the path before resolving
      symlinks. If this is also a non-existent path, the creation of
      the table will fail anyway.
      
      Backport to 5.6.0.    6.0-codebase revid: 2599.60.1
     @ sql/sql_table.cc
        Changed test for data directory to exclude the table name from the
        comparison.
[8 Dec 2009 6:45] Bugs System
Pushed into 6.0.14-alpha (revid:alik@ibmvm-20091208064346-e7bavsqpl86x26dy) (version source revid:alik@ibmvm-20091208064346-e7bavsqpl86x26dy) (merge vers: 6.0.14-alpha) (pib:13)
[8 Dec 2009 6:46] Bugs System
Pushed into 5.6.0-beta (revid:alik@ibmvm-20091207060840-4j0ks51bxwcsln6y) (version source revid:magne.mahre@sun.com-20091126192408-5jdj7ogr662k0poi) (merge vers: 5.6.0-beta) (pib:13)
[8 Dec 2009 16:27] Paul DuBois
Noted in 5.6.0 changelog.

Already fixed in 6.0.x.
[21 Dec 2009 9:40] Tomas Hoger
Is this planned to be fixed in 5.1.x too?  Thanks!
[6 Mar 2010 10:57] Bugs System
Pushed into 5.5.3-m3 (revid:alik@sun.com-20100306103849-hha31z2enhh7jwt3) (version source revid:vvaintroub@mysql.com-20091210104731-27nl9weemor51ige) (merge vers: 5.6.0-beta) (pib:16)
[7 Mar 2010 18:33] Paul DuBois
Moved 5.6.0 changelog entry to 5.5.3.
[22 Apr 2010 6:52] James Day
This is CVE CVE-2008-7247(Candidate).

Mitigation:

Normal database users are not expected to have the capability to create symlinks on the database server and in secure systems are expected to have no access to the database server filesystem and not even a login account on it.

If you believe you are vulnerable, you should fix the root cause, access to the filesystem of the database server.