Thread

  1. BUG #19478: `dblink_close` can be used for injection.

    PG Bug reporting form <noreply@postgresql.org> — 2026-05-15T01:29:54Z

    The following bug has been logged on the website:
    
    Bug reference:      19478
    Logged by:          Man Zeng
    Email address:      zengman@halodbtech.com
    PostgreSQL version: 18.4
    Operating system:   24.04.1-Ubuntu
    Description:        
    
    Hi all,
    
    I think we can impose stricter restrictions on the parameters of
    `dblink_close`.
    For example, when calling `dblink_close`, certain operations can be achieved
    through SQL concatenation,
    which I believe is unexpected behavior.
    
    ```sql
    postgres@zxm-VMware-Virtual-Platform:~/Z-Xiao-M$ psql
    psql (19devel)
    Type "help" for help.
    
    postgres=# \c test
    You are now connected to database "test" as user "postgres".
    test=# CREATE EXTENSION IF NOT EXISTS dblink;
    CREATE EXTENSION
    test=# SELECT dblink_connect('c', 'dbname=' || current_database());
     dblink_connect
    ----------------
     OK
    (1 row)
    
    test=# SELECT dblink_open('c', 'cur', 'SELECT 1');
     dblink_open
    -------------
     OK
    (1 row)
    
    test=# -- CLOSE: CREATE TABLE
    test=# SELECT dblink_close('c', 'cur; CREATE TABLE hacked(id int); --');
     dblink_close
    --------------
     OK
    (1 row)
    
    test=# \d+ hacked
                                             Table "public.hacked"
     Column |  Type   | Collation | Nullable | Default | Storage | Compression |
    Stats target | Description
    --------+---------+-----------+----------+---------+---------+-------------+--------------+-------------
     id     | integer |           |          |         | plain   |             |
    |
    Access method: heap
    
    test=# SELECT dblink_disconnect('c');
     dblink_disconnect
    -------------------
     OK
    (1 row)
    
    test=# SELECT dblink_connect('c', 'dbname=' || current_database());
     dblink_connect
    ----------------
     OK
    (1 row)
    
    test=# SELECT dblink_open('c', 'cur', 'SELECT 1');
     dblink_open
    -------------
     OK
    (1 row)
    
    test=# -- CLOSE: DROP TABLE
    test=# SELECT dblink_close('c', 'cur; DROP TABLE hacked; --');
     dblink_close
    --------------
     OK
    (1 row)
    
    test=# \d+ hacked
    Did not find any relation named "hacked".
    test=#
    ```
    
    This is my SQL for reproducing the problem.
    ```sql
    CREATE EXTENSION IF NOT EXISTS dblink;
    
    SELECT dblink_connect('c', 'dbname=' || current_database());
    SELECT dblink_open('c', 'cur', 'SELECT 1');
    
     -- CLOSE: CREATE TABLE
    SELECT dblink_close('c', 'cur; CREATE TABLE hacked(id int); --');
    
    SELECT dblink_disconnect('c');
    \d+ hacked
    
    SELECT dblink_connect('c', 'dbname=' || current_database());
    SELECT dblink_open('c', 'cur', 'SELECT 1');
    
     -- CLOSE: DROP TABLE
    SELECT dblink_close('c', 'cur; DROP TABLE hacked; --');
    
    \d+ hacked
    SELECT dblink_disconnect('c');
    ```
    
    The solution to this problem is also very simple.
    ```
    postgres@zxm-VMware-Virtual-Platform:~/code/postgres/contrib$ git diff
    diff --git a/contrib/dblink/dblink.c b/contrib/dblink/dblink.c
    index 9798cb535bc..0a9334aa160 100644
    --- a/contrib/dblink/dblink.c
    +++ b/contrib/dblink/dblink.c
    @@ -543,7 +543,7 @@ dblink_close(PG_FUNCTION_ARGS)
     
            conn = rconn->conn;
     
    -       appendStringInfo(&buf, "CLOSE %s", curname);
    +       appendStringInfo(&buf, "CLOSE %s", quote_ident_cstr(curname));
     
            /* close the cursor */
            res = libpqsrv_exec(conn, buf.data, dblink_we_get_result);
    ```
    
    This is the feedback from the security team.
    ```
    Thanks for your report.  We consider dblink_close() to be caller-trusted,
    and thus this is not considered a security vulnerability.  Feel free to
    resubmit to pgsql-bugs@lists.postgresql.org.
    ```
    
    Any thought?
    --
    regards,
    Man Zeng
    
    
    
    
    
    
  2. Re: BUG #19478: `dblink_close` can be used for injection.

    Japin Li <japinli@hotmail.com> — 2026-05-16T01:24:15Z

    On Fri, 15 May 2026 at 01:29, PG Bug reporting form <noreply@postgresql.org> wrote:
    > The following bug has been logged on the website:
    >
    > Bug reference:      19478
    > Logged by:          Man Zeng
    > Email address:      zengman@halodbtech.com
    > PostgreSQL version: 18.4
    > Operating system:   24.04.1-Ubuntu
    > Description:        
    >
    > Hi all,
    >
    > I think we can impose stricter restrictions on the parameters of
    > `dblink_close`.
    > For example, when calling `dblink_close`, certain operations can be achieved
    > through SQL concatenation,
    > which I believe is unexpected behavior.
    >
    > ```sql
    > postgres@zxm-VMware-Virtual-Platform:~/Z-Xiao-M$ psql
    > psql (19devel)
    > Type "help" for help.
    >
    > postgres=# \c test
    > You are now connected to database "test" as user "postgres".
    > test=# CREATE EXTENSION IF NOT EXISTS dblink;
    > CREATE EXTENSION
    > test=# SELECT dblink_connect('c', 'dbname=' || current_database());
    >  dblink_connect
    > ----------------
    >  OK
    > (1 row)
    >
    > test=# SELECT dblink_open('c', 'cur', 'SELECT 1');
    >  dblink_open
    > -------------
    >  OK
    > (1 row)
    >
    > test=# -- CLOSE: CREATE TABLE
    > test=# SELECT dblink_close('c', 'cur; CREATE TABLE hacked(id int); --');
    >  dblink_close
    > --------------
    >  OK
    > (1 row)
    >
    > test=# \d+ hacked
    >                                          Table "public.hacked"
    >  Column |  Type   | Collation | Nullable | Default | Storage | Compression |
    > Stats target | Description
    > --------+---------+-----------+----------+---------+---------+-------------+--------------+-------------
    >  id     | integer |           |          |         | plain   |             |
    > |
    > Access method: heap
    >
    > test=# SELECT dblink_disconnect('c');
    >  dblink_disconnect
    > -------------------
    >  OK
    > (1 row)
    >
    > test=# SELECT dblink_connect('c', 'dbname=' || current_database());
    >  dblink_connect
    > ----------------
    >  OK
    > (1 row)
    >
    > test=# SELECT dblink_open('c', 'cur', 'SELECT 1');
    >  dblink_open
    > -------------
    >  OK
    > (1 row)
    >
    > test=# -- CLOSE: DROP TABLE
    > test=# SELECT dblink_close('c', 'cur; DROP TABLE hacked; --');
    >  dblink_close
    > --------------
    >  OK
    > (1 row)
    >
    > test=# \d+ hacked
    > Did not find any relation named "hacked".
    > test=#
    > ```
    >
    > This is my SQL for reproducing the problem.
    > ```sql
    > CREATE EXTENSION IF NOT EXISTS dblink;
    >
    > SELECT dblink_connect('c', 'dbname=' || current_database());
    > SELECT dblink_open('c', 'cur', 'SELECT 1');
    >
    >  -- CLOSE: CREATE TABLE
    > SELECT dblink_close('c', 'cur; CREATE TABLE hacked(id int); --');
    >
    > SELECT dblink_disconnect('c');
    > \d+ hacked
    >
    > SELECT dblink_connect('c', 'dbname=' || current_database());
    > SELECT dblink_open('c', 'cur', 'SELECT 1');
    >
    >  -- CLOSE: DROP TABLE
    > SELECT dblink_close('c', 'cur; DROP TABLE hacked; --');
    >
    > \d+ hacked
    > SELECT dblink_disconnect('c');
    > ```
    >
    > The solution to this problem is also very simple.
    > ```
    > postgres@zxm-VMware-Virtual-Platform:~/code/postgres/contrib$ git diff
    > diff --git a/contrib/dblink/dblink.c b/contrib/dblink/dblink.c
    > index 9798cb535bc..0a9334aa160 100644
    > --- a/contrib/dblink/dblink.c
    > +++ b/contrib/dblink/dblink.c
    > @@ -543,7 +543,7 @@ dblink_close(PG_FUNCTION_ARGS)
    >  
    >         conn = rconn->conn;
    >  
    > -       appendStringInfo(&buf, "CLOSE %s", curname);
    > +       appendStringInfo(&buf, "CLOSE %s", quote_ident_cstr(curname));
    >  
    >         /* close the cursor */
    >         res = libpqsrv_exec(conn, buf.data, dblink_we_get_result);
    > ```
    >
    > This is the feedback from the security team.
    > ```
    > Thanks for your report.  We consider dblink_close() to be caller-trusted,
    > and thus this is not considered a security vulnerability.  Feel free to
    > resubmit to pgsql-bugs@lists.postgresql.org.
    > ```
    >
    > Any thought?
    
    According to the documentation [1], it should be a cursor name.  Wrapping it
    in quotes can prevent attacks like SQL injection.  I think your modification
    is correct, and we should add test cases for it.
    
    [1] https://www.postgresql.org/docs/current/contrib-dblink-close.html
    
    > --
    > regards,
    > Man Zeng
    
    -- 
    Regards,
    Japin Li
    ChengDu WenWu Information Technology Co., Ltd.
    
    
    
    
  3. Re: BUG #19478: `dblink_close` can be used for injection.

    Kirill Reshke <reshkekirill@gmail.com> — 2026-05-16T04:06:05Z

    On Sat, 16 May 2026, 06:24 Japin Li, <japinli@hotmail.com> wrote:
    
    > On Fri, 15 May 2026 at 01:29, PG Bug reporting form <
    > noreply@postgresql.org> wrote:
    > > The following bug has been logged on the website:
    > >
    > > Bug reference:      19478
    > > Logged by:          Man Zeng
    > > Email address:      zengman@halodbtech.com
    > > PostgreSQL version: 18.4
    > > Operating system:   24.04.1-Ubuntu
    > > Description:
    > >
    > > Hi all,
    > >
    > > I think we can impose stricter restrictions on the parameters of
    > > `dblink_close`.
    > > For example, when calling `dblink_close`, certain operations can be
    > achieved
    > > through SQL concatenation,
    > > which I believe is unexpected behavior.
    > >
    > > ```sql
    > > postgres@zxm-VMware-Virtual-Platform:~/Z-Xiao-M$ psql
    > > psql (19devel)
    > > Type "help" for help.
    > >
    > > postgres=# \c test
    > > You are now connected to database "test" as user "postgres".
    > > test=# CREATE EXTENSION IF NOT EXISTS dblink;
    > > CREATE EXTENSION
    > > test=# SELECT dblink_connect('c', 'dbname=' || current_database());
    > >  dblink_connect
    > > ----------------
    > >  OK
    > > (1 row)
    > >
    > > test=# SELECT dblink_open('c', 'cur', 'SELECT 1');
    > >  dblink_open
    > > -------------
    > >  OK
    > > (1 row)
    > >
    > > test=# -- CLOSE: CREATE TABLE
    > > test=# SELECT dblink_close('c', 'cur; CREATE TABLE hacked(id int); --');
    > >  dblink_close
    > > --------------
    > >  OK
    > > (1 row)
    > >
    > > test=# \d+ hacked
    > >                                          Table "public.hacked"
    > >  Column |  Type   | Collation | Nullable | Default | Storage |
    > Compression |
    > > Stats target | Description
    > >
    > --------+---------+-----------+----------+---------+---------+-------------+--------------+-------------
    > >  id     | integer |           |          |         | plain   |
    >    |
    > > |
    > > Access method: heap
    > >
    > > test=# SELECT dblink_disconnect('c');
    > >  dblink_disconnect
    > > -------------------
    > >  OK
    > > (1 row)
    > >
    > > test=# SELECT dblink_connect('c', 'dbname=' || current_database());
    > >  dblink_connect
    > > ----------------
    > >  OK
    > > (1 row)
    > >
    > > test=# SELECT dblink_open('c', 'cur', 'SELECT 1');
    > >  dblink_open
    > > -------------
    > >  OK
    > > (1 row)
    > >
    > > test=# -- CLOSE: DROP TABLE
    > > test=# SELECT dblink_close('c', 'cur; DROP TABLE hacked; --');
    > >  dblink_close
    > > --------------
    > >  OK
    > > (1 row)
    > >
    > > test=# \d+ hacked
    > > Did not find any relation named "hacked".
    > > test=#
    > > ```
    > >
    > > This is my SQL for reproducing the problem.
    > > ```sql
    > > CREATE EXTENSION IF NOT EXISTS dblink;
    > >
    > > SELECT dblink_connect('c', 'dbname=' || current_database());
    > > SELECT dblink_open('c', 'cur', 'SELECT 1');
    > >
    > >  -- CLOSE: CREATE TABLE
    > > SELECT dblink_close('c', 'cur; CREATE TABLE hacked(id int); --');
    > >
    > > SELECT dblink_disconnect('c');
    > > \d+ hacked
    > >
    > > SELECT dblink_connect('c', 'dbname=' || current_database());
    > > SELECT dblink_open('c', 'cur', 'SELECT 1');
    > >
    > >  -- CLOSE: DROP TABLE
    > > SELECT dblink_close('c', 'cur; DROP TABLE hacked; --');
    > >
    > > \d+ hacked
    > > SELECT dblink_disconnect('c');
    > > ```
    > >
    > > The solution to this problem is also very simple.
    > > ```
    > > postgres@zxm-VMware-Virtual-Platform:~/code/postgres/contrib$ git diff
    > > diff --git a/contrib/dblink/dblink.c b/contrib/dblink/dblink.c
    > > index 9798cb535bc..0a9334aa160 100644
    > > --- a/contrib/dblink/dblink.c
    > > +++ b/contrib/dblink/dblink.c
    > > @@ -543,7 +543,7 @@ dblink_close(PG_FUNCTION_ARGS)
    > >
    > >         conn = rconn->conn;
    > >
    > > -       appendStringInfo(&buf, "CLOSE %s", curname);
    > > +       appendStringInfo(&buf, "CLOSE %s", quote_ident_cstr(curname));
    > >
    > >         /* close the cursor */
    > >         res = libpqsrv_exec(conn, buf.data, dblink_we_get_result);
    > > ```
    > >
    > > This is the feedback from the security team.
    > > ```
    > > Thanks for your report.  We consider dblink_close() to be caller-trusted,
    > > and thus this is not considered a security vulnerability.  Feel free to
    > > resubmit to pgsql-bugs@lists.postgresql.org.
    > > ```
    > >
    > > Any thought?
    >
    > According to the documentation [1], it should be a cursor name.  Wrapping
    > it
    > in quotes can prevent attacks like SQL injection.  I think your
    > modification
    > is correct, and we should add test cases for it.
    >
    > [1] https://www.postgresql.org/docs/current/contrib-dblink-close.html
    >
    > > --
    > > regards,
    > > Man Zeng
    >
    > --
    > Regards,
    > Japin Li
    > ChengDu WenWu Information Technology Co., Ltd.
    >
    
    
    Well, is there any actual injection? I mean, if user can execute
    dblink_close, then user can do an SQL with dblink_open and simply do a SQL?
    Unless wierd case when we only granted with close function, I guess
    
  4. Re: BUG #19478: `dblink_close` can be used for injection.

    David G. Johnston <david.g.johnston@gmail.com> — 2026-05-16T04:28:56Z

    On Friday, May 15, 2026, Kirill Reshke <reshkekirill@gmail.com> wrote:
    
    >
    >
    > On Sat, 16 May 2026, 06:24 Japin Li, <japinli@hotmail.com> wrote:
    >
    >> On Fri, 15 May 2026 at 01:29, PG Bug reporting form <
    >> noreply@postgresql.org> wrote:
    >> > The following bug has been logged on the website:
    >> >
    >> > Bug reference:      19478
    >> > Logged by:          Man Zeng
    >> > Email address:      zengman@halodbtech.com
    >> > PostgreSQL version: 18.4
    >> > Operating system:   24.04.1-Ubuntu
    >> > Description:
    >> >
    >> >
    >> >
    >> > -       appendStringInfo(&buf, "CLOSE %s", curname);
    >> > +       appendStringInfo(&buf, "CLOSE %s", quote_ident_cstr(curname));
    >> >
    >>
    >>
    >> According to the documentation [1], it should be a cursor name.  Wrapping
    >> it
    >> in quotes can prevent attacks like SQL injection.  I think your
    >> modification
    >> is correct, and we should add test cases for it.
    >>
    >> [1] https://www.postgresql.org/docs/current/contrib-dblink-close.html
    >>
    >
    > Well, is there any actual injection? I mean, if user can execute
    >> dblink_close, then user can do an SQL with dblink_open and simply do a SQL?
    >> Unless wierd case when we only granted with close function, I guess
    >>
    >
    Switching to quote_ident means we no longer lowercase an unquoted input.
    Is this improvement in api design worth the potential breakage?  If so,
    make sure we at least change the dblink_open (and fetch…) code similarly.
    
    I’m disinclined to change this unless it’s shown the only possible use of
    the identifier is within the dblink function arguments where can change all
    uses to quote_identifier.  Even then, inconsistent capitalization still
    might exist.
    
    David J.
    
  5. Re: BUG #19478: `dblink_close` can be used for injection.

    Japin Li <japinli@hotmail.com> — 2026-05-18T03:10:04Z

    On Fri, 15 May 2026 at 21:28, "David G. Johnston" <david.g.johnston@gmail.com> wrote:
    > On Friday, May 15, 2026, Kirill Reshke <reshkekirill@gmail.com> wrote:
    >
    >  On Sat, 16 May 2026, 06:24 Japin Li, <japinli@hotmail.com> wrote:
    >
    >  On Fri, 15 May 2026 at 01:29, PG Bug reporting form <noreply@postgresql.org> wrote:
    >  > The following bug has been logged on the website:
    >  >
    >  > Bug reference:      19478
    >  > Logged by:          Man Zeng
    >  > Email address:      zengman@halodbtech.com
    >  > PostgreSQL version: 18.4
    >  > Operating system:   24.04.1-Ubuntu
    >  > Description:        
    >  >
    >  >
    >  >  
    >  > -       appendStringInfo(&buf, "CLOSE %s", curname);
    >  > +       appendStringInfo(&buf, "CLOSE %s", quote_ident_cstr(curname));
    >  >  
    >
    >  According to the documentation [1], it should be a cursor name.  Wrapping it
    >  in quotes can prevent attacks like SQL injection.  I think your modification
    >  is correct, and we should add test cases for it.
    >
    >  [1] https://www.postgresql.org/docs/current/contrib-dblink-close.html
    >   
    >
    >  Well, is there any actual injection? I mean, if user can execute dblink_close, then user can do an SQL with
    >  dblink_open and simply do a SQL? Unless wierd case when we only granted with close function, I guess
    >
    I think this is similar to SQL injection. However, no actual injection happened.
    
    > Switching to quote_ident means we no longer lowercase an unquoted input. Is this improvement in api design worth the
    > potential breakage?  If so, make sure we at least change the dblink_open (and fetch…) code similarly.
    >
    > I’m disinclined to change this unless it’s shown the only possible use of the identifier is within the dblink function
    > arguments where can change all uses to quote_identifier.  Even then, inconsistent capitalization still might exist.
    >
    
    I don't think the current implementation is acceptable.  Could we restrict the
    cursor name to an identifier characters?
    
    > David J.
    
    -- 
    Regards,
    Japin Li
    ChengDu WenWu Information Technology Co., Ltd.