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 0170b10dff
commit 3eefc51053
5 changed files with 82 additions and 8 deletions

View File

@ -11084,8 +11084,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

@ -910,11 +910,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
JOIN pg_foreign_server S ON (U.umserver = S.oid)
LEFT JOIN pg_authid A ON (A.oid = U.umuser);

View File

@ -1200,7 +1200,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 regress_unprivileged_role;
-- owner of server can see option fields
\deu+
List of user mappings
Server | User name | FDW Options
--------+---------------------------+-------------------
s10 | public | ("user" 'secret')
s4 | regress_foreign_data_user |
s5 | regress_test_role | (modified '1')
s6 | regress_test_role |
s8 | public |
s8 | regress_foreign_data_user |
s9 | regress_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 | regress_foreign_data_user |
s5 | regress_test_role | (modified '1')
s6 | regress_test_role |
s8 | public |
s8 | regress_foreign_data_user | (password 'public')
s9 | regress_unprivileged_role |
t1 | public | (modified '1')
(8 rows)
-- unprivileged user cannot see option fields
SET ROLE regress_unprivileged_role;
\deu+
List of user mappings
Server | User name | FDW Options
--------+---------------------------+-------------
s10 | public |
s4 | regress_foreign_data_user |
s5 | regress_test_role |
s6 | regress_test_role |
s8 | public |
s8 | regress_foreign_data_user |
s9 | regress_unprivileged_role |
t1 | public |
(8 rows)
RESET ROLE;
DROP SERVER s10 CASCADE;
NOTICE: drop cascades to user mapping for public on server s10
-- Triggers
CREATE FUNCTION dummy_trigger() RETURNS TRIGGER AS $$
BEGIN

View File

@ -2228,7 +2228,9 @@ pg_user_mappings| SELECT u.oid AS umid,
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
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

View File

@ -493,7 +493,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 regress_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 regress_unprivileged_role;
\deu+
RESET ROLE;
DROP SERVER s10 CASCADE;
-- Triggers
CREATE FUNCTION dummy_trigger() RETURNS TRIGGER AS $$