Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3950d54a76 | ||
|
|
ebf29434cd |
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,2 +1 @@
|
|||||||
SOURCES/libXfont2-2.0.3.tar.bz2
|
libXfont2-2.0.6.tar.xz
|
||||||
/libXfont2-2.0.3.tar.bz2
|
|
||||||
|
|||||||
511
0004-fserve-bounds-check-cumulative-glyph-data-writes-in-.patch
Normal file
511
0004-fserve-bounds-check-cumulative-glyph-data-writes-in-.patch
Normal file
@ -0,0 +1,511 @@
|
|||||||
|
From 3fa8bcc79c168c12430802ded944131ebe7de8c6 Mon Sep 17 00:00:00 2001
|
||||||
|
From: Peter Hutterer <peter.hutterer@who-t.net>
|
||||||
|
Date: Mon, 13 Jul 2026 15:50:09 +1000
|
||||||
|
Subject: [PATCH] fserve: bounds-check cumulative glyph data writes in
|
||||||
|
fs_read_glyphs
|
||||||
|
|
||||||
|
fs_read_glyphs() copies each glyph's bitmap into a single allbits
|
||||||
|
buffer allocated to rep->nbytes bytes. The per-glyph guard validates
|
||||||
|
only that the source slice (position, length) lies within the pbitmaps
|
||||||
|
source buffer. It does not check whether the running destination cursor
|
||||||
|
has exceeded the allocation.
|
||||||
|
|
||||||
|
A malicious font server can send overlapping source offsets (e.g. 1000
|
||||||
|
glyphs each referencing {position:0, length:64} with nbytes=64). Each
|
||||||
|
individual source range passes validation, but the cumulative writes
|
||||||
|
total 64000 bytes into a 64-byte destination buffer.
|
||||||
|
|
||||||
|
Interestingly there was an unconditional debug printf in place that
|
||||||
|
sort-of warned about this but didn't prevent this. Let's remove that and
|
||||||
|
instead use the actual check to bail out before we run OOB.
|
||||||
|
|
||||||
|
A regression test is included that sends 100 glyphs each referencing
|
||||||
|
the same 64-byte source slice into a 64-byte destination buffer, and
|
||||||
|
verifies the library rejects the overflow.
|
||||||
|
|
||||||
|
CVE-2026-44950
|
||||||
|
|
||||||
|
Found-by: Zhixi "Jace" Sun, independent security researcher
|
||||||
|
Assisted-by: Claude:claude-opus-4-6
|
||||||
|
Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
|
||||||
|
Part-of: <https://gitlab.freedesktop.org/xorg/lib/libxfont/-/merge_requests/36>
|
||||||
|
---
|
||||||
|
src/fc/fserve.c | 23 +-
|
||||||
|
test/test-fserve-read-glyphs.c | 412 +++++++++++++++++++++++++++++++++
|
||||||
|
2 files changed, 426 insertions(+), 9 deletions(-)
|
||||||
|
create mode 100644 test/test-fserve-read-glyphs.c
|
||||||
|
|
||||||
|
diff --git a/src/fc/fserve.c b/src/fc/fserve.c
|
||||||
|
index c676234..97436cd 100644
|
||||||
|
--- a/src/fc/fserve.c
|
||||||
|
+++ b/src/fc/fserve.c
|
||||||
|
@@ -1922,10 +1922,7 @@ fs_read_glyphs(FontPathElementPtr fpe, FSBlockDataPtr blockrec)
|
||||||
|
fsOffset32 local_off;
|
||||||
|
char *off_adr;
|
||||||
|
pointer pbitmaps;
|
||||||
|
- char *bits, *allbits;
|
||||||
|
-#ifdef DEBUG
|
||||||
|
- char *origallbits;
|
||||||
|
-#endif
|
||||||
|
+ char *bits, *allbits, *origallbits;
|
||||||
|
int i,
|
||||||
|
err;
|
||||||
|
int nranges = 0;
|
||||||
|
@@ -2015,8 +2012,8 @@ fs_read_glyphs(FontPathElementPtr fpe, FSBlockDataPtr blockrec)
|
||||||
|
goto bail;
|
||||||
|
}
|
||||||
|
|
||||||
|
-#ifdef DEBUG
|
||||||
|
origallbits = allbits;
|
||||||
|
+#ifdef DEBUG
|
||||||
|
fprintf (stderr, "Reading %d glyphs in %d bytes for %s\n",
|
||||||
|
(int) rep->num_chars, (int) rep->nbytes, fsd->name);
|
||||||
|
#endif
|
||||||
|
@@ -2037,6 +2034,18 @@ fs_read_glyphs(FontPathElementPtr fpe, FSBlockDataPtr blockrec)
|
||||||
|
(local_off.position < rep->nbytes) &&
|
||||||
|
(local_off.length <= (rep->nbytes - local_off.position)))
|
||||||
|
{
|
||||||
|
+ /* Check that the destination buffer has enough room
|
||||||
|
+ for this glyph to prevent a heap overflow from
|
||||||
|
+ overlapping source offsets. */
|
||||||
|
+ if (local_off.length >
|
||||||
|
+ rep->nbytes - (allbits - origallbits))
|
||||||
|
+ {
|
||||||
|
+ ErrorF("fserve: glyph data overflow: "
|
||||||
|
+ "cumulative write exceeds nbytes (%u)\n",
|
||||||
|
+ (unsigned) rep->nbytes);
|
||||||
|
+ err = AllocError;
|
||||||
|
+ goto bail;
|
||||||
|
+ }
|
||||||
|
bits = allbits;
|
||||||
|
allbits += local_off.length;
|
||||||
|
memcpy(bits, (char *)pbitmaps + local_off.position,
|
||||||
|
@@ -2064,10 +2073,6 @@ fs_read_glyphs(FontPathElementPtr fpe, FSBlockDataPtr blockrec)
|
||||||
|
}
|
||||||
|
off_adr += SIZEOF(fsOffset32);
|
||||||
|
}
|
||||||
|
-#ifdef DEBUG
|
||||||
|
- fprintf (stderr, "Used %d bytes instead of %d\n",
|
||||||
|
- (int) (allbits - origallbits), (int) rep->nbytes);
|
||||||
|
-#endif
|
||||||
|
|
||||||
|
if (blockrec->type == FS_OPEN_FONT)
|
||||||
|
{
|
||||||
|
diff --git a/test/test-fserve-read-glyphs.c b/test/test-fserve-read-glyphs.c
|
||||||
|
new file mode 100644
|
||||||
|
index 0000000..21c3410
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/test/test-fserve-read-glyphs.c
|
||||||
|
@@ -0,0 +1,412 @@
|
||||||
|
+/*
|
||||||
|
+ * Security regression tests for fs_read_glyphs() in src/fc/fserve.c.
|
||||||
|
+ *
|
||||||
|
+ * Approach: include fserve.c directly to access the static fs_read_glyphs()
|
||||||
|
+ * function. Pre-fill the FSFpeRec.inBuf with a crafted protocol reply so
|
||||||
|
+ * fs_get_reply() returns it without any network I/O.
|
||||||
|
+ *
|
||||||
|
+ * Copyright (c) 2026, Red Hat, Inc.
|
||||||
|
+ *
|
||||||
|
+ * Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
+ * copy of this software and associated documentation files (the "Software"),
|
||||||
|
+ * to deal in the Software without restriction, including without limitation
|
||||||
|
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
+ * and/or sell copies of the Software, and to permit persons to whom the
|
||||||
|
+ * Software is furnished to do so, subject to the following conditions:
|
||||||
|
+ *
|
||||||
|
+ * The above copyright notice and this permission notice (including the next
|
||||||
|
+ * paragraph) shall be included in all copies or substantial portions of the
|
||||||
|
+ * Software.
|
||||||
|
+ *
|
||||||
|
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||||
|
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
+ * DEALINGS IN THE SOFTWARE.
|
||||||
|
+ */
|
||||||
|
+
|
||||||
|
+/*
|
||||||
|
+ * Include fserve.c directly to access the static fs_read_glyphs().
|
||||||
|
+ * All non-static symbols from fserve.c are hidden in libXfont2.so
|
||||||
|
+ * (via the linker version script), so there are no duplicate symbol
|
||||||
|
+ * conflicts when linking against the library.
|
||||||
|
+ */
|
||||||
|
+#include "src/fc/fserve.c"
|
||||||
|
+
|
||||||
|
+#include <stdio.h>
|
||||||
|
+#include <stdlib.h>
|
||||||
|
+#include <string.h>
|
||||||
|
+#include <unistd.h>
|
||||||
|
+
|
||||||
|
+/*
|
||||||
|
+ * Set up an FSFpeRec with its inBuf pre-filled with the given data.
|
||||||
|
+ * fs_get_reply() will return this data without attempting any network I/O
|
||||||
|
+ * because fs_inqueued(conn) >= size.
|
||||||
|
+ */
|
||||||
|
+static void
|
||||||
|
+setup_conn(FSFpeRec *conn, const void *reply_data, long reply_size)
|
||||||
|
+{
|
||||||
|
+ memset(conn, 0, sizeof(*conn));
|
||||||
|
+
|
||||||
|
+ /*
|
||||||
|
+ * fs_get_reply() checks: conn->fs_fd != -1 && conn->fs_listening
|
||||||
|
+ * Use a dup'd fd so it's valid but harmless.
|
||||||
|
+ */
|
||||||
|
+ conn->fs_fd = dup(STDERR_FILENO);
|
||||||
|
+ conn->fs_listening = TRUE;
|
||||||
|
+
|
||||||
|
+ /* Pre-fill the input buffer with our crafted reply */
|
||||||
|
+ conn->inBuf.buf = malloc(reply_size);
|
||||||
|
+ if (!conn->inBuf.buf) {
|
||||||
|
+ fprintf(stderr, "FAIL: malloc for inBuf\n");
|
||||||
|
+ exit(1);
|
||||||
|
+ }
|
||||||
|
+ memcpy(conn->inBuf.buf, reply_data, reply_size);
|
||||||
|
+ conn->inBuf.size = reply_size;
|
||||||
|
+ conn->inBuf.insert = reply_size;
|
||||||
|
+ conn->inBuf.remove = 0;
|
||||||
|
+ conn->inNeed = 0;
|
||||||
|
+
|
||||||
|
+ /* Allocate a minimal output buffer to keep _fs_flush happy */
|
||||||
|
+ conn->outBuf.buf = calloc(1, FS_BUF_INC);
|
||||||
|
+ conn->outBuf.size = FS_BUF_INC;
|
||||||
|
+ conn->outBuf.insert = 0;
|
||||||
|
+ conn->outBuf.remove = 0;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+static void
|
||||||
|
+cleanup_conn(FSFpeRec *conn)
|
||||||
|
+{
|
||||||
|
+ if (conn->fs_fd >= 0)
|
||||||
|
+ close(conn->fs_fd);
|
||||||
|
+ free(conn->inBuf.buf);
|
||||||
|
+ free(conn->outBuf.buf);
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+/*
|
||||||
|
+ * Set up the minimum font state needed by fs_read_glyphs():
|
||||||
|
+ * - FontPathElementRec (fpe) with fpe->private = conn
|
||||||
|
+ * - FontRec (pfont) with info, fontPrivate, fpePrivate
|
||||||
|
+ * - FSFontRec (fsfont) with encoding[] array
|
||||||
|
+ * - FSFontDataRec (fsd)
|
||||||
|
+ * - FSBlockDataRec (blockrec) of type FS_OPEN_FONT
|
||||||
|
+ * - FSBlockedFontRec (bfont) embedded in blockrec->data
|
||||||
|
+ */
|
||||||
|
+struct test_font_state {
|
||||||
|
+ FontPathElementRec fpe;
|
||||||
|
+ FontRec pfont;
|
||||||
|
+ FSFontRec fsfont;
|
||||||
|
+ FSFontDataRec fsd;
|
||||||
|
+ FSBlockDataRec blockrec;
|
||||||
|
+ FSBlockedFontRec bfont;
|
||||||
|
+ CharInfoPtr encoding;
|
||||||
|
+};
|
||||||
|
+
|
||||||
|
+static void
|
||||||
|
+setup_font_state(struct test_font_state *s, FSFpeRec *conn,
|
||||||
|
+ int num_encoding)
|
||||||
|
+{
|
||||||
|
+ int i;
|
||||||
|
+
|
||||||
|
+ memset(s, 0, sizeof(*s));
|
||||||
|
+
|
||||||
|
+ /* Font path element */
|
||||||
|
+ s->fpe.name = (char *)"test-fserve";
|
||||||
|
+ s->fpe.name_length = strlen(s->fpe.name);
|
||||||
|
+ s->fpe.private = conn;
|
||||||
|
+
|
||||||
|
+ /* Font data (fpePrivate) */
|
||||||
|
+ s->fsd.name = (char *)"test-font";
|
||||||
|
+ s->fsd.namelen = strlen(s->fsd.name);
|
||||||
|
+ s->fsd.glyphs_to_get = 0;
|
||||||
|
+
|
||||||
|
+ /* Encoding array -- this is what num_extents sized */
|
||||||
|
+ s->encoding = calloc(num_encoding, sizeof(CharInfoRec));
|
||||||
|
+ if (!s->encoding) {
|
||||||
|
+ fprintf(stderr, "FAIL: calloc encoding\n");
|
||||||
|
+ exit(1);
|
||||||
|
+ }
|
||||||
|
+ /* Mark all glyphs as having nonzero metrics and undefined bits
|
||||||
|
+ * so fs_read_glyphs will try to process them */
|
||||||
|
+ for (i = 0; i < num_encoding; i++) {
|
||||||
|
+ s->encoding[i].metrics.ascent = 10;
|
||||||
|
+ s->encoding[i].metrics.descent = 2;
|
||||||
|
+ s->encoding[i].metrics.characterWidth = 8;
|
||||||
|
+ s->encoding[i].metrics.leftSideBearing = 0;
|
||||||
|
+ s->encoding[i].metrics.rightSideBearing = 8;
|
||||||
|
+ s->encoding[i].bits = &_fs_glyph_undefined;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ /* FSFontRec */
|
||||||
|
+ s->fsfont.encoding = s->encoding;
|
||||||
|
+ s->fsfont.num_encoding = num_encoding;
|
||||||
|
+ s->fsfont.pDefault = NULL;
|
||||||
|
+ s->fsfont.inkMetrics = s->encoding;
|
||||||
|
+ s->fsfont.glyphs = NULL;
|
||||||
|
+
|
||||||
|
+ /* FontRec */
|
||||||
|
+ s->pfont.fontPrivate = &s->fsfont;
|
||||||
|
+ s->pfont.fpePrivate = &s->fsd;
|
||||||
|
+ s->pfont.fpe = &s->fpe;
|
||||||
|
+ s->pfont.info.firstRow = 0;
|
||||||
|
+ s->pfont.info.lastRow = 0;
|
||||||
|
+ s->pfont.info.firstCol = 0;
|
||||||
|
+ s->pfont.info.lastCol = num_encoding > 0 ? num_encoding - 1 : 0;
|
||||||
|
+ s->pfont.info.maxbounds.ascent = 20;
|
||||||
|
+ s->pfont.info.maxbounds.descent = 10;
|
||||||
|
+ s->pfont.info.maxbounds.characterWidth = 20;
|
||||||
|
+
|
||||||
|
+ /* Block record -- simulating FS_OPEN_FONT path */
|
||||||
|
+ s->bfont.pfont = &s->pfont;
|
||||||
|
+ s->bfont.flags = FontLoadBitmaps;
|
||||||
|
+ s->bfont.state = FS_GLYPHS_REPLY;
|
||||||
|
+ s->bfont.freeFont = FALSE;
|
||||||
|
+
|
||||||
|
+ s->blockrec.type = FS_OPEN_FONT;
|
||||||
|
+ s->blockrec.data = (pointer)&s->bfont;
|
||||||
|
+ s->blockrec.client = NULL;
|
||||||
|
+ s->blockrec.sequenceNumber = 0;
|
||||||
|
+ s->blockrec.errcode = 0;
|
||||||
|
+ s->blockrec.depending = NULL;
|
||||||
|
+ s->blockrec.next = NULL;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+static void
|
||||||
|
+cleanup_font_state(struct test_font_state *s)
|
||||||
|
+{
|
||||||
|
+ FSGlyphPtr g, next;
|
||||||
|
+
|
||||||
|
+ /* Free any glyph allocations made by fs_alloc_glyphs */
|
||||||
|
+ for (g = s->fsfont.glyphs; g; g = next) {
|
||||||
|
+ next = g->next;
|
||||||
|
+ free(g);
|
||||||
|
+ }
|
||||||
|
+ free(s->encoding);
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+/*
|
||||||
|
+ * Build a crafted fsQueryXBitmaps16Reply in a buffer.
|
||||||
|
+ * Returns the total buffer size. Caller must free *out_buf.
|
||||||
|
+ *
|
||||||
|
+ * The reply contains:
|
||||||
|
+ * - fsQueryXBitmaps16Reply header
|
||||||
|
+ * - num_chars fsOffset32 entries
|
||||||
|
+ * - nbytes of bitmap data
|
||||||
|
+ */
|
||||||
|
+static long
|
||||||
|
+build_reply(char **out_buf,
|
||||||
|
+ CARD32 num_chars, CARD32 nbytes,
|
||||||
|
+ CARD32 off_position, CARD32 off_length)
|
||||||
|
+{
|
||||||
|
+ long hdr_size = SIZEOF(fsQueryXBitmaps16Reply);
|
||||||
|
+ long offsets_size = SIZEOF(fsOffset32) * num_chars;
|
||||||
|
+ /* Bitmap data area: at least nbytes, but we need off_position + off_length
|
||||||
|
+ * to be valid source, so ensure bitmap area is large enough */
|
||||||
|
+ long bitmap_size = nbytes;
|
||||||
|
+ long total = hdr_size + offsets_size + bitmap_size;
|
||||||
|
+ long total_padded = (total + 3) & ~3; /* pad to 4 bytes */
|
||||||
|
+ char *buf;
|
||||||
|
+ fsQueryXBitmaps16Reply *rep;
|
||||||
|
+ fsOffset32 off;
|
||||||
|
+ long i;
|
||||||
|
+
|
||||||
|
+ buf = calloc(1, total_padded);
|
||||||
|
+ if (!buf) {
|
||||||
|
+ fprintf(stderr, "FAIL: calloc reply buffer\n");
|
||||||
|
+ exit(1);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ /* Fill header */
|
||||||
|
+ rep = (fsQueryXBitmaps16Reply *)buf;
|
||||||
|
+ rep->type = FS_Reply; /* normal reply (0), not FS_Error (1) */
|
||||||
|
+ rep->sequenceNumber = 0;
|
||||||
|
+ rep->length = total_padded >> 2; /* length in 32-bit words */
|
||||||
|
+ rep->replies_hint = 0;
|
||||||
|
+ rep->num_chars = num_chars;
|
||||||
|
+ rep->nbytes = nbytes;
|
||||||
|
+
|
||||||
|
+ /* Fill offset entries -- all pointing to the same source range */
|
||||||
|
+ off.position = off_position;
|
||||||
|
+ off.length = off_length;
|
||||||
|
+ for (i = 0; i < (long)num_chars; i++) {
|
||||||
|
+ memcpy(buf + hdr_size + i * SIZEOF(fsOffset32),
|
||||||
|
+ &off, SIZEOF(fsOffset32));
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ /* Fill bitmap data with recognizable pattern */
|
||||||
|
+ memset(buf + hdr_size + offsets_size, 0xAA, bitmap_size);
|
||||||
|
+
|
||||||
|
+ *out_buf = buf;
|
||||||
|
+ return total_padded;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+/*
|
||||||
|
+ * Test 1: num_chars > num_encoding
|
||||||
|
+ *
|
||||||
|
+ * Allocate encoding[] with 2 entries, but send a reply with
|
||||||
|
+ * num_chars = 100. Without the fix, this would read/write
|
||||||
|
+ * encoding[2..99] out of bounds.
|
||||||
|
+ */
|
||||||
|
+static int
|
||||||
|
+test_num_chars_exceeds_encoding(void)
|
||||||
|
+{
|
||||||
|
+ FSFpeRec conn;
|
||||||
|
+ struct test_font_state state;
|
||||||
|
+ char *reply_buf;
|
||||||
|
+ long reply_size;
|
||||||
|
+ int result;
|
||||||
|
+ int num_encoding = 2;
|
||||||
|
+ CARD32 num_chars = 100;
|
||||||
|
+ CARD32 nbytes = num_chars * 16; /* enough bitmap data */
|
||||||
|
+
|
||||||
|
+ /* Build a reply with num_chars=100 but valid source data */
|
||||||
|
+ reply_size = build_reply(&reply_buf, num_chars, nbytes, 0, 16);
|
||||||
|
+ setup_conn(&conn, reply_buf, reply_size);
|
||||||
|
+ setup_font_state(&state, &conn, num_encoding);
|
||||||
|
+
|
||||||
|
+ result = fs_read_glyphs(&state.fpe, &state.blockrec);
|
||||||
|
+
|
||||||
|
+ cleanup_font_state(&state);
|
||||||
|
+ cleanup_conn(&conn);
|
||||||
|
+ free(reply_buf);
|
||||||
|
+
|
||||||
|
+ if (result != Successful) {
|
||||||
|
+ printf("ok 1 - num_chars (%u) > num_encoding (%d) rejected\n",
|
||||||
|
+ (unsigned)num_chars, num_encoding);
|
||||||
|
+ return 0;
|
||||||
|
+ } else {
|
||||||
|
+ printf("not ok 1 - num_chars (%u) > num_encoding (%d) "
|
||||||
|
+ "should have been rejected\n",
|
||||||
|
+ (unsigned)num_chars, num_encoding);
|
||||||
|
+ return 1;
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+/*
|
||||||
|
+ * Test 2: cumulative glyph data overflow
|
||||||
|
+ *
|
||||||
|
+ * Allocate allbits with nbytes=64, but send 100 glyphs each
|
||||||
|
+ * with offset {position:0, length:64}. Each individual source
|
||||||
|
+ * range is valid, but the cumulative writes total 6400 bytes
|
||||||
|
+ * into a 64-byte buffer.
|
||||||
|
+ */
|
||||||
|
+static int
|
||||||
|
+test_cumulative_allbits_overflow(void)
|
||||||
|
+{
|
||||||
|
+ FSFpeRec conn;
|
||||||
|
+ struct test_font_state state;
|
||||||
|
+ char *reply_buf;
|
||||||
|
+ long reply_size;
|
||||||
|
+ int result;
|
||||||
|
+ int num_encoding = 100; /* match num_chars so encoding[] is fine */
|
||||||
|
+ CARD32 num_chars = 100;
|
||||||
|
+ CARD32 nbytes = 64; /* tiny destination buffer */
|
||||||
|
+
|
||||||
|
+ /* All offsets point to {position:0, length:64} -- each source
|
||||||
|
+ * range is valid but they overlap, causing 100*64=6400 bytes
|
||||||
|
+ * to be written to a 64-byte buffer */
|
||||||
|
+ reply_size = build_reply(&reply_buf, num_chars, nbytes, 0, 64);
|
||||||
|
+ setup_conn(&conn, reply_buf, reply_size);
|
||||||
|
+ setup_font_state(&state, &conn, num_encoding);
|
||||||
|
+
|
||||||
|
+ result = fs_read_glyphs(&state.fpe, &state.blockrec);
|
||||||
|
+
|
||||||
|
+ cleanup_font_state(&state);
|
||||||
|
+ cleanup_conn(&conn);
|
||||||
|
+ free(reply_buf);
|
||||||
|
+
|
||||||
|
+ if (result != Successful) {
|
||||||
|
+ printf("ok 2 - cumulative allbits overflow (100 * 64 into 64) rejected\n");
|
||||||
|
+ return 0;
|
||||||
|
+ } else {
|
||||||
|
+ printf("not ok 2 - cumulative allbits overflow (100 * 64 into 64) "
|
||||||
|
+ "should have been rejected\n");
|
||||||
|
+ return 1;
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+/*
|
||||||
|
+ * Test 3: legitimate reply should still be accepted
|
||||||
|
+ *
|
||||||
|
+ * num_chars == num_encoding, each glyph has unique non-overlapping
|
||||||
|
+ * offsets, and total data fits in nbytes.
|
||||||
|
+ */
|
||||||
|
+static int
|
||||||
|
+test_legitimate_reply(void)
|
||||||
|
+{
|
||||||
|
+ FSFpeRec conn;
|
||||||
|
+ struct test_font_state state;
|
||||||
|
+ char *reply_buf;
|
||||||
|
+ long hdr_size = SIZEOF(fsQueryXBitmaps16Reply);
|
||||||
|
+ long offsets_size;
|
||||||
|
+ int result;
|
||||||
|
+ int num_encoding = 4;
|
||||||
|
+ CARD32 num_chars = 4;
|
||||||
|
+ CARD32 glyph_size = 16;
|
||||||
|
+ CARD32 nbytes = num_chars * glyph_size;
|
||||||
|
+ long total, total_padded;
|
||||||
|
+ fsQueryXBitmaps16Reply *rep;
|
||||||
|
+ fsOffset32 off;
|
||||||
|
+ int i;
|
||||||
|
+
|
||||||
|
+ offsets_size = SIZEOF(fsOffset32) * num_chars;
|
||||||
|
+ total = hdr_size + offsets_size + nbytes;
|
||||||
|
+ total_padded = (total + 3) & ~3;
|
||||||
|
+
|
||||||
|
+ reply_buf = calloc(1, total_padded);
|
||||||
|
+ if (!reply_buf) {
|
||||||
|
+ fprintf(stderr, "FAIL: calloc\n");
|
||||||
|
+ return 1;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ rep = (fsQueryXBitmaps16Reply *)reply_buf;
|
||||||
|
+ rep->type = FS_Reply;
|
||||||
|
+ rep->sequenceNumber = 0;
|
||||||
|
+ rep->length = total_padded >> 2;
|
||||||
|
+ rep->replies_hint = 0;
|
||||||
|
+ rep->num_chars = num_chars;
|
||||||
|
+ rep->nbytes = nbytes;
|
||||||
|
+
|
||||||
|
+ /* Each glyph gets its own non-overlapping slice */
|
||||||
|
+ for (i = 0; i < (int)num_chars; i++) {
|
||||||
|
+ off.position = i * glyph_size;
|
||||||
|
+ off.length = glyph_size;
|
||||||
|
+ memcpy(reply_buf + hdr_size + i * SIZEOF(fsOffset32),
|
||||||
|
+ &off, SIZEOF(fsOffset32));
|
||||||
|
+ }
|
||||||
|
+ memset(reply_buf + hdr_size + offsets_size, 0xBB, nbytes);
|
||||||
|
+
|
||||||
|
+ setup_conn(&conn, reply_buf, total_padded);
|
||||||
|
+ setup_font_state(&state, &conn, num_encoding);
|
||||||
|
+
|
||||||
|
+ result = fs_read_glyphs(&state.fpe, &state.blockrec);
|
||||||
|
+
|
||||||
|
+ cleanup_font_state(&state);
|
||||||
|
+ cleanup_conn(&conn);
|
||||||
|
+ free(reply_buf);
|
||||||
|
+
|
||||||
|
+ if (result == Successful) {
|
||||||
|
+ printf("ok 3 - legitimate reply (4 glyphs, non-overlapping) accepted\n");
|
||||||
|
+ return 0;
|
||||||
|
+ } else {
|
||||||
|
+ printf("not ok 3 - legitimate reply (4 glyphs, non-overlapping) "
|
||||||
|
+ "rejected with error %d\n", result);
|
||||||
|
+ return 1;
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+int
|
||||||
|
+main(int argc, char **argv)
|
||||||
|
+{
|
||||||
|
+ int failures = 0;
|
||||||
|
+
|
||||||
|
+ printf("1..3\n");
|
||||||
|
+
|
||||||
|
+ failures += test_num_chars_exceeds_encoding();
|
||||||
|
+ failures += test_cumulative_allbits_overflow();
|
||||||
|
+ failures += test_legitimate_reply();
|
||||||
|
+
|
||||||
|
+ return failures ? 1 : 0;
|
||||||
|
+}
|
||||||
@ -1,6 +0,0 @@
|
|||||||
--- !Policy
|
|
||||||
product_versions:
|
|
||||||
- rhel-8
|
|
||||||
decision_context: osci_compose_gate
|
|
||||||
rules:
|
|
||||||
- !PassingTestCaseRule {test_case_name: desktop-qe.desktop-ci.tier1-gating.functional}
|
|
||||||
113
libXfont2-2.0.6-CVE-2026-59679.patch
Normal file
113
libXfont2-2.0.6-CVE-2026-59679.patch
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
From aa98f78ea17762d0bb0900715f07bb4e144dc432 Mon Sep 17 00:00:00 2001
|
||||||
|
From: Peter Hutterer <peter.hutterer@who-t.net>
|
||||||
|
Date: Mon, 13 Jul 2026 15:48:06 +1000
|
||||||
|
Subject: [PATCH] fserve: validate num_chars against encoding array size in
|
||||||
|
fs_read_glyphs
|
||||||
|
|
||||||
|
FS_QueryXExtents16 causes us to allocate the encoding[] array, later
|
||||||
|
during the FS_QueryXBitmaps16 reply handling we fill in that array.
|
||||||
|
There is no verification that the allocation is large enough, a
|
||||||
|
malicious font server could send us a small numExtents and a
|
||||||
|
large num_chars to force underallocation and OOB read/rwrite.
|
||||||
|
|
||||||
|
A regression test is included that constructs a crafted
|
||||||
|
FS_QueryXBitmaps16 reply with num_chars > num_encoding and verifies
|
||||||
|
the library rejects it.
|
||||||
|
|
||||||
|
CVE-2026-59679
|
||||||
|
|
||||||
|
Found-by: Zhixi "Jace" Sun, independent security researcher
|
||||||
|
Assisted-by: Claude:claude-opus-4-6
|
||||||
|
Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
|
||||||
|
Part-of: <https://gitlab.freedesktop.org/xorg/lib/libxfont/-/merge_requests/36>
|
||||||
|
---
|
||||||
|
Makefile.am | 15 +++++++++++++++
|
||||||
|
src/fc/fserve.c | 22 ++++++++++++++++++++++
|
||||||
|
src/fc/fservestr.h | 1 +
|
||||||
|
3 files changed, 38 insertions(+)
|
||||||
|
|
||||||
|
diff --git a/Makefile.am b/Makefile.am
|
||||||
|
index 62ac7e2..34a6abf 100644
|
||||||
|
--- a/Makefile.am
|
||||||
|
+++ b/Makefile.am
|
||||||
|
@@ -168,6 +168,21 @@ noinst_PROGRAMS = lsfontdir
|
||||||
|
lsfontdir_SOURCES = test/utils/lsfontdir.c $(TEST_UTIL_SRCS)
|
||||||
|
lsfontdir_LDADD = libXfont2.la $(LTLIBOBJS)
|
||||||
|
|
||||||
|
+# Security regression tests
|
||||||
|
+TESTS =
|
||||||
|
+check_PROGRAMS =
|
||||||
|
+
|
||||||
|
+if XFONT_FC
|
||||||
|
+TESTS += test-fserve-read-glyphs
|
||||||
|
+check_PROGRAMS += test-fserve-read-glyphs
|
||||||
|
+
|
||||||
|
+# The test #includes fserve.c directly to access static functions so
|
||||||
|
+# we statically link against libXfont2.a.
|
||||||
|
+test_fserve_read_glyphs_SOURCES = test/test-fserve-read-glyphs.c $(TEST_UTIL_SRCS)
|
||||||
|
+test_fserve_read_glyphs_CFLAGS = $(AM_CFLAGS) -I$(top_srcdir)/include -I$(top_srcdir)/src/fc
|
||||||
|
+test_fserve_read_glyphs_LDFLAGS = -static
|
||||||
|
+test_fserve_read_glyphs_LDADD = libXfont2.la $(LTLIBOBJS)
|
||||||
|
+endif XFONT_FC
|
||||||
|
|
||||||
|
MAINTAINERCLEANFILES = ChangeLog INSTALL
|
||||||
|
|
||||||
|
diff --git a/src/fc/fserve.c b/src/fc/fserve.c
|
||||||
|
index c676234..027f71d 100644
|
||||||
|
--- a/src/fc/fserve.c
|
||||||
|
+++ b/src/fc/fserve.c
|
||||||
|
@@ -1097,6 +1097,7 @@ fs_read_extent_info(FontPathElementPtr fpe, FSBlockDataPtr blockrec)
|
||||||
|
return AllocError;
|
||||||
|
}
|
||||||
|
fsfont->encoding = pCI;
|
||||||
|
+ fsfont->num_encoding = numExtents;
|
||||||
|
if (haveInk)
|
||||||
|
fsfont->inkMetrics = pCI + numExtents;
|
||||||
|
else
|
||||||
|
@@ -2003,6 +2004,17 @@ fs_read_glyphs(FontPathElementPtr fpe, FSBlockDataPtr blockrec)
|
||||||
|
{
|
||||||
|
minchar = 0;
|
||||||
|
maxchar = rep->num_chars;
|
||||||
|
+
|
||||||
|
+ /* Reject replies where num_chars exceeds the encoding array
|
||||||
|
+ size allocated in fs_read_extent_info() to prevent
|
||||||
|
+ out-of-bounds access on encoding[]. */
|
||||||
|
+ if (rep->num_chars > (CARD32)fsdata->num_encoding)
|
||||||
|
+ {
|
||||||
|
+ ErrorF("fserve: num_chars (%u) > num_encoding (%d)\n",
|
||||||
|
+ (unsigned) rep->num_chars, fsdata->num_encoding);
|
||||||
|
+ err = AllocError;
|
||||||
|
+ goto bail;
|
||||||
|
+ }
|
||||||
|
}
|
||||||
|
|
||||||
|
off_adr = (char *)ppbits;
|
||||||
|
@@ -2024,6 +2036,16 @@ fs_read_glyphs(FontPathElementPtr fpe, FSBlockDataPtr blockrec)
|
||||||
|
for (i = 0; i < rep->num_chars; i++)
|
||||||
|
{
|
||||||
|
memcpy(&local_off, off_adr, SIZEOF(fsOffset32)); /* align it */
|
||||||
|
+ /* Bounds-check minchar against the encoding array size to
|
||||||
|
+ prevent out-of-bounds access from a malicious font server
|
||||||
|
+ reply with more num_chars than num_extents. */
|
||||||
|
+ if (minchar >= (unsigned long)fsdata->num_encoding)
|
||||||
|
+ {
|
||||||
|
+ ErrorF("fserve: glyph index %lu >= num_encoding (%d)\n",
|
||||||
|
+ minchar, fsdata->num_encoding);
|
||||||
|
+ err = AllocError;
|
||||||
|
+ goto bail;
|
||||||
|
+ }
|
||||||
|
if (blockrec->type == FS_OPEN_FONT ||
|
||||||
|
fsdata->encoding[minchar].bits == &_fs_glyph_requested)
|
||||||
|
{
|
||||||
|
diff --git a/src/fc/fservestr.h b/src/fc/fservestr.h
|
||||||
|
index 29ae46e..da95e41 100644
|
||||||
|
--- a/src/fc/fservestr.h
|
||||||
|
+++ b/src/fc/fservestr.h
|
||||||
|
@@ -43,6 +43,7 @@ typedef struct _fs_glyph {
|
||||||
|
typedef struct _fs_font {
|
||||||
|
CharInfoPtr pDefault;
|
||||||
|
CharInfoPtr encoding;
|
||||||
|
+ int num_encoding;
|
||||||
|
CharInfoPtr inkMetrics;
|
||||||
|
FSGlyphPtr glyphs;
|
||||||
|
} FSFontRec, *FSFontPtr;
|
||||||
@ -1,13 +1,13 @@
|
|||||||
Summary: X.Org X11 libXfont2 runtime library
|
Summary: X.Org X11 libXfont2 runtime library
|
||||||
Name: libXfont2
|
Name: libXfont2
|
||||||
Version: 2.0.3
|
Version: 2.0.6
|
||||||
Release: 2%{?dist}.1
|
Release: 5%{?dist}.3
|
||||||
License: MIT
|
License: BSD-2-Clause AND BSD-4-Clause-UC AND HPND-sell-variant AND MIT-open-group AND SMLNJ AND X11
|
||||||
Group: System Environment/Libraries
|
|
||||||
URL: http://www.x.org
|
URL: http://www.x.org
|
||||||
|
|
||||||
Source0: http://www.x.org/pub/individual/lib/%{name}-%{version}.tar.bz2
|
Source0: http://www.x.org/pub/individual/lib/%{name}-%{version}.tar.xz
|
||||||
|
|
||||||
|
BuildRequires: make
|
||||||
BuildRequires: autoconf automake libtool
|
BuildRequires: autoconf automake libtool
|
||||||
BuildRequires: pkgconfig(fontsproto)
|
BuildRequires: pkgconfig(fontsproto)
|
||||||
BuildRequires: xorg-x11-util-macros
|
BuildRequires: xorg-x11-util-macros
|
||||||
@ -15,16 +15,20 @@ BuildRequires: xorg-x11-xtrans-devel >= 1.0.3-3
|
|||||||
BuildRequires: libfontenc-devel
|
BuildRequires: libfontenc-devel
|
||||||
BuildRequires: freetype-devel
|
BuildRequires: freetype-devel
|
||||||
|
|
||||||
Patch1: 0001-bitscale-fix-integer-overflow-in-BitmapScaleBitmaps-.patch
|
Patch: 0001-bitscale-fix-integer-overflow-in-BitmapScaleBitmaps-.patch
|
||||||
Patch2: 0002-pcfread-validate-bitmap-sizes-and-offsets-against-pe.patch
|
Patch: 0002-pcfread-validate-bitmap-sizes-and-offsets-against-pe.patch
|
||||||
Patch3: 0003-bitscale-add-bounds-check-to-computeProps-for-proper.patch
|
Patch: 0003-bitscale-add-bounds-check-to-computeProps-for-proper.patch
|
||||||
|
# https://gitlab.freedesktop.org/xorg/lib/libxfont/-/commit/c2d222bb22c623d8a40f3275077fc7e6617f2c8a
|
||||||
|
Patch: 0004-fserve-bounds-check-cumulative-glyph-data-writes-in-.patch
|
||||||
|
# https://redhat.atlassian.net/browse/RHEL-221949
|
||||||
|
# https://gitlab.freedesktop.org/xorg/lib/libxfont/-/commit/668fea81f40bcb48ec67fb55d0b851049d265290
|
||||||
|
Patch: libXfont2-2.0.6-CVE-2026-59679.patch
|
||||||
|
|
||||||
%description
|
%description
|
||||||
X.Org X11 libXfont2 runtime library
|
X.Org X11 libXfont2 runtime library
|
||||||
|
|
||||||
%package devel
|
%package devel
|
||||||
Summary: X.Org X11 libXfont2 development package
|
Summary: X.Org X11 libXfont2 development package
|
||||||
Group: Development/Libraries
|
|
||||||
Requires: %{name}%{?_isa} = %{version}-%{release}
|
Requires: %{name}%{?_isa} = %{version}-%{release}
|
||||||
Requires: libfontenc-devel%{?_isa}
|
Requires: libfontenc-devel%{?_isa}
|
||||||
|
|
||||||
@ -51,7 +55,7 @@ rm -f $RPM_BUILD_ROOT%{_libdir}/*.la
|
|||||||
|
|
||||||
%files
|
%files
|
||||||
%license COPYING
|
%license COPYING
|
||||||
%doc AUTHORS README ChangeLog
|
%doc AUTHORS README.md ChangeLog
|
||||||
%{_libdir}/libXfont2.so.2*
|
%{_libdir}/libXfont2.so.2*
|
||||||
|
|
||||||
%files devel
|
%files devel
|
||||||
@ -60,11 +64,77 @@ rm -f $RPM_BUILD_ROOT%{_libdir}/*.la
|
|||||||
%{_libdir}/pkgconfig/xfont2.pc
|
%{_libdir}/pkgconfig/xfont2.pc
|
||||||
|
|
||||||
%changelog
|
%changelog
|
||||||
* Wed Jul 08 2026 Olivier Fourdan <ofourdan@redhat.com> - 2.0.3-2.1
|
* Thu Aug 06 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 2.0.6-5.3
|
||||||
|
- CVE fix for: CVE-2026-59679
|
||||||
|
Resolves: https://redhat.atlassian.net/browse/RHEL-221949
|
||||||
|
|
||||||
|
* Thu Aug 06 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 2.0.6-5.2
|
||||||
|
- CVE fix for: CVE-2026-44950
|
||||||
|
Resolves: https://redhat.atlassian.net/browse/RHEL-222024
|
||||||
|
|
||||||
|
* Wed Jul 08 2026 Olivier Fourdan <ofourdan@redhat.com> - 2.0.6-5.1
|
||||||
- CVE fix for: CVE-2026-56001, CVE-2026-56002, CVE-2026-56003
|
- CVE fix for: CVE-2026-56001, CVE-2026-56002, CVE-2026-56003
|
||||||
Resolves: https://redhat.atlassian.net/browse/RHEL-191877
|
Resolves: https://redhat.atlassian.net/browse/RHEL-191882
|
||||||
Resolves: https://redhat.atlassian.net/browse/RHEL-191928
|
Resolves: https://redhat.atlassian.net/browse/RHEL-191930
|
||||||
Resolves: https://redhat.atlassian.net/browse/RHEL-191948
|
Resolves: https://redhat.atlassian.net/browse/RHEL-191949
|
||||||
|
|
||||||
|
* Tue Oct 29 2024 Troy Dawson <tdawson@redhat.com> - 2.0.6-5
|
||||||
|
- Bump release for October 2024 mass rebuild:
|
||||||
|
Resolves: RHEL-64018
|
||||||
|
|
||||||
|
* Mon Jun 24 2024 Troy Dawson <tdawson@redhat.com> - 2.0.6-4
|
||||||
|
- Bump release for June 2024 mass rebuild
|
||||||
|
|
||||||
|
* Thu Jan 25 2024 Fedora Release Engineering <releng@fedoraproject.org> - 2.0.6-3
|
||||||
|
- Rebuilt for https://fedoraproject.org/wiki/Fedora_40_Mass_Rebuild
|
||||||
|
|
||||||
|
* Sun Jan 21 2024 Fedora Release Engineering <releng@fedoraproject.org> - 2.0.6-2
|
||||||
|
- Rebuilt for https://fedoraproject.org/wiki/Fedora_40_Mass_Rebuild
|
||||||
|
|
||||||
|
* Thu Oct 05 2023 José Expósito <jexposit@redhat.com> - 2.0.6-1
|
||||||
|
- libXfont2 2.0.6
|
||||||
|
|
||||||
|
* Thu Sep 07 2023 José Expósito <jexposit@redhat.com> - 2.0.3-16
|
||||||
|
- SPDX Migration
|
||||||
|
|
||||||
|
* Thu Jul 20 2023 Fedora Release Engineering <releng@fedoraproject.org> - 2.0.3-15
|
||||||
|
- Rebuilt for https://fedoraproject.org/wiki/Fedora_39_Mass_Rebuild
|
||||||
|
|
||||||
|
* Thu Jan 19 2023 Fedora Release Engineering <releng@fedoraproject.org> - 2.0.3-14
|
||||||
|
- Rebuilt for https://fedoraproject.org/wiki/Fedora_38_Mass_Rebuild
|
||||||
|
|
||||||
|
* Thu Jul 21 2022 Fedora Release Engineering <releng@fedoraproject.org> - 2.0.3-13
|
||||||
|
- Rebuilt for https://fedoraproject.org/wiki/Fedora_37_Mass_Rebuild
|
||||||
|
|
||||||
|
* Thu Jan 20 2022 Fedora Release Engineering <releng@fedoraproject.org> - 2.0.3-12
|
||||||
|
- Rebuilt for https://fedoraproject.org/wiki/Fedora_36_Mass_Rebuild
|
||||||
|
|
||||||
|
* Thu Jul 22 2021 Fedora Release Engineering <releng@fedoraproject.org> - 2.0.3-11
|
||||||
|
- Rebuilt for https://fedoraproject.org/wiki/Fedora_35_Mass_Rebuild
|
||||||
|
|
||||||
|
* Tue Jan 26 2021 Fedora Release Engineering <releng@fedoraproject.org> - 2.0.3-10
|
||||||
|
- Rebuilt for https://fedoraproject.org/wiki/Fedora_34_Mass_Rebuild
|
||||||
|
|
||||||
|
* Thu Nov 5 11:25:30 AEST 2020 Peter Hutterer <peter.hutterer@redhat.com> - 2.0.3-9
|
||||||
|
- Add BuildRequires for make
|
||||||
|
|
||||||
|
* Tue Jul 28 2020 Fedora Release Engineering <releng@fedoraproject.org> - 2.0.3-8
|
||||||
|
- Rebuilt for https://fedoraproject.org/wiki/Fedora_33_Mass_Rebuild
|
||||||
|
|
||||||
|
* Wed Jan 29 2020 Fedora Release Engineering <releng@fedoraproject.org> - 2.0.3-7
|
||||||
|
- Rebuilt for https://fedoraproject.org/wiki/Fedora_32_Mass_Rebuild
|
||||||
|
|
||||||
|
* Thu Jul 25 2019 Fedora Release Engineering <releng@fedoraproject.org> - 2.0.3-6
|
||||||
|
- Rebuilt for https://fedoraproject.org/wiki/Fedora_31_Mass_Rebuild
|
||||||
|
|
||||||
|
* Thu Mar 21 2019 Adam Jackson <ajax@redhat.com> - 2.0.3-5
|
||||||
|
- Rebuild for xtrans 1.4.0
|
||||||
|
|
||||||
|
* Fri Feb 01 2019 Fedora Release Engineering <releng@fedoraproject.org> - 2.0.3-4
|
||||||
|
- Rebuilt for https://fedoraproject.org/wiki/Fedora_30_Mass_Rebuild
|
||||||
|
|
||||||
|
* Fri Jul 13 2018 Fedora Release Engineering <releng@fedoraproject.org> - 2.0.3-3
|
||||||
|
- Rebuilt for https://fedoraproject.org/wiki/Fedora_29_Mass_Rebuild
|
||||||
|
|
||||||
* Fri Jun 29 2018 Adam Jackson <ajax@redhat.com> - 2.0.3-2
|
* Fri Jun 29 2018 Adam Jackson <ajax@redhat.com> - 2.0.3-2
|
||||||
- Use ldconfig scriptlet macros
|
- Use ldconfig scriptlet macros
|
||||||
|
|||||||
2
sources
2
sources
@ -1 +1 @@
|
|||||||
SHA512 (libXfont2-2.0.3.tar.bz2) = 648b664e2aa58cbc7366a1b05873aa06bd4a38060f64085783043388244af8ceced77b29a22c3ac8b6d34cd226e093bbbcc785ea1748ea65720fe7ea05b4b44b
|
SHA512 (libXfont2-2.0.6.tar.xz) = 24d6baeef8dc2ca2730925c6b790a97f4a3f46ea7b4d908555e7df29cee6e27b63d638b50c88dd30c853cecff7d9b079402cb617f4498f96410c5770b8815cbd
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user