Re: running logical replication as the subscription owner

From: Robert Haas <robertmhaas(at)gmail(dot)com>
To: Mark Dilger <mark(dot)dilger(at)enterprisedb(dot)com>
Cc: Jeff Davis <pgsql(at)j-davis(dot)com>, "pgsql-hackers(at)postgresql(dot)org" <pgsql-hackers(at)postgresql(dot)org>, Andres Freund <andres(at)anarazel(dot)de>, Noah Misch <noah(at)leadboat(dot)com>
Subject: Re: running logical replication as the subscription owner
Date: 2023-03-27 15:31:47
Message-ID: CA+Tgmob4L4Z2gc=TROjUgdf+oJcKFze59ehLGXNi1Ghsi5OENQ@mail.gmail.com
Views: Raw Message | Whole Thread | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

On Fri, Mar 24, 2023 at 4:11 PM Mark Dilger
<mark(dot)dilger(at)enterprisedb(dot)com> wrote:
> > > Imagine for example that the table
> > > owner has a trigger which doesn't sanitize search_path. The
> > > subscription owner can potentially leverage that to get the table
> > > owner's privileges.
>
> I don't find that terribly convincing. First, there's no reason a subscription owner should be an ordinary user able to volitionally do anything. The subscription owner should just be a role that the subscription runs under, as a means of superuser dropping privileges before applying changes. So the only real problem would be that the changes coming from the publisher might, upon application, hack the table owner. But if that's the case, the table owner's vulnerability on the subscription-database side is equal to their vulnerability on the publication-database side (assuming equal schemas on both). Flagging this vulnerability as being logical replication related seems a category error. Instead, it's a schema vulnerability.

I think there are actually a number of reasons why the subscription
owner should be a regular user account rather than a special
low-privilege account. First, it's only barely possible to do anything
else. As of today, you can't create a subscription owned by a
non-superuser except by making the subscription first and then
removing superuser from the account. You can't even do this:

rhaas=# alter subscription s1 owner to alice;
ERROR: permission denied to change owner of subscription "s1"
HINT: The owner of a subscription must be a superuser.

That hint is actually a lie because of the loophole mentioned above,
but even if making the subscription owner a low-privilege account were
the right model (which I don't believe) we've got error messages in
the current source code saying that you're not allowed to even do it.

Second, even if this kind of setup were fully supported and all the
stuff worked as you expect, it's not very convenient. It requires you
to create this extra dummy account that doesn't otherwise need to
exist. I don't see a good reason to impose that requirement on
everybody. Given that subscriptions initially could only be owned by
superusers, and that's still mostly true, it seems to me that the
feature was intended to be used with the superuser as the subscription
owner, and I think that's what most people must be doing now and will
probably want to continue to do, and we should try to make it safe
instead of back-pedaling and saying, hey, do it this totally other way
instead.

More discussion of problems below.

> > So I think that if we allow user A to replicate into user B's
> > table with fewer privileges than A-can-set-role-to-B, we're building a
> > privilege-escalation attack into the system. But if we do require
> > A-can-set-role-to-B, then things change as described above.
>
> I don't understand the direction this patch is going. I'm emphatically not objecting to it, merely expressing my confusion about it.
>
> I had imagined the solution to the replication security problem was to stop running the replication as superuser, and instead as a trivial user. Imagine that superuser creates roles "deadhead_bob" and "deadhead_alice" which cannot log in, are not members of any groups nor have any other roles as members of themselves, and have no privileges beyond begin able to replicate into bob's and alice's tables, respectively. The superuser sets up the subscriptions disabled, transfers ownership to deadhead_bob and deadhead_alice, and only then enables the subscriptions.
>
> Since deadhead_bob and deadhead_alice cannot log in, and nobody can set role to them, I don't see what the vulnerability is. Sure, maybe alice can attack deadhead_alice, or bob can attack deadhead_bob, but that's more of a privilege deescalation than a privilege escalation, so where's the risk? That's not a rhetorical question. Is there a risk here? Or are we just concerned that most users will set up replication with superuser or some other high-privilege user, and we're trying to protect them from the consequences of that choice?

Having a separate subscription for each different table owner is
extremely undesirable from a performance perspective, because it means
that the WAL on the origin server has to be decoded once per table
owner. That's not very much fun even if the number of table owners is
only two, as in your example, and there could be many more than two
users who own tables. In addition, it breaks the transactional
consistency of replication. If there's any single transaction that
modifies both a table owned by alice and a table owned by bob, we lose
transactional consistency on the subscriber side unless both of those
transactions are replicated in a single transaction, which means they
also need to be part of a single subscription.

I admit that hacking into deadhead_alice or deadhead_bob is probably
only minimally interesting. There are, perhaps, things you could do
with that, like creating objects owned by that user, and maybe your
own account is subject to some kind of restrictions separate from
what's in place on the deadhead account, but most likely in most
circumstances you can't get very far by breaking into a deadhead
account whose only purpose is to replicate changes on tables you
already own. However, because of the problems with having a
subscription per table owner, you're probably really going to need a
shared deadhead account that is used to replicate across all users who
own tables, and now breaking into that account gets a lot more
interesting. Two users that aren't supposed to be able to talk to each
other could use the shared deadhead account to pass data back and
forth, e.g. to exfiltrate data from a top secret account to one with a
lower security classification. Or maybe a malicious user can try to
steal data as it's being replicated, or just mess with the system.
They can create objects owned by the deadhead account in any
publicly-writable schemas, and they can mess with the GUC settings
that apply to the deadhead account, at a minimum.

I basically don't think there's such a thing as an account that is so
low-privilege that we don't care about someone hacking into it. Now,
you can go way down the rabbit hole here and say "well, what if we
invented a SUPER low privilege account that ever create or own objects
and can't use even those privileges that are granted to public and
<insert other restrictions here>." And I admit that could be done, but
that sounds like a lot of work and I see no point. If we just make it
safe for superusers to own subscriptions, then the job's done. And I
see nothing preventing us from doing that. That's the point of this
patch, in fact.

--
Robert Haas
EDB: http://www.enterprisedb.com

In response to

Browse pgsql-hackers by date

  From Date Subject
Next Message Aleksander Alekseev 2023-03-27 16:19:34 Re: [EXTERNAL] Support load balancing in libpq
Previous Message Daniel Gustafsson 2023-03-27 15:23:34 Re: MacOS: xsltproc fails with "warning: failed to load external entity"