Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 19491026 authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

We can't be as clever as we'd like.

We had hoped that SQLiteQueryBuilder could enforce some pretty strict
grammar rules, but there are apps depending on the full quirkiness
of what SQLite will allow, which is pretty much "expr" everywhere.

So we shift our strategy from a whitelist-style approach to a
blacklist-style one, where we're willing to let any valid keyword
be used _except_ for ones we know are rooted in abuse, such as
performing SELECT subqueries or relying on SQL injection to blur
the lines between well-defined clauses.

Bug: 146478800, 146482076
Test: atest --test-mapping packages/providers/MediaProvider
Change-Id: Ib57ceab0e229e01177cabd8277f3c989c817b10c
parent 03c71310
Loading
Loading
Loading
Loading
+23 −40
Original line number Diff line number Diff line
@@ -782,59 +782,42 @@ public class SQLiteQueryBuilder {
    private void enforceStrictGrammar(@Nullable String selection, @Nullable String groupBy,
            @Nullable String having, @Nullable String sortOrder, @Nullable String limit) {
        SQLiteTokenizer.tokenize(selection, SQLiteTokenizer.OPTION_NONE,
                this::enforceStrictGrammarWhereHaving);
                this::enforceStrictToken);
        SQLiteTokenizer.tokenize(groupBy, SQLiteTokenizer.OPTION_NONE,
                this::enforceStrictGrammarGroupBy);
                this::enforceStrictToken);
        SQLiteTokenizer.tokenize(having, SQLiteTokenizer.OPTION_NONE,
                this::enforceStrictGrammarWhereHaving);
                this::enforceStrictToken);
        SQLiteTokenizer.tokenize(sortOrder, SQLiteTokenizer.OPTION_NONE,
                this::enforceStrictGrammarOrderBy);
                this::enforceStrictToken);
        SQLiteTokenizer.tokenize(limit, SQLiteTokenizer.OPTION_NONE,
                this::enforceStrictGrammarLimit);
                this::enforceStrictToken);
    }

    private void enforceStrictGrammarWhereHaving(@NonNull String token) {
    private void enforceStrictToken(@NonNull String token) {
        if (isTableOrColumn(token)) return;
        if (SQLiteTokenizer.isFunction(token)) return;
        if (SQLiteTokenizer.isType(token)) return;

        // NOTE: we explicitly don't allow SELECT subqueries, since they could
        // leak data that should have been filtered by the trusted where clause
        // Carefully block any tokens that are attempting to jump across query
        // clauses or create subqueries, since they could leak data that should
        // have been filtered by the trusted where clause
        boolean isAllowedKeyword = SQLiteTokenizer.isKeyword(token);
        switch (token.toUpperCase(Locale.US)) {
            case "AND": case "AS": case "BETWEEN": case "BINARY":
            case "CASE": case "CAST": case "COLLATE": case "DISTINCT":
            case "ELSE": case "END": case "ESCAPE": case "EXISTS":
            case "GLOB": case "IN": case "IS": case "ISNULL":
            case "LIKE": case "MATCH": case "NOCASE": case "NOT":
            case "NOTNULL": case "NULL": case "OR": case "REGEXP":
            case "RTRIM": case "THEN": case "WHEN":
                return;
        }
        throw new IllegalArgumentException("Invalid token " + token);
    }

    private void enforceStrictGrammarGroupBy(@NonNull String token) {
        if (isTableOrColumn(token)) return;
        throw new IllegalArgumentException("Invalid token " + token);
    }

    private void enforceStrictGrammarOrderBy(@NonNull String token) {
        if (isTableOrColumn(token)) return;
        switch (token.toUpperCase(Locale.US)) {
            case "COLLATE": case "ASC": case "DESC":
            case "BINARY": case "RTRIM": case "NOCASE":
            case "LOCALIZED": case "UNICODE":
                return;
            case "SELECT":
            case "FROM":
            case "WHERE":
            case "GROUP":
            case "HAVING":
            case "WINDOW":
            case "VALUES":
            case "ORDER":
            case "LIMIT":
                isAllowedKeyword = false;
                break;
        }
        if (!isAllowedKeyword) {
            throw new IllegalArgumentException("Invalid token " + token);
        }

    private void enforceStrictGrammarLimit(@NonNull String token) {
        switch (token.toUpperCase(Locale.US)) {
            case "OFFSET":
                return;
        }
        throw new IllegalArgumentException("Invalid token " + token);
    }

    /**