diff --git a/contrib/citext/citext--unpackaged--1.0.sql b/contrib/citext/citext--unpackaged--1.0.sql index ef6d6b0639..4061a0aeff 100644 --- a/contrib/citext/citext--unpackaged--1.0.sql +++ b/contrib/citext/citext--unpackaged--1.0.sql @@ -89,8 +89,17 @@ ALTER EXTENSION citext ADD function translate(citext,citext,text); -- default collation is pinned. -- +DO LANGUAGE plpgsql +$$ +DECLARE + my_schema pg_catalog.text := pg_catalog.quote_ident(pg_catalog.current_schema()); + old_path pg_catalog.text := pg_catalog.current_setting('search_path'); +BEGIN +-- for safety, transiently set search_path to just pg_catalog+pg_temp +PERFORM pg_catalog.set_config('search_path', 'pg_catalog, pg_temp', true); + WITH RECURSIVE typeoids(typoid) AS - ( SELECT 'citext'::pg_catalog.regtype UNION + ( SELECT (my_schema || '.citext')::pg_catalog.regtype UNION SELECT oid FROM pg_catalog.pg_type, typeoids WHERE typelem = typoid OR typbasetype = typoid ) UPDATE pg_catalog.pg_type SET typcollation = 100 @@ -98,7 +107,7 @@ FROM typeoids WHERE oid = typeoids.typoid; WITH RECURSIVE typeoids(typoid) AS - ( SELECT 'citext'::pg_catalog.regtype UNION + ( SELECT (my_schema || '.citext')::pg_catalog.regtype UNION SELECT oid FROM pg_catalog.pg_type, typeoids WHERE typelem = typoid OR typbasetype = typoid ) UPDATE pg_catalog.pg_attribute SET attcollation = 100 @@ -113,7 +122,7 @@ UPDATE pg_catalog.pg_index SET indcollation = pg_catalog.regexp_replace(indcollation::pg_catalog.text, '^0', '100')::pg_catalog.oidvector WHERE indclass[0] IN ( WITH RECURSIVE typeoids(typoid) AS - ( SELECT 'citext'::pg_catalog.regtype UNION + ( SELECT (my_schema || '.citext')::pg_catalog.regtype UNION SELECT oid FROM pg_catalog.pg_type, typeoids WHERE typelem = typoid OR typbasetype = typoid ) SELECT oid FROM pg_catalog.pg_opclass, typeoids @@ -124,7 +133,7 @@ UPDATE pg_catalog.pg_index SET indcollation = pg_catalog.regexp_replace(indcollation::pg_catalog.text, E'^(\\d+) 0', E'\\1 100')::pg_catalog.oidvector WHERE indclass[1] IN ( WITH RECURSIVE typeoids(typoid) AS - ( SELECT 'citext'::pg_catalog.regtype UNION + ( SELECT (my_schema || '.citext')::pg_catalog.regtype UNION SELECT oid FROM pg_catalog.pg_type, typeoids WHERE typelem = typoid OR typbasetype = typoid ) SELECT oid FROM pg_catalog.pg_opclass, typeoids @@ -135,7 +144,7 @@ UPDATE pg_catalog.pg_index SET indcollation = pg_catalog.regexp_replace(indcollation::pg_catalog.text, E'^(\\d+ \\d+) 0', E'\\1 100')::pg_catalog.oidvector WHERE indclass[2] IN ( WITH RECURSIVE typeoids(typoid) AS - ( SELECT 'citext'::pg_catalog.regtype UNION + ( SELECT (my_schema || '.citext')::pg_catalog.regtype UNION SELECT oid FROM pg_catalog.pg_type, typeoids WHERE typelem = typoid OR typbasetype = typoid ) SELECT oid FROM pg_catalog.pg_opclass, typeoids @@ -146,7 +155,7 @@ UPDATE pg_catalog.pg_index SET indcollation = pg_catalog.regexp_replace(indcollation::pg_catalog.text, E'^(\\d+ \\d+ \\d+) 0', E'\\1 100')::pg_catalog.oidvector WHERE indclass[3] IN ( WITH RECURSIVE typeoids(typoid) AS - ( SELECT 'citext'::pg_catalog.regtype UNION + ( SELECT (my_schema || '.citext')::pg_catalog.regtype UNION SELECT oid FROM pg_catalog.pg_type, typeoids WHERE typelem = typoid OR typbasetype = typoid ) SELECT oid FROM pg_catalog.pg_opclass, typeoids @@ -157,7 +166,7 @@ UPDATE pg_catalog.pg_index SET indcollation = pg_catalog.regexp_replace(indcollation::pg_catalog.text, E'^(\\d+ \\d+ \\d+ \\d+) 0', E'\\1 100')::pg_catalog.oidvector WHERE indclass[4] IN ( WITH RECURSIVE typeoids(typoid) AS - ( SELECT 'citext'::pg_catalog.regtype UNION + ( SELECT (my_schema || '.citext')::pg_catalog.regtype UNION SELECT oid FROM pg_catalog.pg_type, typeoids WHERE typelem = typoid OR typbasetype = typoid ) SELECT oid FROM pg_catalog.pg_opclass, typeoids @@ -168,7 +177,7 @@ UPDATE pg_catalog.pg_index SET indcollation = pg_catalog.regexp_replace(indcollation::pg_catalog.text, E'^(\\d+ \\d+ \\d+ \\d+ \\d+) 0', E'\\1 100')::pg_catalog.oidvector WHERE indclass[5] IN ( WITH RECURSIVE typeoids(typoid) AS - ( SELECT 'citext'::pg_catalog.regtype UNION + ( SELECT (my_schema || '.citext')::pg_catalog.regtype UNION SELECT oid FROM pg_catalog.pg_type, typeoids WHERE typelem = typoid OR typbasetype = typoid ) SELECT oid FROM pg_catalog.pg_opclass, typeoids @@ -179,7 +188,7 @@ UPDATE pg_catalog.pg_index SET indcollation = pg_catalog.regexp_replace(indcollation::pg_catalog.text, E'^(\\d+ \\d+ \\d+ \\d+ \\d+ \\d+) 0', E'\\1 100')::pg_catalog.oidvector WHERE indclass[6] IN ( WITH RECURSIVE typeoids(typoid) AS - ( SELECT 'citext'::pg_catalog.regtype UNION + ( SELECT (my_schema || '.citext')::pg_catalog.regtype UNION SELECT oid FROM pg_catalog.pg_type, typeoids WHERE typelem = typoid OR typbasetype = typoid ) SELECT oid FROM pg_catalog.pg_opclass, typeoids @@ -190,7 +199,7 @@ UPDATE pg_catalog.pg_index SET indcollation = pg_catalog.regexp_replace(indcollation::pg_catalog.text, E'^(\\d+ \\d+ \\d+ \\d+ \\d+ \\d+ \\d+) 0', E'\\1 100')::pg_catalog.oidvector WHERE indclass[7] IN ( WITH RECURSIVE typeoids(typoid) AS - ( SELECT 'citext'::pg_catalog.regtype UNION + ( SELECT (my_schema || '.citext')::pg_catalog.regtype UNION SELECT oid FROM pg_catalog.pg_type, typeoids WHERE typelem = typoid OR typbasetype = typoid ) SELECT oid FROM pg_catalog.pg_opclass, typeoids @@ -198,3 +207,7 @@ WHERE indclass[7] IN ( ); -- somewhat arbitrarily, we assume no citext indexes have more than 8 columns + +PERFORM pg_catalog.set_config('search_path', old_path, true); +END +$$; diff --git a/contrib/earthdistance/earthdistance--1.0.sql b/contrib/earthdistance/earthdistance--1.0.sql index c7e9429b61..901b2b0610 100644 --- a/contrib/earthdistance/earthdistance--1.0.sql +++ b/contrib/earthdistance/earthdistance--1.0.sql @@ -31,7 +31,7 @@ CREATE DOMAIN earth AS cube CONSTRAINT not_point check(cube_is_point(value)) CONSTRAINT not_3d check(cube_dim(value) <= 3) CONSTRAINT on_surface check(abs(cube_distance(value, '(0)'::cube) / - earth() - 1) < '10e-7'::float8); + earth() - '1'::float8) < '10e-7'::float8); CREATE FUNCTION sec_to_gc(float8) RETURNS float8 diff --git a/contrib/hstore/hstore--1.1--1.2.sql b/contrib/hstore/hstore--1.1--1.2.sql index a868ffe48e..cc69fc7f80 100644 --- a/contrib/hstore/hstore--1.1--1.2.sql +++ b/contrib/hstore/hstore--1.1--1.2.sql @@ -9,10 +9,13 @@ -- dependent on the extension. DO LANGUAGE plpgsql - $$ - +DECLARE + my_schema pg_catalog.text := pg_catalog.quote_ident(pg_catalog.current_schema()); + old_path pg_catalog.text := pg_catalog.current_setting('search_path'); BEGIN +-- for safety, transiently set search_path to just pg_catalog+pg_temp +PERFORM pg_catalog.set_config('search_path', 'pg_catalog, pg_temp', true); PERFORM 1 FROM pg_proc p @@ -27,6 +30,7 @@ BEGIN IF NOT FOUND THEN + PERFORM pg_catalog.set_config('search_path', old_path, true); CREATE FUNCTION hstore_to_json(hstore) RETURNS json @@ -43,6 +47,7 @@ BEGIN END IF; +PERFORM pg_catalog.set_config('search_path', old_path, true); END; $$; diff --git a/contrib/intarray/intarray--unpackaged--1.0.sql b/contrib/intarray/intarray--unpackaged--1.0.sql index 63814cef98..7f1bef60b1 100644 --- a/contrib/intarray/intarray--unpackaged--1.0.sql +++ b/contrib/intarray/intarray--unpackaged--1.0.sql @@ -84,13 +84,23 @@ ALTER EXTENSION intarray ADD function ginint4_consistent(internal,smallint,inter -- entries. This is ugly as can be, but there's no other way to do it -- while preserving the identities (OIDs) of the functions. +DO LANGUAGE plpgsql +$$ +DECLARE + my_schema_unquoted pg_catalog.text := pg_catalog.current_schema(); + my_schema pg_catalog.text := pg_catalog.quote_ident(pg_catalog.current_schema()); + old_path pg_catalog.text := pg_catalog.current_setting('search_path'); +BEGIN +-- for safety, transiently set search_path to just pg_catalog+pg_temp +PERFORM pg_catalog.set_config('search_path', 'pg_catalog, pg_temp', true); + UPDATE pg_catalog.pg_proc SET pronargs = 7, proargtypes = '2281 2281 21 2281 2281 2281 2281' -WHERE oid = 'ginint4_queryextract(internal,internal,smallint,internal,internal)'::pg_catalog.regprocedure; +WHERE oid = (my_schema || '.ginint4_queryextract(internal,internal,smallint,internal,internal)')::pg_catalog.regprocedure; UPDATE pg_catalog.pg_proc SET pronargs = 8, proargtypes = '2281 21 2281 23 2281 2281 2281 2281' -WHERE oid = 'ginint4_consistent(internal,smallint,internal,integer,internal,internal)'::pg_catalog.regprocedure; +WHERE oid = (my_schema || '.ginint4_consistent(internal,smallint,internal,integer,internal,internal)')::pg_catalog.regprocedure; -- intarray also relies on the core function ginarrayextract, which changed -- signature in 9.1. To support upgrading, pg_catalog contains entries @@ -104,8 +114,12 @@ SET amproc = 'pg_catalog.ginarrayextract(anyarray,internal,internal)'::pg_catalo WHERE amprocfamily = (SELECT oid FROM pg_catalog.pg_opfamily WHERE opfname = 'gin__int_ops' AND opfnamespace = (SELECT oid FROM pg_catalog.pg_namespace - WHERE nspname = pg_catalog.current_schema())) + WHERE nspname = my_schema_unquoted)) AND amproclefttype = 'integer[]'::pg_catalog.regtype AND amprocrighttype = 'integer[]'::pg_catalog.regtype AND amprocnum = 2 AND amproc = 'pg_catalog.ginarrayextract(anyarray,internal)'::pg_catalog.regprocedure; + +PERFORM pg_catalog.set_config('search_path', old_path, true); +END +$$; diff --git a/contrib/pg_trgm/pg_trgm--unpackaged--1.0.sql b/contrib/pg_trgm/pg_trgm--unpackaged--1.0.sql index d3eab97d41..99444d37c3 100644 --- a/contrib/pg_trgm/pg_trgm--unpackaged--1.0.sql +++ b/contrib/pg_trgm/pg_trgm--unpackaged--1.0.sql @@ -57,13 +57,26 @@ LANGUAGE C IMMUTABLE STRICT; -- entries. This is ugly as can be, but there's no other way to do it -- while preserving the identities (OIDs) of the functions. +DO LANGUAGE plpgsql +$$ +DECLARE + my_schema pg_catalog.text := pg_catalog.quote_ident(pg_catalog.current_schema()); + old_path pg_catalog.text := pg_catalog.current_setting('search_path'); +BEGIN +-- for safety, transiently set search_path to just pg_catalog+pg_temp +PERFORM pg_catalog.set_config('search_path', 'pg_catalog, pg_temp', true); + UPDATE pg_catalog.pg_proc SET pronargs = 7, proargtypes = '25 2281 21 2281 2281 2281 2281' -WHERE oid = 'gin_extract_query_trgm(text,internal,int2,internal,internal)'::pg_catalog.regprocedure; +WHERE oid = (my_schema || '.gin_extract_query_trgm(text,internal,int2,internal,internal)')::pg_catalog.regprocedure; UPDATE pg_catalog.pg_proc SET pronargs = 8, proargtypes = '2281 21 25 23 2281 2281 2281 2281' -WHERE oid = 'gin_trgm_consistent(internal,smallint,text,integer,internal,internal)'::pg_catalog.regprocedure; +WHERE oid = (my_schema || '.gin_trgm_consistent(internal,smallint,text,integer,internal,internal)')::pg_catalog.regprocedure; + +PERFORM pg_catalog.set_config('search_path', old_path, true); +END +$$; -- These were not in 9.0: diff --git a/contrib/tsearch2/tsearch2--unpackaged--1.0.sql b/contrib/tsearch2/tsearch2--unpackaged--1.0.sql index e123297132..7a66d5982f 100644 --- a/contrib/tsearch2/tsearch2--unpackaged--1.0.sql +++ b/contrib/tsearch2/tsearch2--unpackaged--1.0.sql @@ -110,12 +110,22 @@ ALTER EXTENSION tsearch2 ADD operator class @extschema@.tsquery_ops using btree; -- Avert your eyes while we hack the pg_amproc entries to make them link to -- the new forms ... +DO LANGUAGE plpgsql +$$ +DECLARE + my_schema_unquoted pg_catalog.text := pg_catalog.current_schema(); + my_schema pg_catalog.text := pg_catalog.quote_ident(pg_catalog.current_schema()); + old_path pg_catalog.text := pg_catalog.current_setting('search_path'); +BEGIN +-- for safety, transiently set search_path to just pg_catalog+pg_temp +PERFORM pg_catalog.set_config('search_path', 'pg_catalog, pg_temp', true); + UPDATE pg_catalog.pg_amproc SET amproc = 'pg_catalog.gin_extract_tsvector(pg_catalog.tsvector,internal,internal)'::pg_catalog.regprocedure WHERE amprocfamily = (SELECT oid FROM pg_catalog.pg_opfamily WHERE opfname = 'gin_tsvector_ops' AND opfnamespace = (SELECT oid FROM pg_catalog.pg_namespace - WHERE nspname = '@extschema@')) + WHERE nspname = my_schema_unquoted)) AND amproclefttype = 'pg_catalog.tsvector'::pg_catalog.regtype AND amprocrighttype = 'pg_catalog.tsvector'::pg_catalog.regtype AND amprocnum = 2 @@ -126,7 +136,7 @@ SET amproc = 'pg_catalog.gin_extract_tsquery(pg_catalog.tsquery,internal,smallin WHERE amprocfamily = (SELECT oid FROM pg_catalog.pg_opfamily WHERE opfname = 'gin_tsvector_ops' AND opfnamespace = (SELECT oid FROM pg_catalog.pg_namespace - WHERE nspname = '@extschema@')) + WHERE nspname = my_schema_unquoted)) AND amproclefttype = 'pg_catalog.tsvector'::pg_catalog.regtype AND amprocrighttype = 'pg_catalog.tsvector'::pg_catalog.regtype AND amprocnum = 3 @@ -137,8 +147,12 @@ SET amproc = 'pg_catalog.gin_tsquery_consistent(internal,smallint,pg_catalog.tsq WHERE amprocfamily = (SELECT oid FROM pg_catalog.pg_opfamily WHERE opfname = 'gin_tsvector_ops' AND opfnamespace = (SELECT oid FROM pg_catalog.pg_namespace - WHERE nspname = '@extschema@')) + WHERE nspname = my_schema_unquoted)) AND amproclefttype = 'pg_catalog.tsvector'::pg_catalog.regtype AND amprocrighttype = 'pg_catalog.tsvector'::pg_catalog.regtype AND amprocnum = 4 AND amproc = 'pg_catalog.gin_tsquery_consistent(internal,smallint,pg_catalog.tsquery,integer,internal,internal)'::pg_catalog.regprocedure; + +PERFORM pg_catalog.set_config('search_path', old_path, true); +END +$$; diff --git a/doc/src/sgml/earthdistance.sgml b/doc/src/sgml/earthdistance.sgml index 2b6df5fee5..d01f8ffa7d 100644 --- a/doc/src/sgml/earthdistance.sgml +++ b/doc/src/sgml/earthdistance.sgml @@ -10,9 +10,8 @@ The earthdistance module provides two different approaches to calculating great circle distances on the surface of the Earth. The one - described first depends on the cube module (which - must be installed before earthdistance can be - installed). The second one is based on the built-in point data type, + described first depends on the cube module. + The second one is based on the built-in point data type, using longitude and latitude for the coordinates. @@ -23,6 +22,26 @@ project.) + + The cube module must be installed + before earthdistance can be installed. + + + + + It is strongly recommended that earthdistance + and cube be installed in the same schema, and that + that schema be one for which CREATE privilege has not been and will not + be granted to any untrusted users. + Otherwise there are installation-time security hazards + if earthdistance's schema contains objects defined + by a hostile user. + Furthermore, when using earthdistance's functions + after installation, the entire search path should contain only trusted + schemas. + + + Cube-based Earth Distances diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml index afd2b8f9fc..eb0d55f3e5 100644 --- a/doc/src/sgml/extend.sgml +++ b/doc/src/sgml/extend.sgml @@ -380,32 +380,6 @@ schema(s) its member objects are within. - - Defining Extension Objects - - - - Widely-distributed extensions should assume little about the database - they occupy. In particular, unless you issued SET search_path = - pg_temp, assume each unqualified name could resolve to an - object that a malicious user has defined. Beware of constructs that - depend on search_path implicitly: IN - and CASE expression WHEN - always select an operator using the search path. In their place, use - OPERATOR(schema.=) ANY - and CASE WHEN expression. - - - - Extension Files @@ -661,7 +635,7 @@ schema; that is, CREATE EXTENSION does the equivalent of this: -SET LOCAL search_path TO @extschema@; +SET LOCAL search_path TO @extschema@, pg_temp; This allows the objects created by the script file to go into the target schema. The script file can change search_path if it wishes, @@ -681,9 +655,15 @@ SET LOCAL search_path TO @extschema@; If any prerequisite extensions are listed in requires - in the control file, their target schemas are appended to the initial - setting of search_path. This allows their objects to be - visible to the new extension's script file. + in the control file, their target schemas are added to the initial + setting of search_path, following the new + extension's target schema. This allows their objects to be visible to + the new extension's script file. + + + + For security, pg_temp is automatically appended to + the end of search_path in all cases. @@ -880,7 +860,144 @@ SELECT * FROM pg_extension_update_paths('extension_name'); - + + Security Considerations for Extensions + + + Widely-distributed extensions should assume little about the database + they occupy. Therefore, it's appropriate to write functions provided + by an extension in a secure style that cannot be compromised by + search-path-based attacks. + + + + An extension that has the superuser property set to + true must also consider security hazards for the actions taken within + its installation and update scripts. It is not terribly difficult for + a malicious user to create trojan-horse objects that will compromise + later execution of a carelessly-written extension script, allowing that + user to acquire superuser privileges. + + + + Advice about writing functions securely is provided in + below, and advice + about writing installation scripts securely is provided in + . + + + + Security Considerations for Extension Functions + + + SQL-language and PL-language functions provided by extensions are at + risk of search-path-based attacks when they are executed, since + parsing of these functions occurs at execution time not creation time. + + + + The CREATE + FUNCTION reference page contains advice about + writing SECURITY DEFINER functions safely. It's + good practice to apply those techniques for any function provided by + an extension, since the function might be called by a high-privilege + user. + + + + + If you cannot set the search_path to contain only + secure schemas, assume that each unqualified name could resolve to an + object that a malicious user has defined. Beware of constructs that + depend on search_path implicitly; for + example, IN + and CASE expression WHEN + always select an operator using the search path. In their place, use + OPERATOR(schema.=) ANY + and CASE WHEN expression. + + + + A general-purpose extension usually should not assume that it's been + installed into a secure schema, which means that even schema-qualified + references to its own objects are not entirely risk-free. For + example, if the extension has defined a + function myschema.myfunc(bigint) then a call such + as myschema.myfunc(42) could be captured by a + hostile function myschema.myfunc(integer). Be + careful that the data types of function and operator parameters exactly + match the declared argument types, using explicit casts where necessary. + + + + + Security Considerations for Extension Scripts + + + An extension installation or update script should be written to guard + against search-path-based attacks occurring when the script executes. + If an object reference in the script can be made to resolve to some + other object than the script author intended, then a compromise might + occur immediately, or later when the mis-defined extension object is + used. + + + + DDL commands such as CREATE FUNCTION + and CREATE OPERATOR CLASS are generally secure, + but beware of any command having a general-purpose expression as a + component. For example, CREATE VIEW needs to be + vetted, as does a DEFAULT expression + in CREATE FUNCTION. + + + + Sometimes an extension script might need to execute general-purpose + SQL, for example to make catalog adjustments that aren't possible via + DDL. Be careful to execute such commands with a + secure search_path; do not + trust the path provided by CREATE/ALTER EXTENSION + to be secure. Best practice is to temporarily + set search_path to 'pg_catalog, + pg_temp' and insert references to the extension's + installation schema explicitly where needed. (This practice might + also be helpful for creating views.) Examples can be found in + the contrib modules in + the PostgreSQL source code distribution. + + + + Cross-extension references are extremely difficult to make fully + secure, partially because of uncertainty about which schema the other + extension is in. The hazards are reduced if both extensions are + installed in the same schema, because then a hostile object cannot be + placed ahead of the referenced extension in the installation-time + search_path. However, no mechanism currently exists + to require that. + + + + Do not use CREATE OR REPLACE + FUNCTION, except in an update script that must change the + definition of a function that is known to be an extension member + already. (Likewise for other OR REPLACE options.) + Using OR REPLACE unnecessarily not only has a risk + of accidentally overwriting someone else's function, but it creates a + security hazard since the overwritten function would still be owned by + its original owner, who could modify it. + + + + + Extension Example @@ -899,18 +1016,18 @@ SELECT * FROM pg_extension_update_paths('extension_name'); CREATE TYPE pair AS ( k text, v text ); -CREATE OR REPLACE FUNCTION pair(text, text) +CREATE FUNCTION pair(text, text) RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::@extschema@.pair;'; CREATE OPERATOR ~> (LEFTARG = text, RIGHTARG = text, PROCEDURE = pair); -- "SET search_path" is easy to get right, but qualified names perform better. -CREATE OR REPLACE FUNCTION lower(pair) +CREATE FUNCTION lower(pair) RETURNS pair LANGUAGE SQL AS 'SELECT ROW(lower($1.k), lower($1.v))::@extschema@.pair;' SET search_path = pg_temp; -CREATE OR REPLACE FUNCTION pair_concat(pair, pair) +CREATE FUNCTION pair_concat(pair, pair) RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1.k OPERATOR(pg_catalog.||) $2.k, $1.v OPERATOR(pg_catalog.||) $2.v)::@extschema@.pair;'; @@ -925,6 +1042,7 @@ AS 'SELECT ROW($1.k OPERATOR(pg_catalog.||) $2.k, # pair extension comment = 'A key/value pair data type' default_version = '1.0' +# cannot be relocatable because of use of @extschema@ relocatable = false diff --git a/doc/src/sgml/hstore.sgml b/doc/src/sgml/hstore.sgml index 5c411950dd..d9f92e15a3 100644 --- a/doc/src/sgml/hstore.sgml +++ b/doc/src/sgml/hstore.sgml @@ -633,6 +633,15 @@ ALTER TABLE tablename ALTER hstorecol TYPE hstore USING hstorecol || ''; convention). If you use them, hstore values are mapped to Python dictionaries. + + + + It is strongly recommended that the transform extensions be installed in + the same schema as hstore. Otherwise there are + installation-time security hazards if a transform extension's schema + contains objects defined by a hostile user. + + diff --git a/doc/src/sgml/ltree.sgml b/doc/src/sgml/ltree.sgml index b399391da1..70ad7d3421 100644 --- a/doc/src/sgml/ltree.sgml +++ b/doc/src/sgml/ltree.sgml @@ -674,6 +674,15 @@ ltreetest=> SELECT ins_label(path,2,'Space') FROM test WHERE path <@ 'Top. creating a function, ltree values are mapped to Python lists. (The reverse is currently not supported, however.) + + + + It is strongly recommended that the transform extensions be installed in + the same schema as ltree. Otherwise there are + installation-time security hazards if a transform extension's schema + contains objects defined by a hostile user. + + diff --git a/doc/src/sgml/ref/create_extension.sgml b/doc/src/sgml/ref/create_extension.sgml index a1e7e4f812..b96602a7dd 100644 --- a/doc/src/sgml/ref/create_extension.sgml +++ b/doc/src/sgml/ref/create_extension.sgml @@ -161,6 +161,33 @@ CREATE EXTENSION [ IF NOT EXISTS ] extension_name system views. + + + Installing an extension as superuser requires trusting that the + extension's author wrote the extension installation script in a secure + fashion. It is not terribly difficult for a malicious user to create + trojan-horse objects that will compromise later execution of a + carelessly-written extension script, allowing that user to acquire + superuser privileges. However, trojan-horse objects are only hazardous + if they are in the search_path during script + execution, meaning that they are in the extension's installation target + schema or in the schema of some extension it depends on. Therefore, a + good rule of thumb when dealing with extensions whose scripts have not + been carefully vetted is to install them only into schemas for which + CREATE privilege has not been and will not be granted to any untrusted + users. Likewise for any extensions they depend on. + + + + The extensions supplied with PostgreSQL are + believed to be secure against installation-time attacks of this sort, + except for a few that depend on other extensions. As stated in the + documentation for those extensions, they should be installed into secure + schemas, or installed into the same schemas as the extensions they + depend on, or both. + + + For information about writing new extensions, see . @@ -172,8 +199,13 @@ CREATE EXTENSION [ IF NOT EXISTS ] extension_name Install the hstore extension into the - current database: + current database, placing its objects in schema addons: +CREATE EXTENSION hstore SCHEMA addons; + + Another way to accomplish the same thing: + +SET search_path = addons; CREATE EXTENSION hstore; diff --git a/doc/src/sgml/release-9.5.sgml b/doc/src/sgml/release-9.5.sgml index 853febf9a3..95213333a2 100644 --- a/doc/src/sgml/release-9.5.sgml +++ b/doc/src/sgml/release-9.5.sgml @@ -6856,7 +6856,7 @@ Branch: REL9_5_STABLE [3c0e07a46] 2018-05-01 12:02:41 -0400 one's search path. Relevant documentation appears in (for database administrators and users), (for application authors), - (for extension authors), and + (for extension authors), and (for authors of SECURITY DEFINER functions). (CVE-2018-1058) diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c index 39c9411367..a4ab97cf5c 100644 --- a/src/backend/commands/extension.c +++ b/src/backend/commands/extension.c @@ -821,9 +821,21 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control, GUC_ACTION_SAVE, true, 0, false); /* - * Set up the search path to contain the target schema, then the schemas - * of any prerequisite extensions, and nothing else. In particular this - * makes the target schema be the default creation target namespace. + * Similarly disable check_function_bodies, to ensure that SQL functions + * won't be parsed during creation. + */ + if (check_function_bodies) + (void) set_config_option("check_function_bodies", "off", + PGC_USERSET, PGC_S_SESSION, + GUC_ACTION_SAVE, true, 0, false); + + /* + * Set up the search path to have the target schema first, making it be + * the default creation target namespace. Then add the schemas of any + * prerequisite extensions, unless they are in pg_catalog which would be + * searched anyway. (Listing pg_catalog explicitly in a non-first + * position would be bad for security.) Finally add pg_temp to ensure + * that temp objects can't take precedence over others. * * Note: it might look tempting to use PushOverrideSearchPath for this, * but we cannot do that. We have to actually set the search_path GUC in @@ -837,9 +849,10 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control, Oid reqschema = lfirst_oid(lc); char *reqname = get_namespace_name(reqschema); - if (reqname) + if (reqname && strcmp(reqname, "pg_catalog") != 0) appendStringInfo(&pathbuf, ", %s", quote_identifier(reqname)); } + appendStringInfoString(&pathbuf, ", pg_temp"); (void) set_config_option("search_path", pathbuf.data, PGC_USERSET, PGC_S_SESSION, diff --git a/src/backend/commands/operatorcmds.c b/src/backend/commands/operatorcmds.c index 2257c56256..3a19db583b 100644 --- a/src/backend/commands/operatorcmds.c +++ b/src/backend/commands/operatorcmds.c @@ -249,6 +249,8 @@ DefineOperator(List *names, List *parameters) */ if (joinName) { + Oid joinOid2; + typeId[0] = INTERNALOID; /* PlannerInfo */ typeId[1] = OIDOID; /* operator OID */ typeId[2] = INTERNALOID; /* args list */ @@ -257,15 +259,26 @@ DefineOperator(List *names, List *parameters) /* * As of Postgres 8.4, the preferred signature for join estimators has - * 5 arguments, but we still allow the old 4-argument form. Try the - * preferred form first. + * 5 arguments, but we still allow the old 4-argument form. Whine + * about ambiguity if both forms exist. */ joinOid = LookupFuncName(joinName, 5, typeId, true); - if (!OidIsValid(joinOid)) - joinOid = LookupFuncName(joinName, 4, typeId, true); - /* If not found, reference the 5-argument signature in error msg */ - if (!OidIsValid(joinOid)) - joinOid = LookupFuncName(joinName, 5, typeId, false); + joinOid2 = LookupFuncName(joinName, 4, typeId, true); + if (OidIsValid(joinOid)) + { + if (OidIsValid(joinOid2)) + ereport(ERROR, + (errcode(ERRCODE_AMBIGUOUS_FUNCTION), + errmsg("join estimator function %s has multiple matches", + NameListToString(joinName)))); + } + else + { + joinOid = joinOid2; + /* If not found, reference the 5-argument signature in error msg */ + if (!OidIsValid(joinOid)) + joinOid = LookupFuncName(joinName, 5, typeId, false); + } /* estimators must return float8 */ if (get_func_rettype(joinOid) != FLOAT8OID) diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c index cedd42433d..b5875405f0 100644 --- a/src/backend/commands/typecmds.c +++ b/src/backend/commands/typecmds.c @@ -1642,7 +1642,11 @@ static Oid findTypeInputFunction(List *procname, Oid typeOid) { Oid argList[3]; + int nmatches = 0; Oid procOid; + Oid procOid2; + Oid procOid3; + Oid procOid4; /* * Input functions can take a single argument of type CSTRING, or three @@ -1650,32 +1654,45 @@ findTypeInputFunction(List *procname, Oid typeOid) * * For backwards compatibility we allow OPAQUE in place of CSTRING; if we * see this, we issue a warning and fix up the pg_proc entry. + * + * Whine about ambiguity if multiple forms exist. */ argList[0] = CSTRINGOID; - - procOid = LookupFuncName(procname, 1, argList, true); - if (OidIsValid(procOid)) - return procOid; - argList[1] = OIDOID; argList[2] = INT4OID; - procOid = LookupFuncName(procname, 3, argList, true); + procOid = LookupFuncName(procname, 1, argList, true); if (OidIsValid(procOid)) - return procOid; + nmatches++; + procOid2 = LookupFuncName(procname, 3, argList, true); + if (OidIsValid(procOid2)) + nmatches++; - /* No luck, try it with OPAQUE */ argList[0] = OPAQUEOID; - procOid = LookupFuncName(procname, 1, argList, true); + procOid3 = LookupFuncName(procname, 1, argList, true); + if (OidIsValid(procOid3)) + nmatches++; + procOid4 = LookupFuncName(procname, 3, argList, true); + if (OidIsValid(procOid4)) + nmatches++; - if (!OidIsValid(procOid)) - { - argList[1] = OIDOID; - argList[2] = INT4OID; + if (nmatches > 1) + ereport(ERROR, + (errcode(ERRCODE_AMBIGUOUS_FUNCTION), + errmsg("type input function %s has multiple matches", + NameListToString(procname)))); - procOid = LookupFuncName(procname, 3, argList, true); - } + if (OidIsValid(procOid)) + return procOid; + if (OidIsValid(procOid2)) + return procOid2; + + /* Cases with OPAQUE need adjustment */ + if (OidIsValid(procOid3)) + procOid = procOid3; + else + procOid = procOid4; if (OidIsValid(procOid)) { @@ -1761,24 +1778,32 @@ findTypeReceiveFunction(List *procname, Oid typeOid) { Oid argList[3]; Oid procOid; + Oid procOid2; /* * Receive functions can take a single argument of type INTERNAL, or three - * arguments (internal, typioparam OID, typmod). + * arguments (internal, typioparam OID, typmod). Whine about ambiguity if + * both forms exist. */ argList[0] = INTERNALOID; - - procOid = LookupFuncName(procname, 1, argList, true); - if (OidIsValid(procOid)) - return procOid; - argList[1] = OIDOID; argList[2] = INT4OID; - procOid = LookupFuncName(procname, 3, argList, true); + procOid = LookupFuncName(procname, 1, argList, true); + procOid2 = LookupFuncName(procname, 3, argList, true); if (OidIsValid(procOid)) + { + if (OidIsValid(procOid2)) + ereport(ERROR, + (errcode(ERRCODE_AMBIGUOUS_FUNCTION), + errmsg("type receive function %s has multiple matches", + NameListToString(procname)))); return procOid; + } + else if (OidIsValid(procOid2)) + return procOid2; + /* If not found, reference the 1-argument signature in error msg */ ereport(ERROR, (errcode(ERRCODE_UNDEFINED_FUNCTION), errmsg("function %s does not exist",