Introduce psystem() to replace system()

Started by Jonathan Gonzalez V.13 days ago6 messageshackers
Beta feature

Hackorum builds and tests every patch posted to the lists, not only commitfest submissions. This is Hackorum's own CI rather than the PostgreSQL project's, and it is still under testing - please report anything that looks wrong.

appliessuccessCI history

You can run a PostgreSQL built from this patch straight from Docker, with no checkout and no build:

docker run --rm -p 5432:5432 ghcr.io/hackorum-dev/postgres-patch:t253368
psql -h localhost -U postgres

Built from patchset v1 (message #1), August 23, 2026 at 12:15 AM.

Every patchset is also pushed to a branch of our PostgreSQL fork, so you can check out the same tree CI built. Without a PostgreSQL checkout:

git clone --branch t253368_1 https://github.com/hackorum-dev/postgres.git

In a checkout you already have, add the fork once:

git remote add hackorum https://github.com/hackorum-dev/postgres.git

then, for this patchset and every later one:

git fetch hackorum t253368_1 && git checkout t253368_1

Patchset v1 (message #1) is on t253368_1

Jump to latest
#1Jonathan Gonzalez V.
jonathan@abdiel.eu

Hello!!

For some time I've been wondering why PostgreSQL needs system() calls,
which use a shell that can lead to many problems, and also why it
requires a shell to run a command.

I first started thinking about this when I was trying to run a full
distroless PostgreSQL container. It turns out that isn't possible since
the shell is a requirement, and distroless containers are secure exactly
_because_ there's no shell to execute any command other than the ones
that are meant to be executed.

After some research I found out that using system() has other problems,
like issues related to quoting that are really painful to solve [0]/messages/by-id/7606.1153326421@sss.pgh.pa.us[1]/messages/by-id/CA+TgmobBmWWCgPUd04NGoQ=_XvcidV+sE2F7KChEXfs8KBPg6w@mail.gmail.com,
and also the exit codes control[2]/messages/by-id/21292.1358698487@sss.pgh.pa.us. Both topics have already been
discussed on the list.

But the main argument now for me is security. Not having a shell avoids
any possible PATH injection, missing quoting to escape a command, or new
lines that the shell interprets differently from what you'd expect.

After some thinking I came up with a small interface, which only purpose
is to replace system() calls in a more smooth way using execv() under
the hood. I suppose you could use execl() but I've decided to keep it
simple, leaving the opportunity to expand in the future. I already
implemented one call with `pg_ctl initdb` as an example.

There's an important topic related to using shell versus not a shell. In
some places like `archive_command` people may use `&&`, but this idea
aims to avoid this kind of behavior since it's not secure. Probably we
can implement a way to run commands in sequence, or simply tell the
users that this isn't allowed anymore, but it's possible to trigger
commands in sequence since the interface allows to manipulate the STDIN
and STDOUT.

I would like to open the discussion here if this is the right direction.
There's a lot to do and this still a work in progress, the current patch
is small and simple, but already provides building blocks in this direction.

[0]: /messages/by-id/7606.1153326421@sss.pgh.pa.us
[1]: /messages/by-id/CA+TgmobBmWWCgPUd04NGoQ=_XvcidV+sE2F7KChEXfs8KBPg6w@mail.gmail.com
[2]: /messages/by-id/21292.1358698487@sss.pgh.pa.us

--
Jonathan Gonzalez V.
EDB https://enterprisedb.com

Attachments:

t253368_1
0001-replace-calls-to-system-with-PostgreSQL-own-implemen.patchtext/x-diffDownload+324-75
#2Tom Lane
tgl@sss.pgh.pa.us
In reply to: Jonathan Gonzalez V. (#1)
Re: Introduce psystem() to replace system()

"Jonathan Gonzalez V." <jonathan@abdiel.eu> writes:

For some time I've been wondering why PostgreSQL needs system() calls,
which use a shell that can lead to many problems, and also why it
requires a shell to run a command.

There's a lot to be said for not going through system() if we don't
have to, and I think you are right that there are many places where
we don't have to, if we're willing to write our own stdio-redirection
code (but that might be a bigger can of worms than it seems). That'd
improve security and also performance, though I'm not very sure how
big the latter win would be.

However, I think your apparent ambition to have *zero* use of system()
is a bridge too far. In particular:

There's an important topic related to using shell versus not a shell. In
some places like `archive_command` people may use `&&`, but this idea
aims to avoid this kind of behavior since it's not secure.

I think breaking the existing definition of archive_command and
similar GUCs is a nonstarter. You're going to make many users
unhappy and only a tiny minority happier.

There might be some way to compromise, along the lines of "if the
string contains no shell metacharacters then parse it ourselves and
use execv(), else use system()". The devil's in the details there;
but if it could work then it'd satisfy people who'd like to not
have a shell available and are willing to deal with the ensuing
restrictions. But if you think that description covers all or even
most of our users, I'm here to tell you you're wrong.

As for details ... doesn't this pcommand_count_args thing break
instantly on cases like pathnames containing spaces? I think
you need a much clearer concept (and, um, some documentation)
about the semantics of these functions. I don't think we're
really going to move the goalposts far unless we can get away
from assumptions like that.

regards, tom lane

#3Haibo Yan
tristan.yim@gmail.com
In reply to: Jonathan Gonzalez V. (#1)
Re: Introduce psystem() to replace system()

On Mon, Aug 10, 2026 at 12:28 PM Jonathan Gonzalez V. <jonathan@abdiel.eu>
wrote:

Hello!!

For some time I've been wondering why PostgreSQL needs system() calls,
which use a shell that can lead to many problems, and also why it
requires a shell to run a command.

I first started thinking about this when I was trying to run a full
distroless PostgreSQL container. It turns out that isn't possible since
the shell is a requirement, and distroless containers are secure exactly
_because_ there's no shell to execute any command other than the ones
that are meant to be executed.

After some research I found out that using system() has other problems,
like issues related to quoting that are really painful to solve [0][1],
and also the exit codes control[2]. Both topics have already been
discussed on the list.

But the main argument now for me is security. Not having a shell avoids
any possible PATH injection, missing quoting to escape a command, or new
lines that the shell interprets differently from what you'd expect.

After some thinking I came up with a small interface, which only purpose
is to replace system() calls in a more smooth way using execv() under
the hood. I suppose you could use execl() but I've decided to keep it
simple, leaving the opportunity to expand in the future. I already
implemented one call with `pg_ctl initdb` as an example.

There's an important topic related to using shell versus not a shell. In
some places like `archive_command` people may use `&&`, but this idea
aims to avoid this kind of behavior since it's not secure. Probably we
can implement a way to run commands in sequence, or simply tell the
users that this isn't allowed anymore, but it's possible to trigger
commands in sequence since the interface allows to manipulate the STDIN
and STDOUT.

I would like to open the discussion here if this is the right direction.
There's a lot to do and this still a work in progress, the current patch
is small and simple, but already provides building blocks in this
direction.

Hi Jonathan

Thanks for working on this. I think avoiding the shell for cases where
PostgreSQL is just
invoking a known executable makes sense.

There is some related work here which may be worth looking at:

/messages/by-id/CAGECzQQh6VSy3KG4pN1d=h9J=D1rStFCMR+t7yh_Kwj-g87aLQ@mail.gmail.com

That effort ran into some similar issues around replacing system()/popen()
and handling the
fork/exec boundary.

A few things I noticed in this patch:

1. system() and execv() do not have the same semantics. For internal
commands such as pg_ctl
invoking initdb, that is probably fine and preferable. But for things like
archive_command,
shell features such as PATH lookup, &&, pipes and redirection are part of
the existing
interface, so changing those would be a compatibility decision rather than
just an
implementation change.
2. For pipelines, pcommand_exec() only knows the stdin/stdout/stderr FDs.
After fork(),
however, a child may inherit other pipe endpoints as well. Those need to be
closed before
exec(), otherwise an unused write end can keep a pipe alive and prevent EOF
from being seen.
3. I also wonder about returning errno from pcommand_exec(). An execv()
failure should
probably remain distinguishable from a program that successfully execs and
later exits with
the same numeric status. An error pipe from child to parent may be useful
here.

Overall I like the direction, but I think it would be useful to reuse some
of the lessons
from the earlier pg_system() / pg_popen() work before this grows into a
more general process
/pipeline API.

Regards,
Haibo

Show quoted text

[0] /messages/by-id/7606.1153326421@sss.pgh.pa.us
[1]
/messages/by-id/CA+TgmobBmWWCgPUd04NGoQ=_XvcidV+sE2F7KChEXfs8KBPg6w@mail.gmail.com
[2] /messages/by-id/21292.1358698487@sss.pgh.pa.us

--
Jonathan Gonzalez V.
EDB https://enterprisedb.com

#4Peter Eisentraut
peter_e@gmx.net
In reply to: Tom Lane (#2)
Re: Introduce psystem() to replace system()

On 11.08.26 01:00, Tom Lane wrote:

However, I think your apparent ambition to have*zero* use of system()
is a bridge too far. In particular:

There's an important topic related to using shell versus not a shell. In
some places like `archive_command` people may use `&&`, but this idea
aims to avoid this kind of behavior since it's not secure.

I think breaking the existing definition of archive_command and
similar GUCs is a nonstarter. You're going to make many users
unhappy and only a tiny minority happier.

Right. The direction should be, you don't have to use archive_command.
There is already an alternative in archive_library, or indirectly by
using something like pg_receivewal. Similar alternatives could be
designed for other cases where a shell command appears in a public API.

#5Nathan Bossart
nathandbossart@gmail.com
In reply to: Peter Eisentraut (#4)
Re: Introduce psystem() to replace system()

On Wed, Aug 12, 2026 at 07:05:36PM +0200, Peter Eisentraut wrote:

On 11.08.26 01:00, Tom Lane wrote:

I think breaking the existing definition of archive_command and
similar GUCs is a nonstarter. You're going to make many users
unhappy and only a tiny minority happier.

Right. The direction should be, you don't have to use archive_command.
There is already an alternative in archive_library, or indirectly by using
something like pg_receivewal. Similar alternatives could be designed for
other cases where a shell command appears in a public API.

+1. This conversation makes me wonder about reviving the "restore modules"
work [0]/messages/by-id/flat/20221227192449.GA3672473@nathanxps13.

[0]: /messages/by-id/flat/20221227192449.GA3672473@nathanxps13

--
nathan

#6Jonathan Gonzalez V.
jonathan@abdiel.eu
In reply to: Tom Lane (#2)
Re: Introduce psystem() to replace system()

Hello!

Tom Lane <tgl@sss.pgh.pa.us> writes:

"Jonathan Gonzalez V." <jonathan@abdiel.eu> writes:

For some time I've been wondering why PostgreSQL needs system() calls,
which use a shell that can lead to many problems, and also why it
requires a shell to run a command.

There's a lot to be said for not going through system() if we don't
have to, and I think you are right that there are many places where
we don't have to, if we're willing to write our own stdio-redirection
code (but that might be a bigger can of worms than it seems). That'd
improve security and also performance, though I'm not very sure how
big the latter win would be.

I found a lot of discussions in the past, that's why I thought the idea
will be of interest. The stdio-redirection, it's for now, just something
for send stuff to DEVNULL, probably it can evolve in future patches, but
for now, I think that for the silent stuff it's enough, because the
redirection stuff will open the door to manage some piping and the idea
it's to remove those kind of needs too.

In terms of security, there is an improvement, but for performance, I wasn't
able to establish a base line or even what to measure, but I can imagine
that people here may have more ideas about what and how to measure it.

However, I think your apparent ambition to have *zero* use of system()
is a bridge too far. In particular:

There's an important topic related to using shell versus not a shell. In
some places like `archive_command` people may use `&&`, but this idea
aims to avoid this kind of behavior since it's not secure.

I think breaking the existing definition of archive_command and
similar GUCs is a nonstarter. You're going to make many users
unhappy and only a tiny minority happier.

There might be some way to compromise, along the lines of "if the
string contains no shell metacharacters then parse it ourselves and
use execv(), else use system()". The devil's in the details there;
but if it could work then it'd satisfy people who'd like to not
have a shell available and are willing to deal with the ensuing
restrictions. But if you think that description covers all or even
most of our users, I'm here to tell you you're wrong.

I'm pretty sure it will not, but I open the door to think about it and
keep I'm trying to keep in mind those situations when thinking about the
design of the patches.

As for details ... doesn't this pcommand_count_args thing break
instantly on cases like pathnames containing spaces? I think
you need a much clearer concept (and, um, some documentation)
about the semantics of these functions. I don't think we're
really going to move the goalposts far unless we can get away
from assumptions like that.

Well, that function it's designed in case we should keep compatibility
with system(), I'm not happy with that idea but it may be required, but
if we can avoid having that from the beginning I'm more than happy with
this.

With all that said, I'll start putting some documentation and clarify
the concepts for this and have a better v2 version.

Regards!
--
Jonathan Gonzalez V.
EDB
https://www.enterprisedb.com