mirror of
https://github.com/systemd/systemd
synced 2024-07-21 18:24:38 +00:00
Merge pull request #31242 from poettering/socket-uid-account
pid1: make MaxConnectionsPerSource= do something useful on AF_UNIX sockets
This commit is contained in:
commit
8b68a199c2
|
@ -470,9 +470,10 @@
|
|||
|
||||
<varlistentry>
|
||||
<term><varname>MaxConnectionsPerSource=</varname></term>
|
||||
<listitem><para>The maximum number of connections for a service per source IP address.
|
||||
This is very similar to the <varname>MaxConnections=</varname> directive
|
||||
above. Disabled by default.</para>
|
||||
<listitem><para>The maximum number of connections for a service per source IP address (in case of
|
||||
IPv4/IPv6), per source CID (in case of <constant>AF_VSOCK</constant>), or source UID (in case of
|
||||
<constant>AF_UNIX</constant>). This is very similar to the <varname>MaxConnections=</varname>
|
||||
directive above. Disabled by default.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v232"/>
|
||||
</listitem>
|
||||
|
|
|
@ -53,6 +53,7 @@ struct SocketPeer {
|
|||
Socket *socket;
|
||||
union sockaddr_union peer;
|
||||
socklen_t peer_salen;
|
||||
struct ucred peer_cred;
|
||||
};
|
||||
|
||||
static const UnitActiveState state_translation_table[_SOCKET_STATE_MAX] = {
|
||||
|
@ -420,6 +421,8 @@ static void peer_address_hash_func(const SocketPeer *s, struct siphash *state) {
|
|||
siphash24_compress_typesafe(s->peer.in6.sin6_addr, state);
|
||||
else if (s->peer.sa.sa_family == AF_VSOCK)
|
||||
siphash24_compress_typesafe(s->peer.vm.svm_cid, state);
|
||||
else if (s->peer.sa.sa_family == AF_UNIX)
|
||||
siphash24_compress_typesafe(s->peer_cred.uid, state);
|
||||
else
|
||||
assert_not_reached();
|
||||
}
|
||||
|
@ -438,6 +441,8 @@ static int peer_address_compare_func(const SocketPeer *x, const SocketPeer *y) {
|
|||
return memcmp(&x->peer.in6.sin6_addr, &y->peer.in6.sin6_addr, sizeof(x->peer.in6.sin6_addr));
|
||||
case AF_VSOCK:
|
||||
return CMP(x->peer.vm.svm_cid, y->peer.vm.svm_cid);
|
||||
case AF_UNIX:
|
||||
return CMP(x->peer_cred.uid, y->peer_cred.uid);
|
||||
}
|
||||
assert_not_reached();
|
||||
}
|
||||
|
@ -466,16 +471,22 @@ static int socket_load(Unit *u) {
|
|||
return socket_verify(s);
|
||||
}
|
||||
|
||||
static SocketPeer *socket_peer_new(void) {
|
||||
static SocketPeer *socket_peer_dup(const SocketPeer *q) {
|
||||
SocketPeer *p;
|
||||
|
||||
assert(q);
|
||||
|
||||
p = new(SocketPeer, 1);
|
||||
if (!p)
|
||||
return NULL;
|
||||
|
||||
*p = (SocketPeer) {
|
||||
.n_ref = 1,
|
||||
.peer = q->peer,
|
||||
.peer_salen = q->peer_salen,
|
||||
.peer_cred = q->peer_cred,
|
||||
};
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
|
@ -492,8 +503,9 @@ DEFINE_TRIVIAL_REF_UNREF_FUNC(SocketPeer, socket_peer, socket_peer_free);
|
|||
|
||||
int socket_acquire_peer(Socket *s, int fd, SocketPeer **ret) {
|
||||
_cleanup_(socket_peer_unrefp) SocketPeer *remote = NULL;
|
||||
SocketPeer sa = {
|
||||
SocketPeer key = {
|
||||
.peer_salen = sizeof(union sockaddr_union),
|
||||
.peer_cred = UCRED_INVALID,
|
||||
}, *i;
|
||||
int r;
|
||||
|
||||
|
@ -501,27 +513,36 @@ int socket_acquire_peer(Socket *s, int fd, SocketPeer **ret) {
|
|||
assert(s);
|
||||
assert(ret);
|
||||
|
||||
if (getpeername(fd, &sa.peer.sa, &sa.peer_salen) < 0)
|
||||
if (getpeername(fd, &key.peer.sa, &key.peer_salen) < 0)
|
||||
return log_unit_error_errno(UNIT(s), errno, "getpeername() failed: %m");
|
||||
|
||||
if (!IN_SET(sa.peer.sa.sa_family, AF_INET, AF_INET6, AF_VSOCK)) {
|
||||
switch (key.peer.sa.sa_family) {
|
||||
case AF_INET:
|
||||
case AF_INET6:
|
||||
case AF_VSOCK:
|
||||
break;
|
||||
|
||||
case AF_UNIX:
|
||||
r = getpeercred(fd, &key.peer_cred);
|
||||
if (r < 0)
|
||||
return log_unit_error_errno(UNIT(s), r, "Failed to get peer credentials of socket: %m");
|
||||
break;
|
||||
|
||||
default:
|
||||
*ret = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
i = set_get(s->peers_by_address, &sa);
|
||||
i = set_get(s->peers_by_address, &key);
|
||||
if (i) {
|
||||
*ret = socket_peer_ref(i);
|
||||
return 1;
|
||||
}
|
||||
|
||||
remote = socket_peer_new();
|
||||
remote = socket_peer_dup(&key);
|
||||
if (!remote)
|
||||
return log_oom();
|
||||
|
||||
remote->peer = sa.peer;
|
||||
remote->peer_salen = sa.peer_salen;
|
||||
|
||||
r = set_ensure_put(&s->peers_by_address, &peer_address_hash_ops, remote);
|
||||
if (r < 0)
|
||||
return log_unit_error_errno(UNIT(s), r, "Failed to insert peer info into hash table: %m");
|
||||
|
@ -2300,7 +2321,10 @@ static void socket_enter_running(Socket *s, int cfd_in) {
|
|||
if (r > 0 && p->n_ref > s->max_connections_per_source) {
|
||||
_cleanup_free_ char *t = NULL;
|
||||
|
||||
(void) sockaddr_pretty(&p->peer.sa, p->peer_salen, true, false, &t);
|
||||
if (p->peer.sa.sa_family == AF_UNIX)
|
||||
(void) asprintf(&t, "UID " UID_FMT, p->peer_cred.uid);
|
||||
else
|
||||
(void) sockaddr_pretty(&p->peer.sa, p->peer_salen, /* translate_ipv6= */ true, /* include_port= */ false, &t);
|
||||
|
||||
log_unit_warning(UNIT(s),
|
||||
"Too many incoming connections (%u) from source %s, dropping connection.",
|
||||
|
|
75
test/units/testsuite-74.socket.sh
Executable file
75
test/units/testsuite-74.socket.sh
Executable file
|
@ -0,0 +1,75 @@
|
|||
#!/usr/bin/env bash
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
# shellcheck disable=SC2016
|
||||
set -eux
|
||||
set -o pipefail
|
||||
|
||||
# shellcheck source=test/units/util.sh
|
||||
. "$(dirname "$0")"/util.sh
|
||||
|
||||
at_exit() {
|
||||
systemctl stop per-source-limit.socket
|
||||
rm -f /run/systemd/system/per-source-limit.socket /run/systemd/system/per-source-limit@.service
|
||||
rm -f /tmp/foo.conn1 /tmp/foo.conn2 /tmp/foo.conn3 /tmp/foo.conn4
|
||||
systemctl daemon-reload
|
||||
}
|
||||
|
||||
trap at_exit EXIT
|
||||
|
||||
cat > /run/systemd/system/per-source-limit.socket <<EOF
|
||||
[Socket]
|
||||
ListenStream=/run/per-source-limit.sk
|
||||
MaxConnectionsPerSource=2
|
||||
Accept=yes
|
||||
EOF
|
||||
|
||||
cat > /run/systemd/system/per-source-limit@.service <<EOF
|
||||
[Unit]
|
||||
BindsTo=per-source-limit.socket
|
||||
After=per-source-limit.socket
|
||||
|
||||
[Service]
|
||||
ExecStartPre=echo waldo
|
||||
ExecStart=sleep infinity
|
||||
StandardOutput=socket
|
||||
EOF
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl start per-source-limit.socket
|
||||
|
||||
# So these two should take up the first two connection slots
|
||||
socat - UNIX-CONNECT:/run/per-source-limit.sk > /tmp/foo.conn1 &
|
||||
J1="$!"
|
||||
socat - UNIX-CONNECT:/run/per-source-limit.sk > /tmp/foo.conn2 &
|
||||
J2="$!"
|
||||
|
||||
waitfor() {
|
||||
while ! grep -q "waldo" "$1" ; do
|
||||
sleep .2
|
||||
done
|
||||
}
|
||||
|
||||
# Wait until the word "waldo" shows in the output files
|
||||
waitfor /tmp/foo.conn1
|
||||
waitfor /tmp/foo.conn2
|
||||
|
||||
# The next connection should fail, because the limit is hit
|
||||
socat - UNIX-CONNECT:/run/per-source-limit.sk > /tmp/foo.conn3 &
|
||||
J3="$!"
|
||||
|
||||
# But this one should work, because done under a differen UID
|
||||
setpriv --reuid=1 socat - UNIX-CONNECT:/run/per-source-limit.sk > /tmp/foo.conn4 &
|
||||
J4="$!"
|
||||
|
||||
waitfor /tmp/foo.conn4
|
||||
|
||||
# The third job should fail quickly, wait for it
|
||||
wait "$J3"
|
||||
|
||||
# The other jobs will hang forever, since we run "sleep infinity" on the server side. Let's kill the jobs now.
|
||||
kill "$J1"
|
||||
kill "$J2"
|
||||
kill "$J4"
|
||||
|
||||
# The 3rd connection should not have seen "waldo", since it should have been refused too early
|
||||
(! grep -q "waldo" /tmp/foo.conn3 )
|
|
@ -19,3 +19,4 @@ ListenSequentialPacket=/run/systemd/coredump
|
|||
SocketMode=0600
|
||||
Accept=yes
|
||||
MaxConnections=16
|
||||
MaxConnectionsPerSource=8
|
||||
|
|
|
@ -18,3 +18,4 @@ ListenStream=/run/systemd/io.systemd.Credentials
|
|||
FileDescriptorName=varlink
|
||||
SocketMode=0666
|
||||
Accept=yes
|
||||
MaxConnectionsPerSource=16
|
||||
|
|
|
@ -20,6 +20,7 @@ ListenStream=/run/systemd/io.systemd.PCRExtend
|
|||
FileDescriptorName=varlink
|
||||
SocketMode=0600
|
||||
Accept=yes
|
||||
MaxConnectionsPerSource=16
|
||||
|
||||
[Install]
|
||||
WantedBy=sockets.target
|
||||
|
|
|
@ -20,6 +20,7 @@ ListenStream=/run/systemd/io.systemd.sysext
|
|||
FileDescriptorName=varlink
|
||||
SocketMode=0600
|
||||
Accept=yes
|
||||
MaxConnectionsPerSource=16
|
||||
|
||||
[Install]
|
||||
WantedBy=sockets.target
|
||||
|
|
Loading…
Reference in a new issue