Match pg_user_mappings limits to information_schema.user_mapping_options.

Both views replace the umoptions field with NULL when the user does not
meet qualifications to see it.  They used different qualifications, and
pg_user_mappings documented qualifications did not match its implemented
qualifications.  Make its documentation and implementation match those
of user_mapping_options.  One might argue for stronger qualifications,
but these have long, documented tenure.  pg_user_mappings has always
exhibited this problem, so back-patch to 9.2 (all supported versions).

Michael Paquier and Feike Steenbergen.  Reviewed by Jeff Janes.
Reported by Andrew Wheelwright.

Security: CVE-2017-7486
This commit is contained in:
Noah Misch 2017-05-08 07:24:24 -07:00
parent 47864be33c
commit 99cbb0bd9a
5 changed files with 80 additions and 8 deletions

View File

@ -8769,8 +8769,11 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
<entry></entry>
<entry>
User mapping specific options, as <quote>keyword=value</>
strings, if the current user is the owner of the foreign
server, else null
strings. This column will show as null unless the current user
is the user being mapped, or the mapping is for
<literal>PUBLIC</literal> and the current user is the server
owner, or the current user is a superuser. The intent is
to protect password information stored as user mapping option.
</entry>
</row>
</tbody>

View File

@ -669,11 +669,11 @@ CREATE VIEW pg_user_mappings AS
ELSE
A.rolname
END AS usename,
CASE WHEN pg_has_role(S.srvowner, 'USAGE') OR has_server_privilege(S.oid, 'USAGE') THEN
U.umoptions
ELSE
NULL
END AS umoptions
CASE WHEN (U.umuser <> 0 AND A.rolname = current_user)
OR (U.umuser = 0 AND pg_has_role(S.srvowner, 'USAGE'))
OR (SELECT rolsuper FROM pg_authid WHERE rolname = current_user)
THEN U.umoptions
ELSE NULL END AS umoptions
FROM pg_user_mapping U
LEFT JOIN pg_authid A ON (A.oid = U.umuser) JOIN
pg_foreign_server S ON (U.umserver = S.oid);

View File

@ -1159,7 +1159,61 @@ WARNING: no privileges were granted for "s9"
CREATE USER MAPPING FOR current_user SERVER s9;
DROP SERVER s9 CASCADE; -- ERROR
ERROR: must be owner of foreign server s9
-- Check visibility of user mapping data
SET ROLE regress_test_role;
CREATE SERVER s10 FOREIGN DATA WRAPPER foo;
CREATE USER MAPPING FOR public SERVER s10 OPTIONS (user 'secret');
GRANT USAGE ON FOREIGN SERVER s10 TO unprivileged_role;
-- owner of server can see option fields
\deu+
List of user mappings
Server | User name | FDW Options
--------+-------------------+-------------------
s10 | public | ("user" 'secret')
s4 | foreign_data_user |
s5 | regress_test_role | (modified '1')
s6 | regress_test_role |
s8 | foreign_data_user |
s8 | public |
s9 | unprivileged_role |
t1 | public | (modified '1')
(8 rows)
RESET ROLE;
-- superuser can see option fields
\deu+
List of user mappings
Server | User name | FDW Options
--------+-------------------+---------------------
s10 | public | ("user" 'secret')
s4 | foreign_data_user |
s5 | regress_test_role | (modified '1')
s6 | regress_test_role |
s8 | foreign_data_user | (password 'public')
s8 | public |
s9 | unprivileged_role |
t1 | public | (modified '1')
(8 rows)
-- unprivileged user cannot see option fields
SET ROLE unprivileged_role;
\deu+
List of user mappings
Server | User name | FDW Options
--------+-------------------+-------------
s10 | public |
s4 | foreign_data_user |
s5 | regress_test_role |
s6 | regress_test_role |
s8 | foreign_data_user |
s8 | public |
s9 | unprivileged_role |
t1 | public |
(8 rows)
RESET ROLE;
DROP SERVER s10 CASCADE;
NOTICE: drop cascades to user mapping for public on server s10
-- DROP FOREIGN TABLE
DROP FOREIGN TABLE no_table; -- ERROR
ERROR: foreign table "no_table" does not exist

View File

@ -1322,7 +1322,7 @@ SELECT viewname, definition FROM pg_views WHERE schemaname <> 'information_schem
pg_timezone_abbrevs | SELECT pg_timezone_abbrevs.abbrev, pg_timezone_abbrevs.utc_offset, pg_timezone_abbrevs.is_dst FROM pg_timezone_abbrevs() pg_timezone_abbrevs(abbrev, utc_offset, is_dst);
pg_timezone_names | SELECT pg_timezone_names.name, pg_timezone_names.abbrev, pg_timezone_names.utc_offset, pg_timezone_names.is_dst FROM pg_timezone_names() pg_timezone_names(name, abbrev, utc_offset, is_dst);
pg_user | SELECT pg_shadow.usename, pg_shadow.usesysid, pg_shadow.usecreatedb, pg_shadow.usesuper, pg_shadow.usecatupd, pg_shadow.userepl, '********'::text AS passwd, pg_shadow.valuntil, pg_shadow.useconfig FROM pg_shadow;
pg_user_mappings | SELECT u.oid AS umid, s.oid AS srvid, s.srvname, u.umuser, CASE WHEN (u.umuser = (0)::oid) THEN 'public'::name ELSE a.rolname END AS usename, CASE WHEN (pg_has_role(s.srvowner, 'USAGE'::text) OR has_server_privilege(s.oid, 'USAGE'::text)) THEN u.umoptions ELSE NULL::text[] END AS umoptions FROM ((pg_user_mapping u LEFT JOIN pg_authid a ON ((a.oid = u.umuser))) JOIN pg_foreign_server s ON ((u.umserver = s.oid)));
pg_user_mappings | SELECT u.oid AS umid, s.oid AS srvid, s.srvname, u.umuser, CASE WHEN (u.umuser = (0)::oid) THEN 'public'::name ELSE a.rolname END AS usename, CASE WHEN ((((u.umuser <> (0)::oid) AND (a.rolname = "current_user"())) OR ((u.umuser = (0)::oid) AND pg_has_role(s.srvowner, 'USAGE'::text))) OR (SELECT pg_authid.rolsuper FROM pg_authid WHERE (pg_authid.rolname = "current_user"()))) THEN u.umoptions ELSE NULL::text[] END AS umoptions FROM ((pg_user_mapping u LEFT JOIN pg_authid a ON ((a.oid = u.umuser))) JOIN pg_foreign_server s ON ((u.umserver = s.oid)));
pg_views | SELECT n.nspname AS schemaname, c.relname AS viewname, pg_get_userbyid(c.relowner) AS viewowner, pg_get_viewdef(c.oid) AS definition FROM (pg_class c LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) WHERE (c.relkind = 'v'::"char");
rtest_v1 | SELECT rtest_t1.a, rtest_t1.b FROM rtest_t1;
rtest_vcomp | SELECT x.part, (x.size * y.factor) AS size_in_cm FROM rtest_comp x, rtest_unitfact y WHERE (x.unit = y.unit);

View File

@ -469,7 +469,22 @@ ALTER SERVER s9 VERSION '1.2'; -- ERROR
GRANT USAGE ON FOREIGN SERVER s9 TO regress_test_role; -- WARNING
CREATE USER MAPPING FOR current_user SERVER s9;
DROP SERVER s9 CASCADE; -- ERROR
-- Check visibility of user mapping data
SET ROLE regress_test_role;
CREATE SERVER s10 FOREIGN DATA WRAPPER foo;
CREATE USER MAPPING FOR public SERVER s10 OPTIONS (user 'secret');
GRANT USAGE ON FOREIGN SERVER s10 TO unprivileged_role;
-- owner of server can see option fields
\deu+
RESET ROLE;
-- superuser can see option fields
\deu+
-- unprivileged user cannot see option fields
SET ROLE unprivileged_role;
\deu+
RESET ROLE;
DROP SERVER s10 CASCADE;
-- DROP FOREIGN TABLE
DROP FOREIGN TABLE no_table; -- ERROR