mirror of
https://github.com/postgres/postgres.git
synced 2026-10-23 13:29:09 +03:00
If the COPY command fails before sending PGRES_COPY_IN, psql did not realize that it ought to consume any in-line data following the command. Failing to do so leads to trying to execute that data as SQL commands, which in the best case is wrong and in the worst case is a SQL-injection hazard. To fix: 1. Extend psqlscan.l to recognize COPY ... FROM STDIN. This can be done with a pretty simple extension to the logic that already recognizes nested BEGIN blocks within CREATE FUNCTION et al. But unlike that case, we need to consider and count multiple COPY commands within a single query string (separated by "\;"). The fallout from that is that psql_scan_reset must now always be called before starting a new query string. (The comment for it that claimed we didn't need that because "the scan state must be INITIAL" was really obsolete already, since it has long reset more state besides start_state.) 2. Teach handleCopyIn() to read and discard data when passed NULL for "conn". 3. Add logic to SendQuery() to call handleCopyIn() that way if the query string contained COPY ... FROM STDIN command(s) that remain unaccounted-for at the end. Now that we have this counting logic, we can also detect if the backend sends an unexpected PGRES_COPY_IN message. That should never happen, but perhaps a malicious server could try to extract data that way. A side-effect of doing this is that we have to adjust a number of test scripts that thought they needn't write "\." after a COPY FROM STDIN that they expect to fail. On the whole this is an improvement, since there's now a uniform rule "write \. after COPY FROM STDIN, whether you expect it to work or not". But it is an annoying amount of test churn. A loose end in this patch is that if it has to skip data, it assumes that that data is text not binary. It seems unduly difficult to detect whether the COPY command requested binary (we could handle the old-style COPY BINARY ... syntax, but not the new style with format options). In practice, copying in-line binary data is unsupported anyway, because there's no way to write an end marker: the textual terminator sequence "\n\\.\n" could appear in binary data and there's no provision for escaping it, so neither psql nor the server look for it when in binary mode. Reported-by: Alexander Lakhin <exclusion@gmail.com> Author: Tom Lane <tgl@sss.pgh.pa.us> Reviewed-by: Noah Misch <noah@leadboat.com> Backpatch-through: 14 Security: CVE-2026-6464
98 lines
2.2 KiB
SQL
98 lines
2.2 KiB
SQL
--
|
|
-- Test cases for COPY (select) TO
|
|
--
|
|
create table test1 (id serial, t text);
|
|
insert into test1 (t) values ('a');
|
|
insert into test1 (t) values ('b');
|
|
insert into test1 (t) values ('c');
|
|
insert into test1 (t) values ('d');
|
|
insert into test1 (t) values ('e');
|
|
|
|
create table test2 (id serial, t text);
|
|
insert into test2 (t) values ('A');
|
|
insert into test2 (t) values ('B');
|
|
insert into test2 (t) values ('C');
|
|
insert into test2 (t) values ('D');
|
|
insert into test2 (t) values ('E');
|
|
|
|
create view v_test1
|
|
as select 'v_'||t from test1;
|
|
|
|
--
|
|
-- Test COPY table TO
|
|
--
|
|
copy test1 to stdout;
|
|
--
|
|
-- This should fail
|
|
--
|
|
copy v_test1 to stdout;
|
|
--
|
|
-- Test COPY (select) TO
|
|
--
|
|
copy (select t from test1 where id=1) to stdout;
|
|
--
|
|
-- Test COPY (select for update) TO
|
|
--
|
|
copy (select t from test1 where id=3 for update) to stdout;
|
|
--
|
|
-- This should fail
|
|
--
|
|
copy (select t into temp test3 from test1 where id=3) to stdout;
|
|
--
|
|
-- This should fail
|
|
--
|
|
copy (select * from test1) from stdin;
|
|
\.
|
|
--
|
|
-- This should fail
|
|
--
|
|
copy (select * from test1) (t,id) to stdout;
|
|
--
|
|
-- Test JOIN
|
|
--
|
|
copy (select * from test1 join test2 using (id)) to stdout;
|
|
--
|
|
-- Test UNION SELECT
|
|
--
|
|
copy (select t from test1 where id = 1 UNION select * from v_test1 ORDER BY 1) to stdout;
|
|
--
|
|
-- Test subselect
|
|
--
|
|
copy (select * from (select t from test1 where id = 1 UNION select * from v_test1 ORDER BY 1) t1) to stdout;
|
|
--
|
|
-- Test headers, CSV and quotes
|
|
--
|
|
copy (select t from test1 where id = 1) to stdout csv header force quote t;
|
|
--
|
|
-- Test psql builtins, plain table
|
|
--
|
|
\copy test1 to stdout
|
|
--
|
|
-- This should fail
|
|
--
|
|
\copy v_test1 to stdout
|
|
--
|
|
-- Test \copy (select ...)
|
|
--
|
|
\copy (select "id",'id','id""'||t,(id + 1)*id,t,"test1"."t" from test1 where id=3) to stdout
|
|
--
|
|
-- Drop everything
|
|
--
|
|
drop table test2;
|
|
drop view v_test1;
|
|
drop table test1;
|
|
|
|
-- psql handling of COPY in multi-command strings
|
|
copy (select 1) to stdout\; select 1/0; -- row, then error
|
|
select 1/0\; copy (select 1) to stdout; -- error only
|
|
copy (select 1) to stdout\; copy (select 2) to stdout\; select 3\; select 4; -- 1 2 3 4
|
|
|
|
create table test3 (c int);
|
|
select 0\; copy test3 from stdin\; copy test3 from stdin\; select 1; -- 0 1
|
|
1
|
|
\.
|
|
2
|
|
\.
|
|
select * from test3;
|
|
drop table test3;
|