Address various spoofing attacks (CVE-2025-40778)
https://kb.isc.org/docs/cve-2025-40778 Modified upstream patch adapted to 9.16 Resolves: RHEL-123318
This commit is contained in:
parent
dbbedd4d99
commit
a7abeea3c3
469
bind-9.18-CVE-2025-40778.patch
Normal file
469
bind-9.18-CVE-2025-40778.patch
Normal file
@ -0,0 +1,469 @@
|
||||
From 83cc066e52542d3f52db98b02da33121b316f4be Mon Sep 17 00:00:00 2001
|
||||
From: Mark Andrews <marka@isc.org>
|
||||
Date: Thu, 10 Jul 2025 09:37:36 +1000
|
||||
Subject: [PATCH] Tighten restrictions on caching NS RRsets in authority
|
||||
section
|
||||
|
||||
To prevent certain spoofing attacks, a new check has been added
|
||||
to the existing rules for whether NS data can be cached: the owner
|
||||
name of the NS RRset must be an ancestor of the name being queried.
|
||||
|
||||
(cherry picked from commit fa153f791f9324bf84abf8d259e11c0531fe6e25)
|
||||
(cherry picked from commit 025d61bacd0f57f994a631654aff7a933d89a547)
|
||||
|
||||
Further restrict addresses that are cached when processing referrals
|
||||
|
||||
Use the owner name of the NS record as the bailwick apex name
|
||||
when determining which additional records to cache, rather than
|
||||
the name of the delegating zone (or a parent thereof).
|
||||
|
||||
(cherry picked from commit a41054e9e606a61f1b3c8bc0c54e2f1059347165)
|
||||
(cherry picked from commit cd17dfe696cdf9b8ef23fbc8738de7c79f957846)
|
||||
|
||||
Retry lookups with unsigned DNAME over TCP
|
||||
|
||||
To prevent spoofed unsigned DNAME responses being accepted retry
|
||||
response with unsigned DNAMEs over TCP if the response is not TSIG
|
||||
signed or there isn't a good DNS CLIENT COOKIE.
|
||||
|
||||
To prevent test failures, this required adding TCP support to the
|
||||
ans3 and ans4 servers in the chain system test.
|
||||
|
||||
(cherry picked from commit 2e40705c06831988106335ed77db3cf924d431f6)
|
||||
(cherry picked from commit 4c6d03b0bb2ffbafcde8e8a5bc0e49908b978a72)
|
||||
---
|
||||
bin/tests/system/chain/ans4/ans.py | 58 +++++++++++++---
|
||||
lib/dns/include/dns/message.h | 8 +++
|
||||
lib/dns/message.c | 12 ++++
|
||||
lib/dns/resolver.c | 108 ++++++++++++++++++++++++-----
|
||||
lib/dns/validator.c | 33 ++++++---
|
||||
5 files changed, 180 insertions(+), 39 deletions(-)
|
||||
|
||||
diff --git a/bin/tests/system/chain/ans4/ans.py b/bin/tests/system/chain/ans4/ans.py
|
||||
index 45d6504..e4fc15a 100755
|
||||
--- a/bin/tests/system/chain/ans4/ans.py
|
||||
+++ b/bin/tests/system/chain/ans4/ans.py
|
||||
@@ -276,16 +276,30 @@ except: port=5300
|
||||
try: ctrlport=int(os.environ['EXTRAPORT1'])
|
||||
except: ctrlport=5300
|
||||
|
||||
-query4_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
-query4_socket.bind((ip4, port))
|
||||
+query4_udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
+query4_udp.bind((ip4, port))
|
||||
+
|
||||
+query4_tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
+query4_tcp.bind((ip4, port))
|
||||
+query4_tcp.listen(1)
|
||||
+query4_tcp.settimeout(1)
|
||||
|
||||
havev6 = True
|
||||
try:
|
||||
- query6_socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
|
||||
+ query6_udp = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
|
||||
+ try:
|
||||
+ query6_udp.bind((ip6, port))
|
||||
+ except:
|
||||
+ query6_udp.close()
|
||||
+ havev6 = False
|
||||
+
|
||||
+ query6_tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
try:
|
||||
- query6_socket.bind((ip6, port))
|
||||
+ query6_tcp.bind((ip4, port))
|
||||
+ query6_tcp.listen(1)
|
||||
+ query6_tcp.settimeout(1)
|
||||
except:
|
||||
- query6_socket.close()
|
||||
+ query6_tcp.close()
|
||||
havev6 = False
|
||||
except:
|
||||
havev6 = False
|
||||
@@ -310,9 +324,9 @@ print ("Control channel on %s port %d" % (ip4, ctrlport))
|
||||
print ("Ctrl-c to quit")
|
||||
|
||||
if havev6:
|
||||
- input = [query4_socket, query6_socket, ctrl_socket]
|
||||
+ input = [query4_udp, query4_tcp, query6_udp, query6_tcp, ctrl_socket]
|
||||
else:
|
||||
- input = [query4_socket, ctrl_socket]
|
||||
+ input = [query4_udp, query4_tcp, ctrl_socket]
|
||||
|
||||
while running:
|
||||
try:
|
||||
@@ -335,13 +349,37 @@ while running:
|
||||
break
|
||||
ctl_channel(msg)
|
||||
conn.close()
|
||||
- if s == query4_socket or s == query6_socket:
|
||||
- print ("Query received on %s" %
|
||||
- (ip4 if s == query4_socket else ip6))
|
||||
+ elif s == query4_udp or s == query6_udp:
|
||||
+ print("Query received on %s" % (ip4 if s == query4_udp else ip6))
|
||||
# Handle incoming queries
|
||||
msg = s.recvfrom(65535)
|
||||
rsp = create_response(msg[0])
|
||||
if rsp:
|
||||
s.sendto(rsp, msg[1])
|
||||
+ elif s == query4_tcp or s == query6_tcp:
|
||||
+ try:
|
||||
+ conn, _ = s.accept()
|
||||
+ if s == query4_tcp or s == query6_tcp:
|
||||
+ print(
|
||||
+ "TCP Query received on %s" % (ip4 if s == query4_tcp else ip6),
|
||||
+ end=" ",
|
||||
+ )
|
||||
+ # get TCP message length
|
||||
+ msg = conn.recv(2)
|
||||
+ if len(msg) != 2:
|
||||
+ print("couldn't read TCP message length")
|
||||
+ continue
|
||||
+ length = struct.unpack(">H", msg[:2])[0]
|
||||
+ msg = conn.recv(length)
|
||||
+ if len(msg) != length:
|
||||
+ print("couldn't read TCP message")
|
||||
+ continue
|
||||
+ rsp = create_response(msg)
|
||||
+ if rsp:
|
||||
+ conn.send(struct.pack(">H", len(rsp)))
|
||||
+ conn.send(rsp)
|
||||
+ conn.close()
|
||||
+ except socket.error as e:
|
||||
+ print("error: %s" % str(e))
|
||||
if not running:
|
||||
break
|
||||
diff --git a/lib/dns/include/dns/message.h b/lib/dns/include/dns/message.h
|
||||
index 68c13ee..d321a58 100644
|
||||
--- a/lib/dns/include/dns/message.h
|
||||
+++ b/lib/dns/include/dns/message.h
|
||||
@@ -233,6 +233,7 @@ struct dns_message {
|
||||
unsigned int cc_bad : 1;
|
||||
unsigned int tkey : 1;
|
||||
unsigned int rdclass_set : 1;
|
||||
+ unsigned int has_dname : 1;
|
||||
|
||||
unsigned int opt_reserved;
|
||||
unsigned int sig_reserved;
|
||||
@@ -1449,4 +1450,11 @@ dns_message_clonebuffer(dns_message_t *msg);
|
||||
* \li msg be a valid message.
|
||||
*/
|
||||
|
||||
+bool
|
||||
+dns_message_hasdname(dns_message_t *msg);
|
||||
+/*%<
|
||||
+ * Return whether a DNAME was detected in the ANSWER section of a QUERY
|
||||
+ * message when it was parsed.
|
||||
+ */
|
||||
+
|
||||
ISC_LANG_ENDDECLS
|
||||
diff --git a/lib/dns/message.c b/lib/dns/message.c
|
||||
index 04315bc..aa434a7 100644
|
||||
--- a/lib/dns/message.c
|
||||
+++ b/lib/dns/message.c
|
||||
@@ -438,6 +438,7 @@ msginit(dns_message_t *m) {
|
||||
m->cc_bad = 0;
|
||||
m->tkey = 0;
|
||||
m->rdclass_set = 0;
|
||||
+ m->has_dname = 0;
|
||||
m->querytsig = NULL;
|
||||
m->indent.string = "\t";
|
||||
m->indent.count = 0;
|
||||
@@ -1717,6 +1718,11 @@ getsection(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx,
|
||||
*/
|
||||
msg->tsigname->attributes |= DNS_NAMEATTR_NOCOMPRESS;
|
||||
free_name = false;
|
||||
+ } else if (rdtype == dns_rdatatype_dname &&
|
||||
+ sectionid == DNS_SECTION_ANSWER &&
|
||||
+ msg->opcode == dns_opcode_query)
|
||||
+ {
|
||||
+ msg->has_dname = 1;
|
||||
}
|
||||
rdataset = NULL;
|
||||
|
||||
@@ -4750,3 +4756,9 @@ dns_message_clonebuffer(dns_message_t *msg) {
|
||||
msg->free_query = 1;
|
||||
}
|
||||
}
|
||||
+
|
||||
+bool
|
||||
+dns_message_hasdname(dns_message_t *msg) {
|
||||
+ REQUIRE(DNS_MESSAGE_VALID(msg));
|
||||
+ return msg->has_dname;
|
||||
+}
|
||||
diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c
|
||||
index ad0a1b8..c284b67 100644
|
||||
--- a/lib/dns/resolver.c
|
||||
+++ b/lib/dns/resolver.c
|
||||
@@ -751,6 +751,7 @@ typedef struct respctx {
|
||||
bool get_nameservers; /* get a new NS rrset at
|
||||
* zone cut? */
|
||||
bool resend; /* resend this query? */
|
||||
+ bool secured; /* message was signed or had a valid cookie */
|
||||
bool nextitem; /* invalid response; keep
|
||||
* listening for the correct one */
|
||||
bool truncated; /* response was truncated */
|
||||
@@ -7155,7 +7156,8 @@ mark_related(dns_name_t *name, dns_rdataset_t *rdataset, bool external,
|
||||
* locally served zone.
|
||||
*/
|
||||
static inline bool
|
||||
-name_external(const dns_name_t *name, dns_rdatatype_t type, fetchctx_t *fctx) {
|
||||
+name_external(const dns_name_t *name, dns_rdatatype_t type, respctx_t *rctx) {
|
||||
+ fetchctx_t *fctx = rctx->fctx;
|
||||
isc_result_t result;
|
||||
dns_forwarders_t *forwarders = NULL;
|
||||
dns_fixedname_t fixed, zfixed;
|
||||
@@ -7167,7 +7169,8 @@ name_external(const dns_name_t *name, dns_rdatatype_t type, fetchctx_t *fctx) {
|
||||
unsigned int labels;
|
||||
dns_namereln_t rel;
|
||||
|
||||
- apex = ISFORWARDER(fctx->addrinfo) ? fctx->fwdname : &fctx->domain;
|
||||
+ apex = ISFORWARDER(fctx->addrinfo) ? fctx->fwdname :
|
||||
+ (rctx->ns_name != NULL) ? rctx->ns_name : &fctx->domain;
|
||||
|
||||
/*
|
||||
* The name is outside the queried namespace.
|
||||
@@ -7275,7 +7278,7 @@ check_section(void *arg, const dns_name_t *addname, dns_rdatatype_t type,
|
||||
result = dns_message_findname(rctx->query->rmessage, section, addname,
|
||||
dns_rdatatype_any, 0, &name, NULL);
|
||||
if (result == ISC_R_SUCCESS) {
|
||||
- external = name_external(name, type, fctx);
|
||||
+ external = name_external(name, type, rctx);
|
||||
if (type == dns_rdatatype_a) {
|
||||
for (rdataset = ISC_LIST_HEAD(name->list);
|
||||
rdataset != NULL;
|
||||
@@ -7884,6 +7887,47 @@ betterreferral(respctx_t *rctx) {
|
||||
return (false);
|
||||
}
|
||||
|
||||
+static bool
|
||||
+rctx_need_tcpretry(respctx_t *rctx) {
|
||||
+ resquery_t *query = rctx->query;
|
||||
+ if ((rctx->retryopts & DNS_FETCHOPT_TCP) != 0) {
|
||||
+ /* TCP is already in the retry flags */
|
||||
+ return false;
|
||||
+ }
|
||||
+
|
||||
+ /*
|
||||
+ * If the message was secured, no need to continue.
|
||||
+ */
|
||||
+ if (rctx->secured) {
|
||||
+ return false;
|
||||
+ }
|
||||
+
|
||||
+ /*
|
||||
+ * Currently the only extra reason why we might need to
|
||||
+ * retry a UDP response over TCP is a DNAME in the message.
|
||||
+ */
|
||||
+ if (dns_message_hasdname(query->rmessage)) {
|
||||
+ return true;
|
||||
+ }
|
||||
+
|
||||
+ return false;
|
||||
+}
|
||||
+
|
||||
+static isc_result_t
|
||||
+rctx_tcpretry(respctx_t *rctx) {
|
||||
+ /*
|
||||
+ * Do we need to retry a UDP response over TCP?
|
||||
+ */
|
||||
+ if (rctx_need_tcpretry(rctx)) {
|
||||
+ rctx->retryopts |= DNS_FETCHOPT_TCP;
|
||||
+ rctx->resend = true;
|
||||
+ rctx_done(rctx, ISC_R_SUCCESS);
|
||||
+ return ISC_R_COMPLETE;
|
||||
+ }
|
||||
+
|
||||
+ return ISC_R_SUCCESS;
|
||||
+}
|
||||
+
|
||||
/*
|
||||
* resquery_response():
|
||||
* Handles responses received in response to iterative queries sent by
|
||||
@@ -8069,15 +8113,23 @@ resquery_response(isc_task_t *task, isc_event_t *event) {
|
||||
* ensured by the dispatch code).
|
||||
*/
|
||||
|
||||
+ /*
|
||||
+ * Remember whether this message was signed or had a
|
||||
+ * valid client cookie; if not, we may need to retry over
|
||||
+ * TCP later.
|
||||
+ */
|
||||
+ if (query->rmessage->cc_ok || query->rmessage->tsig != NULL ||
|
||||
+ query->rmessage->sig0 != NULL)
|
||||
+ {
|
||||
+ rctx.secured = true;
|
||||
+ }
|
||||
+
|
||||
/*
|
||||
* If we have had a server cookie and don't get one retry over TCP.
|
||||
* This may be a misconfigured anycast server or an attempt to send
|
||||
* a spoofed response. Skip if we have a valid tsig.
|
||||
*/
|
||||
- if (dns_message_gettsig(query->rmessage, NULL) == NULL &&
|
||||
- !query->rmessage->cc_ok && !query->rmessage->cc_bad &&
|
||||
- (rctx.retryopts & DNS_FETCHOPT_TCP) == 0)
|
||||
- {
|
||||
+ if (!rctx.secured && (rctx.retryopts & DNS_FETCHOPT_TCP) == 0) {
|
||||
unsigned char cookie[COOKIE_BUFFER_SIZE];
|
||||
if (dns_adb_getcookie(fctx->adb, query->addrinfo, cookie,
|
||||
sizeof(cookie)) > CLIENT_COOKIE_SIZE)
|
||||
@@ -8103,6 +8155,17 @@ resquery_response(isc_task_t *task, isc_event_t *event) {
|
||||
*/
|
||||
}
|
||||
|
||||
+ /*
|
||||
+ * Check whether we need to retry over TCP for some other reason.
|
||||
+ */
|
||||
+ result = rctx_tcpretry(&rctx);
|
||||
+ if (result == ISC_R_COMPLETE) {
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ /*
|
||||
+ * Check for EDNS issues.
|
||||
+ */
|
||||
rctx_edns(&rctx);
|
||||
|
||||
/*
|
||||
@@ -8830,8 +8893,8 @@ rctx_answer_positive(respctx_t *rctx) {
|
||||
}
|
||||
|
||||
/*
|
||||
- * Cache records in the authority section, if
|
||||
- * there are any suitable for caching.
|
||||
+ * Cache records in the authority section, if there are
|
||||
+ * any suitable for caching.
|
||||
*/
|
||||
rctx_authority_positive(rctx);
|
||||
|
||||
@@ -8902,7 +8965,7 @@ rctx_answer_scan(respctx_t *rctx) {
|
||||
/*
|
||||
* Don't accept DNAME from parent namespace.
|
||||
*/
|
||||
- if (name_external(name, dns_rdatatype_dname, fctx)) {
|
||||
+ if (name_external(name, dns_rdatatype_dname, rctx)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -9200,14 +9263,14 @@ rctx_answer_dname(respctx_t *rctx) {
|
||||
|
||||
/*
|
||||
* rctx_authority_positive():
|
||||
- * Examine the records in the authority section (if there are any) for a
|
||||
- * positive answer. We expect the names for all rdatasets in this section
|
||||
- * to be subdomains of the domain being queried; any that are not are
|
||||
- * skipped. We expect to find only *one* owner name; any names
|
||||
- * after the first one processed are ignored. We expect to find only
|
||||
- * rdatasets of type NS, RRSIG, or SIG; all others are ignored. Whatever
|
||||
- * remains can be cached at trust level authauthority or additional
|
||||
- * (depending on whether the AA bit was set on the answer).
|
||||
+ * If a positive answer was received over TCP or secured with a cookie
|
||||
+ * or TSIG, examine the authority section. We expect names for all
|
||||
+ * rdatasets in this section to be subdomains of the domain being queried;
|
||||
+ * any that are not are skipped. We expect to find only *one* owner name;
|
||||
+ * any names after the first one processed are ignored. We expect to find
|
||||
+ * only rdatasets of type NS; all others are ignored. Whatever remains can
|
||||
+ * be cached at trust level authauthority or additional (depending on
|
||||
+ * whether the AA bit was set on the answer).
|
||||
*/
|
||||
static void
|
||||
rctx_authority_positive(respctx_t *rctx) {
|
||||
@@ -9215,6 +9278,11 @@ rctx_authority_positive(respctx_t *rctx) {
|
||||
bool done = false;
|
||||
isc_result_t result;
|
||||
|
||||
+ /* If it's spoofable, don't cache it. */
|
||||
+ if (!rctx->secured && (rctx->query->options & DNS_FETCHOPT_TCP) == 0) {
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
result = dns_message_firstname(rctx->query->rmessage,
|
||||
DNS_SECTION_AUTHORITY);
|
||||
while (!done && result == ISC_R_SUCCESS) {
|
||||
@@ -9223,7 +9291,9 @@ rctx_authority_positive(respctx_t *rctx) {
|
||||
dns_message_currentname(rctx->query->rmessage,
|
||||
DNS_SECTION_AUTHORITY, &name);
|
||||
|
||||
- if (!name_external(name, dns_rdatatype_ns, fctx)) {
|
||||
+ if (!name_external(name, dns_rdatatype_ns, rctx) &&
|
||||
+ dns_name_issubdomain(&fctx->name, name))
|
||||
+ {
|
||||
dns_rdataset_t *rdataset = NULL;
|
||||
|
||||
/*
|
||||
diff --git a/lib/dns/validator.c b/lib/dns/validator.c
|
||||
index e416cc9..37ddd26 100644
|
||||
--- a/lib/dns/validator.c
|
||||
+++ b/lib/dns/validator.c
|
||||
@@ -1344,17 +1344,17 @@ compute_keytag(dns_rdata_t *rdata) {
|
||||
/*%
|
||||
* Is the DNSKEY rrset in val->event->rdataset self-signed?
|
||||
*/
|
||||
-static bool
|
||||
+static isc_result_t
|
||||
selfsigned_dnskey(dns_validator_t *val) {
|
||||
dns_rdataset_t *rdataset = val->event->rdataset;
|
||||
dns_rdataset_t *sigrdataset = val->event->sigrdataset;
|
||||
dns_name_t *name = val->event->name;
|
||||
isc_result_t result;
|
||||
isc_mem_t *mctx = val->view->mctx;
|
||||
- bool answer = false;
|
||||
+ bool match = false;
|
||||
|
||||
if (rdataset->type != dns_rdatatype_dnskey) {
|
||||
- return (false);
|
||||
+ return DNS_R_NOKEYMATCH;
|
||||
}
|
||||
|
||||
for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS;
|
||||
@@ -1390,10 +1390,24 @@ selfsigned_dnskey(dns_validator_t *val) {
|
||||
continue;
|
||||
}
|
||||
|
||||
+ /*
|
||||
+ * If the REVOKE bit is not set we have a
|
||||
+ * theoretically self-signed DNSKEY RRset;
|
||||
+ * this will be verified later.
|
||||
+ *
|
||||
+ * We don't return the answer yet, though,
|
||||
+ * because we need to check the remaining keys
|
||||
+ * and possbly remove them if they're revoked.
|
||||
+ */
|
||||
+ if ((key.flags & DNS_KEYFLAG_REVOKE) == 0) {
|
||||
+ match = true;
|
||||
+ break;
|
||||
+ }
|
||||
+
|
||||
result = dns_dnssec_keyfromrdata(name, &keyrdata, mctx,
|
||||
&dstkey);
|
||||
if (result != ISC_R_SUCCESS) {
|
||||
- continue;
|
||||
+ break;
|
||||
}
|
||||
|
||||
result = dns_dnssec_verify(name, rdataset, dstkey, true,
|
||||
@@ -1404,16 +1418,15 @@ selfsigned_dnskey(dns_validator_t *val) {
|
||||
continue;
|
||||
}
|
||||
|
||||
- if ((key.flags & DNS_KEYFLAG_REVOKE) == 0) {
|
||||
- answer = true;
|
||||
- continue;
|
||||
- }
|
||||
-
|
||||
dns_view_untrust(val->view, name, &key);
|
||||
}
|
||||
}
|
||||
|
||||
- return (answer);
|
||||
+ if (!match) {
|
||||
+ return DNS_R_NOKEYMATCH;
|
||||
+ }
|
||||
+
|
||||
+ return ISC_R_SUCCESS;
|
||||
}
|
||||
|
||||
/*%
|
||||
--
|
||||
2.51.0
|
||||
|
||||
@ -176,6 +176,10 @@ Patch216: bind-9.18-CVE-2024-11187.patch
|
||||
Patch217: bind-9.16-update-b.root-servers.net.patch
|
||||
# https://gitlab.isc.org/isc-projects/bind9/commit/8330b49fb90bfeae14b47b7983e9459cc2bbaffe
|
||||
Patch225: bind-9.18-CVE-2025-40780.patch
|
||||
# https://gitlab.isc.org/isc-projects/bind9/commit/025d61bacd0f57f994a631654aff7a933d89a547
|
||||
# https://gitlab.isc.org/isc-projects/bind9/commit/cd17dfe696cdf9b8ef23fbc8738de7c79f957846
|
||||
# https://gitlab.isc.org/isc-projects/bind9/commit/4c6d03b0bb2ffbafcde8e8a5bc0e49908b978a72
|
||||
Patch224: bind-9.18-CVE-2025-40778.patch
|
||||
|
||||
%{?systemd_ordering}
|
||||
Requires: coreutils
|
||||
@ -1257,6 +1261,7 @@ fi;
|
||||
%changelog
|
||||
* Wed Oct 29 2025 Petr Menšík <pemensik@redhat.com> - 32:9.16.23-0.22.4
|
||||
- Prevent cache poisoning due to weak PRNG (CVE-2025-40780)
|
||||
- Address various spoofing attacks (CVE-2025-40778)
|
||||
|
||||
* Wed Aug 13 2025 Petr Menšík <pemensik@redhat.com>
|
||||
- Update addresses of b.root-servers.net (RHEL-18449) - 32:9.16.23-0.22.3
|
||||
|
||||
Loading…
Reference in New Issue
Block a user