8000 Add security checks to selectivity estimation functions · postgrespro/postgres@e2d4ef8 · GitHub
[go: up one dir, main page]

Skip to content
  • Commit e2d4ef8

    Browse files
    committed
    Add security checks to selectivity estimation functions
    Some selectivity estimation functions run user-supplied operators over data obtained from pg_statistic without security checks, which allows those operators to leak pg_statistic data without having privileges on the underlying tables. Fix by checking that one of the following is satisfied: (1) the user has table or column privileges on the table underlying the pg_statistic data, or (2) the function implementing the user-supplied operator is leak-proof. If neither is satisfied, planning will proceed as if there are no statistics available. At least one of these is satisfied in most cases in practice. The only situations that are negatively impacted are user-defined or not-leak-proof operators on a security-barrier view. Reported-by: Robert Haas <robertmhaas@gmail.com> Author: Peter Eisentraut <peter_e@gmx.net> Author: Tom Lane <tgl@sss.pgh.pa.us> Security: CVE-2017-7484
    1 parent eb61136 commit e2d4ef8

    File tree

    7 files changed

    +377
    -32
    lines changed

    7 files changed

    +377
    -32
    lines changed

    doc/src/sgml/planstats.sgml

    Lines changed: 61 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -582,4 +582,65 @@ EXPLAIN (ANALYZE, TIMING OFF) SELECT COUNT(*) FROM t GROUP BY a, b;
    582582

    583583
    </sect2>
    584584
    </sect1>
    585+
    586+
    <sect1 id="planner-stats-security">
    587+
    <title>Planner Statistics and Security</title>
    588+
    589+
    <para>
    590+
    Access to the table <structname>pg_statistic</structname> is restricted to
    591+
    superusers, so that ordinary users cannot learn about the contents of the
    592+
    tables of other users from it. Some selectivity estimation functions will
    593+
    use a user-provided operator (either the operator appearing in the query or
    594+
    a related operator) to analyze the stored statistics. For example, in order
    595+
    to determine whether a stored most common value is applicable, the
    596+
    selectivity estimator will have to run the appropriate <literal>=</literal>
    597+
    operator to compare the constant in the query to the stored value.
    598+
    Thus the data in <structname>pg_statistic</structname> is potentially
    599+
    passed to user-defined operators. An appropriately crafted operator can
    600+
    intentionally leak the passed operands (for example, by logging them
    601+
    or writing them to a different table), or accidentally leak them by showing
    602+
    their values in error messages, in either case possibly exposing data from
    603+
    <structname>pg_statistic</structname> to a user who should not be able to
    604+
    see it.
    605+
    </para>
    606+
    607+
    <para>
    608+
    In order to prevent this, the following applies to all built-in selectivity
    609+
    estimation functions. When planning a query, in order to be able to use
    610+
    stored statistics, the current user must either
    611+
    have <literal>SELECT</literal> privilege on the table or the involved
    612+
    columns, or the operator used must be <literal>LEAKPROOF</literal> (more
    613+
    accurately, the function that the operator is based on). If not, then the
    614+
    selectivity estimator will behave as if no statistics are available, and
    615+
    the planner will proceed with default or fall-back assumptions.
    616+
    </para>
    617+
    618+
    <para>
    619+
    If a user does not have the required privilege on the table or columns,
    620+
    then in many cases the query will ultimately receive a permission-denied
    621+
    error, in which case this mechanism is invisible in practice. But if the
    622+
    user is reading from a security-barrier view, then the planner might wish
    623+
    to check the statistics of an underlying table that is otherwise
    624+
    inaccessible to the user. In that case, the operator should be leak-proof
    625+
    or the statistics will not be used. There is no direct feedback about
    626+
    that, except that the plan might be suboptimal. If one suspects that this
    627+
    is the case, one could try running the query as a more privileged user,
    628+
    to see if a different plan results.
    629+
    </para>
    630+
    631+
    <para>
    632+
    This restriction applies only to cases where the planner would need to
    633+
    execute a user-defined operator on one or more values
    634+
    from <structname>pg_statistic</structname>. Thus the planner is permitted
    635+
    to use generic statistical information, such as the fraction of null values
    636+
    or the number of distinct values in a column, regardless of access
    637+
    privileges.
    638+
    </para>
    639+
    640+
    <para>
    641+
    Selectivity estimation functions contained in third-party extensions that
    642+
    potentially operate on statistics with user-defined operators should follow
    643+
    the same security rules. Consult the PostgreSQL source code for guidance.
    644+
    </para>
    645+
    </sect1>
    585646
    </chapter>

    src/backend/utils/adt/array_selfuncs.c

    Lines changed: 4 additions & 2 deletions
    Original file line numberDiff line numberDiff line change
    @@ -133,7 +133,8 @@ scalararraysel_containment(PlannerInfo *root,
    133133
    useOr = !useOr;
    134134

    135135
    /* Get array element stats for var, if available */
    136-
    if (HeapTupleIsValid(vardata.statsTuple))
    136+
    if (HeapTupleIsValid(vardata.statsTuple) &&
    137+
    statistic_proc_security_check(&vardata, cmpfunc->fn_oid))
    137138
    {
    138139
    Form_pg_statistic stats;
    139140
    Datum *values;
    @@ -364,7 +365,8 @@ calc_arraycontsel(VariableStatData *vardata, Datum constval,
    364365
    */
    365366
    array = DatumGetArrayTypeP(constval);
    366367

    367-
    if (HeapTupleIsValid(vardata->statsTuple))
    368+
    if (HeapTupleIsValid(vardata->statsTuple) &&
    369+
    statistic_proc_security_check(vardata, cmpfunc->fn_oid))
    368370
    {
    369371
    Form_pg_statistic stats;
    370372
    Datum *values;

    src/backend/utils/adt/rangetypes_selfuncs.c

    Lines changed: 22 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -255,6 +255,7 @@ calc_rangesel(TypeCacheEntry *typcache, VariableStatData *vardata,
    255255
    if (nnumbers != 1)
    256256
    elog(ERROR, "invalid empty fraction statistic"); /* shouldn't happen */
    257257
    empty_frac = numbers[0];
    258+
    free_attstatsslot(vardata->atttype, NULL, 0, numbers, nnumbers);
    258259
    }
    259260
    else
    260261
    {
    @@ -383,6 +384,15 @@ calc_hist_selectivity(TypeCacheEntry *typcache, VariableStatData *vardata,
    383384
    bool empty;
    384385
    double hist_selec;
    385386

    387+
    /* Can't use the histogram with insecure range support functions */
    388+
    if (!statistic_proc_security_check(vardata,
    389+
    typcache->rng_cmp_proc_finfo.fn_oid))
    390+
    return -1;
    391+
    if (OidIsValid(typcache->rng_subdiff_finfo.fn_oid) &&
    392+
    !statistic_proc_security_check(vardata,
    393+
    typcache->rng_subdiff_finfo.fn_oid))
    394+
    return -1;
    395+
    386396
    /* Try to get histogram of ranges */
    387397
    if (!(HeapTupleIsValid(vardata->statsTuple) &&
    388398
    get_attstatsslot(vardata->statsTuple,
    @@ -420,11 +430,19 @@ calc_hist_selectivity(TypeCacheEntry *typcache, VariableStatData *vardata,
    420430
    NULL,
    421431
    &length_hist_values, &length_nhist,
    422432
    NULL, NULL)))
    433+
    {
    434+
    free_attstatsslot(vardata->atttype, hist_values, nhist, NULL, 0);
    423435
    return -1.0;
    436+
    }
    424437

    425438
    /* check that it's a histogram, not just a dummy entry */
    426439
    if (length_nhist < 2)
    440+
    {
    441+
    free_attstatsslot(vardata->atttype,
    442+
    length_hist_values, length_nhist, NULL, 0);
    443+
    free_attstatsslot(vardata->atttype, hist_values, nhist, NULL, 0);
    427444
    return -1.0;
    445+
    }
    428446
    }
    429447

    430448
    /* Extract the bounds of the constant value. */
    @@ -560,6 +578,10 @@ calc_hist_selectivity(TypeCacheEntry *typcache, VariableStatData *vardata,
    560578
    break;
    561579
    }
    562580

    581+
    free_attstatsslot(vardata->atttype,
    582+
    length_hist_values, length_nhist, NULL, 0);
    583+
    free_attstatsslot(vardata->atttype, hist_values, nhist, NULL, 0);
    584+
    563585
    return hist_selec;
    564586
    }
    565587

    0 commit comments

    Comments
     (0)
    0