Skip to content

sqlite PDO::quote silently corrupts strings with null bytes #13952

Closed

Description

@divinity76

Description

The following code:

<?php

declare(strict_types=1);

$db = new \PDO('sqlite::memory:', null, null, array(
    \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
    \PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC,
    \PDO::ATTR_EMULATE_PREPARES => false,
));
var_dump($db->quote("foo\x00bar"));

Resulted in this output:

string(5) "'foo'"

But I expected this output instead:

string(17) "x'666f6f00626172'"

-or-

Fatal error: Uncaught ValueError: PDO::quote(): Argument #1 ($string) must not contain any null bytes

(but preferably the former)

  • but SILENTLY CORRUPTING THE STRING is certainly not what I expected, and not the appropriate course of action.

PHP Version

PHP 8.3.4

Operating System

Ubuntu 24.04-beta

Activity

  1. ndossche commented on Apr 12, 2024

    @ndossche
    Member

    Lovely, the implementation even has a comment above it stating it is broken:

    /* NB: doesn't handle binary strings... use prepared stmts for that */
    static zend_string* sqlite_handle_quoter(pdo_dbh_t *dbh, const zend_string *unquoted, enum pdo_param_type paramtype)
    {
    char *quoted;
    if (ZSTR_LEN(unquoted) > (INT_MAX - 3) / 2) {
    return NULL;
    }
    quoted = safe_emalloc(2, ZSTR_LEN(unquoted), 3);
    /* TODO use %Q format? */
    sqlite3_snprintf(2*ZSTR_LEN(unquoted) + 3, quoted, "'%q'", ZSTR_VAL(unquoted));
    zend_string *quoted_str = zend_string_init(quoted, strlen(quoted), 0);
    efree(quoted);
    return quoted_str;
    }

  2. divinity76 commented on Apr 12, 2024

    @divinity76
    ContributorAuthor

    Any idea what (INT_MAX - 3) / 2 is supposed to be? It's 73 million bytes higher than SQLite's SQLITE_MAX_SQL_LENGTH default value,

    ((0x7FFFFFFF - 3) / 2) - 1,000,000,000 = 73741822

    Maybe PHP max string length? Idk

  3. ndossche commented on Apr 12, 2024

    @ndossche
    Member

    It's protection against integer overflow causing a buffer overflow.

  4. mvorisek commented on Apr 12, 2024

    @mvorisek
    Contributor
  5. ndossche commented on Apr 12, 2024

    @ndossche
    Member

    I think the best solution is to just implement this thing ourselves, looking at the sqlite code it doesn't seem difficult at all.

  6. added a commit that references this issue on Apr 12, 2024
    bfd329c
  7. divinity76 commented on Apr 12, 2024

    @divinity76
    ContributorAuthor

    proposed fix: #13953

  8. mvorisek commented on Apr 12, 2024

    @mvorisek
    Contributor

    I meant concat using x'00' might be better as otherwise the payload/used bandwith can be 2x as high.

  9. ndossche commented on Apr 12, 2024

    @ndossche
    Member

    I agree with @mvorisek
    This is what I was writing as a prototype: https://gist.github.com/nielsdos/1fc34c55b2ae7d24e7b2759ae26ce586
    It seems to work but I need to do extra tests and cleanup. I'll take a look at @divinity76 's approach now

  10. divinity76 commented on Apr 12, 2024

    @divinity76
    ContributorAuthor

    I actually tried doing concat-optimizations, and was very surprised to learn that SQLite's concat operator does not support nulls:

    $ sqlite3 '' "SELECT LENGTH('foo' || x'00' || 'bar')"
    3
    $ sqlite3 '' "SELECT LENGTH(printf('%s%s%s', 'foo', x'00', 'bar'));"
    6
    

    @nielsdos have you actually tested your approach with null bytes? it seems to me like your approach tries using the || operator, which (to my surprise) does not support null bytes

  11. 2 remaining items

  12. SakiTakamachi commented on Apr 15, 2024

    @SakiTakamachi
    Member

    I have reported it to sqlite along with the reproduction code.
    https://sqlite.org/forum/forumpost/e6d6779ed2

  13. added a commit that references this issue on Apr 13, 2025
    c1b07f4
  14. added a commit that references this issue on Apr 15, 2025
    0a10f6d
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions