laravel / framework

The Laravel Framework.
https://laravel.com
MIT License
32.52k stars 11.02k forks source link

Dot in database table prefix causing php 'Segmentation fault' #28307

Closed dkuzevanov closed 5 years ago

dkuzevanov commented 5 years ago

Description:

Something goes wrong while using tables prefix with dot (when trying to get data from tables like sys.server_principals via model). As a result we got Segmentation fault. The gdb showed that last call is mb_strpos:

[root@centos ums]# gdb php
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-114.el7
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /opt/remi/php73/root/usr/bin/php...Reading symbols from /usr/lib/debug/opt/remi/php73/root/usr/bin/php.debug...done.
done.
(gdb) run artisan tinker
Starting program: /usr/bin/php artisan tinker
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
Psy Shell v0.9.9 (PHP 7.3.4 ā€” cli) by Justin Hileman
>>> App\Models\Special\User::where('name', 'sa')->first();

Program received signal SIGSEGV, Segmentation fault.
mbfl_strpos (haystack=haystack@entry=0x7fffff7ff680, needle=needle@entry=0x7fffff7ff6a0, offset=0, reverse=reverse@entry=0) at /usr/src/debug/php-7.3.4/ext/mbstring/libmbfl/mbfl/mbfilter.c:887
887                             jtbl[i] = needle_u8_len + 1;
(gdb) bt full
#0  mbfl_strpos (haystack=haystack@entry=0x7fffff7ff680, needle=needle@entry=0x7fffff7ff6a0, offset=0, reverse=reverse@entry=0) at /usr/src/debug/php-7.3.4/ext/mbstring/libmbfl/mbfl/mbfilter.c:887
        jtbl = <error reading variable jtbl (Cannot access memory at address 0x7fffff7fee10)>
        needle_u8_len = 2
        i = <optimized out>
        q = <optimized out>
        e = <optimized out>
        needle_u8_val = 0x555555c70368 "->"
        p = <optimized out>
        haystack_u8_val = 0x7fffdd690898 "sys.sys"
        result = 18446744073709551615
        _haystack_u8 = <error reading variable _haystack_u8 (Cannot access memory at address 0x7fffff7fedd0)>
        _needle_u8 = <error reading variable _needle_u8 (Cannot access memory at address 0x7fffff7fedf0)>
        haystack_u8 = 0x7fffff7ff680
        needle_u8 = 0x7fffff7ff6a0
        u8_tbl = 0x7fffe6864980 <mblen_table_utf8> '\001' <repeats 194 times>, "\002\002\002\002\002\002"...
#1  0x00007fffe677b1cd in zif_mb_strpos (execute_data=0x7fffdd655ec0, return_value=0x7fffdd655ea0) at /usr/src/debug/php-7.3.4/ext/mbstring/mbstring.c:2358
        offset = 0
        haystack = {no_language = mbfl_no_language_neutral, encoding = 0x7fffe6a8dac0 <mbfl_encoding_utf8>, val = 0x7fffdd690898 "sys.sys", len = 7}
        needle = {no_language = mbfl_no_language_neutral, encoding = 0x7fffe6a8dac0 <mbfl_encoding_utf8>, val = 0x555555c70368 "->", len = 2}
        enc_name = 0x0
        enc_name_len = 2305843009213693955
        n = <optimized out>
#2  0x00005555558cfd80 in ZEND_DO_FCALL_BY_NAME_SPEC_RETVAL_USED_HANDLER () at /usr/src/debug/php-7.3.4/Zend/zend_vm_execute.h:892
        call = <optimized out>
        fbc = <optimized out>
        ret = <optimized out>
#3  execute_ex (ex=0x7) at /usr/src/debug/php-7.3.4/Zend/zend_vm_execute.h:55481
No locals.
#4  0x0000555555834bd3 in zend_call_function (fci=fci@entry=0x7fffff7ff900, fci_cache=<optimized out>, fci_cache@entry=0x7fffff7ff8e0) at /usr/src/debug/php-7.3.4/Zend/zend_execute_API.c:756
        call_via_handler = 0
        current_opline_before_exception = 0x0
        i = <optimized out>
        call = 0x7fffdd655df0
        dummy_execute_data = {opline = 0x3, call = 0x3, return_value = 0x3, func = 0x3, This = {value = {lval = 3, dval = 1.4821969375237396e-323, counted = 0x3, str = 0x3, arr = 0x3, obj = 0x3, res = 0x3, ref = 0x3, ast = 0x3,
              zv = 0x3, ptr = 0x3, ce = 0x3, func = 0x3, ww = {w1 = 3, w2 = 0}}, u1 = {v = {type = 0 '\000', type_flags = 144 '\220', u = {call_info = 25532, extra = 25532}}, type_info = 1673302016}, u2 = {next = 3101083081,
              cache_slot = 3101083081, opline_num = 3101083081, lineno = 3101083081, num_args = 3101083081, fe_pos = 3101083081, fe_iter_idx = 3101083081, access_flags = 3101083081, property_guard = 3101083081,
              constant_flags = 3101083081, extra = 3101083081}}, prev_execute_data = 0x3, symbol_table = 0x7fffff7ff900, run_time_cache = 0x7fffff7ff8e0}
        fci_cache_local = {function_handler = 0x3, calling_scope = 0x3, called_scope = 0x3, object = 0x0}
        func = <optimized out>
#5  0x000055555576f904 in zif_array_map (execute_data=0x7fffdd6558b0, return_value=0x7fffdd655860) at /usr/src/debug/php-7.3.4/ext/standard/array.c:6225
        arrays = 0x7fffdd655910
        n_arrays = 2
        result = {value = {lval = 140736908031952, dval = 6.9533271360506483e-310, counted = 0x7fffdd68f7d0, str = 0x7fffdd68f7d0, arr = 0x7fffdd68f7d0, obj = 0x7fffdd68f7d0, res = 0x7fffdd68f7d0, ref = 0x7fffdd68f7d0,
            ast = 0x7fffdd68f7d0, zv = 0x7fffdd68f7d0, ptr = 0x7fffdd68f7d0, ce = 0x7fffdd68f7d0, func = 0x7fffdd68f7d0, ww = {w1 = 3714643920, w2 = 32767}}, u1 = {v = {type = 0 '\000', type_flags = 0 '\000', u = {call_info = 0,
                extra = 0}}, type_info = 0}, u2 = {next = 21845, cache_slot = 21845, opline_num = 21845, lineno = 21845, num_args = 21845, fe_pos = 21845, fe_iter_idx = 21845, access_flags = 21845, property_guard = 21845,
            constant_flags = 21845, extra = 21845}}
        fci = {size = 56, function_name = {value = {lval = 140736908054144, dval = 6.9533271371470788e-310, counted = 0x7fffdd694e80, str = 0x7fffdd694e80, arr = 0x7fffdd694e80, obj = 0x7fffdd694e80, res = 0x7fffdd694e80,
              ref = 0x7fffdd694e80, ast = 0x7fffdd694e80, zv = 0x7fffdd694e80, ptr = 0x7fffdd694e80, ce = 0x7fffdd694e80, func = 0x7fffdd694e80, ww = {w1 = 3714666112, w2 = 32767}}, u1 = {v = {type = 8 '\b', type_flags = 1 '\001', u = {
                  call_info = 0, extra = 0}}, type_info = 264}, u2 = {next = 0, cache_slot = 0, opline_num = 0, lineno = 0, num_args = 0, fe_pos = 0, fe_iter_idx = 0, access_flags = 0, property_guard = 0, constant_flags = 0,
              extra = 0}}, retval = 0x7fffff7ff8c0, params = 0x7fffdd690860, object = 0x7fffe1159690, no_separation = 0 '\000', param_count = 2}
        fci_cache = {function_handler = 0x7fffdd694eb8, calling_scope = 0x7fffe000cce0, called_scope = 0x7fffe000cce0, object = 0x7fffe1159690}
        i = <optimized out>
        k = 0
        maxlen = 2
#6  0x00005555558cfd80 in ZEND_DO_FCALL_BY_NAME_SPEC_RETVAL_USED_HANDLER () at /usr/src/debug/php-7.3.4/Zend/zend_vm_execute.h:892
        call = <optimized out>
        fbc = <optimized out>
        ret = <optimized out>
#7  execute_ex (ex=0x7) at /usr/src/debug/php-7.3.4/Zend/zend_vm_execute.h:55481
No locals.

By debug i found place in the code that corresponds gdb trace:

    /**
     * Determine if the given string is a JSON selector.
     *
     * @param  string  $value
     * @return bool
     */
    protected function isJsonSelector($value)
    {
        $return = Str::contains($value, '->');
        return $return;
    }

Steps To Reproduce:

  1. Make any database connection with table prefix with dot;
  2. Make any model corresponds to this connection;
  3. Try to get eny model from database via Illuminate\Database\Eloquent\Builder.
dkuzevanov commented 5 years ago

Looks like uncontrolled recursion in \Illuminate\Database\Query\Grammars\Grammar::wrap() method.

driesvints commented 5 years ago

@staudenmeir didn't we experience this issue before?

staudenmeir commented 5 years ago

@driesvints This is a different issue. It's caused by Laravel and affects all databases. I'll look into it.

driesvints commented 5 years ago

@staudenmeir thanks!

manan-jadhav commented 5 years ago

This points to a bigger problem that Laravel cannot support tables with . in their names, although it is supported by SQL Databases.

staudenmeir commented 5 years ago

@CurosMJ We can't really support dots in table names because we can't differentiate between database.table and table.with.dots.

manan-jadhav commented 5 years ago

@staudenmeir Yes that's true.

Also, to correct my previous comment, MySQL does not allow table and database names with . in them. @dkuzevanov Does SQL Server allow this?

staudenmeir commented 5 years ago

@CurosMJ MySQL does support it if you quote the table name:

select * from `table.with.dots`
staudenmeir commented 5 years ago

@driesvints I couldn't find a good solution and I suggest that we close this as "not supported".

It's an extreme edge case and not really good practice to have table names with dots. Even with a fix, only the table prefix could contain dots but not the "main" table name (https://github.com/laravel/framework/issues/28307#issuecomment-491324905).

driesvints commented 5 years ago

Yeah, I think this is going to be impossible to fix. But we could change it in a major release if enough people would want it to be supported. Thanks for looking into it šŸ‘

bAngerman commented 1 year ago

It feels somewhat hacky but I was able to pass in a table with a dot in the name by providing an \Illuminate\Database\Query\Expression as the argument. Eg.

$table = (new Expression('`tablename.with.dots`'));
dd( DB::table($table)->count() ); // works
BlitzCry commented 8 months ago

It feels somewhat hacky but I was able to pass in a table with a dot in the name by providing an \Illuminate\Database\Query\Expression as the argument. Eg.

$table = (new Expression('`tablename.with.dots`'));
dd( DB::table($table)->count() ); // works

It might be long overdue but I second your answer.

Looks like by passing it as an expression it bypasses the wrap method image

Which somehow ends in an infinite callback between the wrapTable and wrapSegments methods. This is all started by the compileFrom method. image

@staudenmeir or @bAngerman I did not have much time to investigate but I think this might be a bug, mind looking into it? I will too when I will have time.

BlitzCry commented 8 months ago

Edit: Laravel 11 looks like it supports it, lol Sauce: https://github.com/laravel/framework/blob/6089f679d6d29e6071a6448ed5e96de02e57fedb/src/Illuminate/Database/Grammar.php#L60

Issue explanation for older versions:

I came back after exploring what is happening, it looks like it gets stuck in an infinite loop and eventually it Seg faults.

Here is the simplified flow of what is happening if your prefix has dots in it "prefix.prefix.blabla"

image

Sauce: https://github.com/laravel/framework/blob/40c32ce124900b801b8693f65e37c67c8f4747e1/src/Illuminate/Database/Grammar.php#L80