February 13, 2020·5 min read·engineeringpostgresqlsecurityextensions

PostgreSQL 12.2 repairs extension dependency permissions

An unchecked dependency command could make another user's objects disappear when an extension was dropped. PostgreSQL 12.2 closes that permission gap and repairs several partitioning problems.

A command that changes when an object disappears needs an ownership check. PostgreSQL 12.2 repairs a case where that rule was missing. An unprivileged user could mark someone else's database object as dependent on an extension, then leave its deletion to a person entitled to drop that extension. The dangerous operation could therefore occur during an administrator's later maintenance work. The security advisory identifies this as CVE-2020-1720 and lists affected major versions as 9.6 through 12.

The conditions matter. An extension must be installed, and the attacker needs a way to arrange its removal. The security advisory describes environments where an unprivileged user has CREATE privileges, or where an extension owner can be persuaded to drop one or does so predictably. This is a useful distinction for assessing exposure. Permission to run ordinary queries alone does not describe the whole attack. Extension administration and object ownership interact across separate SQL commands.

I default to PostgreSQL for relational work, and its extension system is part of that preference. That makes this particular bug worth understanding beyond its identifier. A database that knows how objects depend on one another can automate substantial administrative work. The authority to change those relationships has to receive the same scrutiny as the authority to change the objects themselves.

Dependencies determine deletion

PostgreSQL's dependency tracking documentation explains why dropping an object can affect other objects. A foreign key depends on its referenced table. A function depends on its declared argument and result types. PostgreSQL generally refuses a drop that would leave dependent objects behind unless the command requests cascading removal. That protection relies on the dependency information being meaningful.

The distinction between deleting a table and deleting a constraint is also relevant. When a table with a referencing foreign key is dropped with cascading removal, the referencing table can remain while its constraint disappears. Deletion follows the recorded relationships and the kinds of objects involved. Reading only the first object named in a command is therefore insufficient to understand its effects.

This dependency model does not inspect every possible runtime reference. The documentation's SQL function example tracks the function's argument type but does not infer a dependency on a table mentioned inside its string body. A recorded dependency is an administrative relationship with defined consequences. Its presence cannot be treated as a harmless annotation, and its absence does not establish that an application has no runtime reliance on an object.

Extension membership has separate meaning

An extension groups related SQL objects into a managed package. The extension packaging documentation describes installation through a control file and SQL script, with a shared library when needed. PostgreSQL understands the resulting member objects as a group. Dropping the extension removes its members, and an individual member normally cannot be dropped independently.

That packaging also changes backup behaviour. A logical dump normally records a command to create the extension instead of reproducing each member's definition. The destination needs the extension's installation files. Editing an extension member directly therefore has consequences for restoration as well as for the running database. The documentation recommends extension update scripts for production changes to those definitions.

The dependency command involved in this vulnerability is another way to connect an object's lifecycle to an extension. It deserves attention precisely because lifecycle management can be destructive even when no function body or table row changes. I regard explicit ownership checks here as part of the ordinary schema security model. Extension convenience should preserve the boundaries that apply when an administrator manipulates the same objects individually.

The ownership rule now applies

The 12.2 release notes say that marking an object dependent on an extension previously performed no privilege check. Routines, triggers, materialized views and indexes could become droppable by someone entitled to drop the extension. The repair requires the caller to own the object being marked. The release notes credit the fix to Álvaro Herrera.

For a function, the relevant command has this form, assuming the function and extension already exist:

ALTER FUNCTION app.calculate_total(integer)
    DEPENDS ON EXTENSION accounting_support;

The ALTER FUNCTION documentation shipped in the 12.2 source specifies the command's syntax and states the ownership requirement. The argument type identifies the function being altered. An argument name is unnecessary because function identity depends on input argument types. This command changes a dependency relationship, so authorization must apply even though the function's calculation remains unchanged.

The practical review question is who owns the affected object. Permission to invoke a function is a different concern from ownership of its definition. The repair aligns this dependency operation with the documented requirement for altering a function. An administrator dropping an extension should not unknowingly exercise deletion authority over objects that an unrelated user attached without permission.

Updating binaries and checking existing state

The release also requires attention to partitioned tables. The announcement explains that a previous partition-level truncation with cascading removal could leave referencing data behind. Updating prevents the faulty operation in future, but existing data may still need cleanup. That is a different task from installing the patched binaries, and it depends on which operations the database has already performed.

A second partitioning problem involved foreign keys failing to reach newly attached sub-partitions. The release notes describe detaching and reattaching an affected partition, or recreating the foreign key constraint. These remedies address database state created before the update. They explain why a successful restart alone cannot establish that every consequence of the previous bugs has disappeared.

For ordinary installation, the February 13 release announcement says minor updates are cumulative and do not require a dump and reload or pg_upgrade. Shutting down and replacing the binaries applies the update. Administrators who skipped earlier maintenance versions still need to consult their intervening migration instructions. I value that separation between installing a maintenance release and repairing state affected by a specific defect. It makes the remaining work depend on identifiable database operations rather than an assumption that every cluster needs rebuilding.

PostgreSQL 9.4.26 is also the final release of its branch. The same announcement states that it will receive no further security or bug fixes. Installing that last update and moving to a supported major branch are therefore separate maintenance decisions, even though the release notice presents them together.