Thread

  1. Re: Avoid corrupting DefElem nodes when parsing publication_names and publish options

    Henson Choi <assam258@gmail.com> — 2025-12-28T12:18:12Z

    Analysis of CF 6339: DefElem corruption in publication option parsing
    
    The original report showed that DefElem is corrupted after
    SplitIdentifierString()
    using a debugger. I looked further to see if this corruption actually
    matters,
    and found that the publicationcmds.c case is a real bug—not just an API
    contract violation.
    
    
    The problem
    -----------
    
    The original commit message says both cases "happen to work in practice".
    This is true for pgoutput.c, but not for publicationcmds.c.
    
    In publicationcmds.c, after parse_publication_options() corrupts the DefElem
    string, EventTriggerCollectSimpleCommand() copies the statement using
    copyObject(). Since copyObject() uses pstrdup() internally, it only copies
    up to the first NULL byte—losing everything after the first comma.
    
    Here's the call sequence in AlterPublicationOptions():
    
        parse_publication_options()    <- corrupts defel->arg:
    "insert\0update\0delete"
            ...
        EventTriggerCollectSimpleCommand(stmt)  <- copyObject(stmt) sees
    truncated string
    
    Event trigger functions then receive "insert" instead of
    "insert,update,delete".
    
    
    Memory lifetime
    ---------------
    
    publicationcmds.c:
    - DefElem: allocated in MessageContext, freed when command completes
    - Copy: allocated in EventTriggerContext by copyObject(), freed when
    trigger completes
    - Corruption happens before copyObject(), so the copy is already truncated
    
    pgoutput.c:
    - DefElem: allocated in cmd_context (child of TopMemoryContext)
    - cmd_context is not freed until walsender exits, but the allocation is
    small
      and happens only once per session, so no memory leak concern
    - DefElem itself is never accessed after parsing, so no problem
    
    
    Reproducing the bug
    -------------------
    
    I wrote a small extension that demonstrates this. It provides two functions:
    get_publish_option_with_bug() simulates the corruption,
    get_publish_option_fixed()
    shows the correct behavior.
    
    See attached bug_simulator.tgz.
    
    To test:
    
        $ tar xzf bug_simulator.tgz
        $ cd bug_simulator
        $ make && sudo make install
    
        $ initdb -D /tmp/pgdata
        $ pg_ctl -D /tmp/pgdata start
        $ psql -d postgres
    
        CREATE EXTENSION bug_simulator;
    
        CREATE TABLE bug_test_log (
            id SERIAL PRIMARY KEY,
            command_tag TEXT,
            buggy_result TEXT,
            fixed_result TEXT
        );
    
        CREATE TABLE test_table (id INT PRIMARY KEY);
    
        CREATE FUNCTION test_bug_behavior()
        RETURNS event_trigger LANGUAGE plpgsql AS $$
        DECLARE obj RECORD;
        BEGIN
            FOR obj IN SELECT * FROM pg_event_trigger_ddl_commands()
            LOOP
                INSERT INTO bug_test_log (command_tag, buggy_result,
    fixed_result)
                VALUES (
                    obj.command_tag,
                    get_publish_option_with_bug(obj.command),
                    get_publish_option_fixed(obj.command)
                );
            END LOOP;
        END;
        $$;
    
        CREATE EVENT TRIGGER bug_test_trigger ON ddl_command_end
            WHEN TAG IN ('CREATE PUBLICATION', 'ALTER PUBLICATION')
            EXECUTE FUNCTION test_bug_behavior();
    
        CREATE PUBLICATION test_pub FOR TABLE test_table
            WITH (publish = 'insert,update,delete');
    
        ALTER PUBLICATION test_pub SET (publish = 'insert,update');
    
        SELECT command_tag, buggy_result, fixed_result FROM bug_test_log;
    
    Result:
    
            command_tag     | buggy_result |    fixed_result
        --------------------+--------------+----------------------
         CREATE PUBLICATION | insert       | insert,update,delete
         ALTER PUBLICATION  | insert       | insert,update
    
    
    Conclusion
    ----------
    
    - publicationcmds.c: real bug, affects event triggers
    - pgoutput.c: harmless, just API cleanup
    
    The bug in publicationcmds.c is minor in practice—it only affects users who
    have event triggers on publication DDL and use extensions that extract
    option
    values from pg_ddl_command. This is an uncommon case, but it is a real bug
    nonetheless.
    
    The patch should be applied.
    
    2025년 12월 24일 (수) PM 8:42, sunil s <sunilfeb26@gmail.com>님이 작성:
    
    > Here is the reproduction of this issue.
    >
    > As per the official documentation
    > <https://www.postgresql.org/docs/current/sql-createpublication.html>, creating
    > a publication with the following syntax will corrupt the option
    > list('insert, update, delete')
    >
    > >  CREATE PUBLICATION mypublication FOR ALL TABLES WITH (publish ='insert,
    > update, delete');
    >
    >
    > By attaching a debugger to *parse_publication_options(), *we can verify
    > that the option list is modified after the call to
    > *splitIdentifierString().*
    >
    > NOTE: Using double quotes (" "), the functionality works correctly.
    >
    > Thanks & Regards,
    > Sunil S
    >