Home | History | Annotate | Line # | Download | only in test
      1 /*
      2  * Security regression tests for fs_read_glyphs() in src/fc/fserve.c.
      3  *
      4  * Approach: include fserve.c directly to access the static fs_read_glyphs()
      5  * function.  Pre-fill the FSFpeRec.inBuf with a crafted protocol reply so
      6  * fs_get_reply() returns it without any network I/O.
      7  *
      8  * Copyright (c) 2026, Red Hat, Inc.
      9  *
     10  * Permission is hereby granted, free of charge, to any person obtaining a
     11  * copy of this software and associated documentation files (the "Software"),
     12  * to deal in the Software without restriction, including without limitation
     13  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
     14  * and/or sell copies of the Software, and to permit persons to whom the
     15  * Software is furnished to do so, subject to the following conditions:
     16  *
     17  * The above copyright notice and this permission notice (including the next
     18  * paragraph) shall be included in all copies or substantial portions of the
     19  * Software.
     20  *
     21  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     22  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     23  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
     24  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
     25  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
     26  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
     27  * DEALINGS IN THE SOFTWARE.
     28  */
     29 
     30 /*
     31  * Include fserve.c directly to access the static fs_read_glyphs().
     32  * All non-static symbols from fserve.c are hidden in libXfont2.so
     33  * (via the linker version script), so there are no duplicate symbol
     34  * conflicts when linking against the library.
     35  */
     36 #include "src/fc/fserve.c"
     37 
     38 #include <stdio.h>
     39 #include <stdlib.h>
     40 #include <string.h>
     41 #include <unistd.h>
     42 
     43 /*
     44  * Set up an FSFpeRec with its inBuf pre-filled with the given data.
     45  * fs_get_reply() will return this data without attempting any network I/O
     46  * because fs_inqueued(conn) >= size.
     47  */
     48 static void
     49 setup_conn(FSFpeRec *conn, const void *reply_data, long reply_size)
     50 {
     51     memset(conn, 0, sizeof(*conn));
     52 
     53     /*
     54      * fs_get_reply() checks:  conn->fs_fd != -1 && conn->fs_listening
     55      * Use a dup'd fd so it's valid but harmless.
     56      */
     57     conn->fs_fd = dup(STDERR_FILENO);
     58     conn->fs_listening = TRUE;
     59 
     60     /* Pre-fill the input buffer with our crafted reply */
     61     conn->inBuf.buf = malloc(reply_size);
     62     if (!conn->inBuf.buf) {
     63         fprintf(stderr, "FAIL: malloc for inBuf\n");
     64         exit(1);
     65     }
     66     memcpy(conn->inBuf.buf, reply_data, reply_size);
     67     conn->inBuf.size = reply_size;
     68     conn->inBuf.insert = reply_size;
     69     conn->inBuf.remove = 0;
     70     conn->inNeed = 0;
     71 
     72     /* Allocate a minimal output buffer to keep _fs_flush happy */
     73     conn->outBuf.buf = calloc(1, FS_BUF_INC);
     74     conn->outBuf.size = FS_BUF_INC;
     75     conn->outBuf.insert = 0;
     76     conn->outBuf.remove = 0;
     77 }
     78 
     79 static void
     80 cleanup_conn(FSFpeRec *conn)
     81 {
     82     if (conn->fs_fd >= 0)
     83         close(conn->fs_fd);
     84     free(conn->inBuf.buf);
     85     free(conn->outBuf.buf);
     86 }
     87 
     88 /*
     89  * Set up the minimum font state needed by fs_read_glyphs():
     90  *   - FontPathElementRec (fpe) with fpe->private = conn
     91  *   - FontRec (pfont) with info, fontPrivate, fpePrivate
     92  *   - FSFontRec (fsfont) with encoding[] array
     93  *   - FSFontDataRec (fsd)
     94  *   - FSBlockDataRec (blockrec) of type FS_OPEN_FONT
     95  *   - FSBlockedFontRec (bfont) embedded in blockrec->data
     96  */
     97 struct test_font_state {
     98     FontPathElementRec fpe;
     99     FontRec pfont;
    100     FSFontRec fsfont;
    101     FSFontDataRec fsd;
    102     FSBlockDataRec blockrec;
    103     FSBlockedFontRec bfont;
    104     CharInfoPtr encoding;
    105 };
    106 
    107 static void
    108 setup_font_state(struct test_font_state *s, FSFpeRec *conn,
    109                  int num_encoding)
    110 {
    111     int i;
    112 
    113     memset(s, 0, sizeof(*s));
    114 
    115     /* Font path element */
    116     s->fpe.name = (char *)"test-fserve";
    117     s->fpe.name_length = strlen(s->fpe.name);
    118     s->fpe.private = conn;
    119 
    120     /* Font data (fpePrivate) */
    121     s->fsd.name = (char *)"test-font";
    122     s->fsd.namelen = strlen(s->fsd.name);
    123     s->fsd.glyphs_to_get = 0;
    124 
    125     /* Encoding array -- this is what num_extents sized */
    126     s->encoding = calloc(num_encoding, sizeof(CharInfoRec));
    127     if (!s->encoding) {
    128         fprintf(stderr, "FAIL: calloc encoding\n");
    129         exit(1);
    130     }
    131     /* Mark all glyphs as having nonzero metrics and undefined bits
    132      * so fs_read_glyphs will try to process them */
    133     for (i = 0; i < num_encoding; i++) {
    134         s->encoding[i].metrics.ascent = 10;
    135         s->encoding[i].metrics.descent = 2;
    136         s->encoding[i].metrics.characterWidth = 8;
    137         s->encoding[i].metrics.leftSideBearing = 0;
    138         s->encoding[i].metrics.rightSideBearing = 8;
    139         s->encoding[i].bits = &_fs_glyph_undefined;
    140     }
    141 
    142     /* FSFontRec */
    143     s->fsfont.encoding = s->encoding;
    144     s->fsfont.num_encoding = num_encoding;
    145     s->fsfont.pDefault = NULL;
    146     s->fsfont.inkMetrics = s->encoding;
    147     s->fsfont.glyphs = NULL;
    148 
    149     /* FontRec */
    150     s->pfont.fontPrivate = &s->fsfont;
    151     s->pfont.fpePrivate = &s->fsd;
    152     s->pfont.fpe = &s->fpe;
    153     s->pfont.info.firstRow = 0;
    154     s->pfont.info.lastRow = 0;
    155     s->pfont.info.firstCol = 0;
    156     s->pfont.info.lastCol = num_encoding > 0 ? num_encoding - 1 : 0;
    157     s->pfont.info.maxbounds.ascent = 20;
    158     s->pfont.info.maxbounds.descent = 10;
    159     s->pfont.info.maxbounds.characterWidth = 20;
    160 
    161     /* Block record -- simulating FS_OPEN_FONT path */
    162     s->bfont.pfont = &s->pfont;
    163     s->bfont.flags = FontLoadBitmaps;
    164     s->bfont.state = FS_GLYPHS_REPLY;
    165     s->bfont.freeFont = FALSE;
    166 
    167     s->blockrec.type = FS_OPEN_FONT;
    168     s->blockrec.data = (pointer)&s->bfont;
    169     s->blockrec.client = NULL;
    170     s->blockrec.sequenceNumber = 0;
    171     s->blockrec.errcode = 0;
    172     s->blockrec.depending = NULL;
    173     s->blockrec.next = NULL;
    174 }
    175 
    176 static void
    177 cleanup_font_state(struct test_font_state *s)
    178 {
    179     FSGlyphPtr g, next;
    180 
    181     /* Free any glyph allocations made by fs_alloc_glyphs */
    182     for (g = s->fsfont.glyphs; g; g = next) {
    183         next = g->next;
    184         free(g);
    185     }
    186     free(s->encoding);
    187 }
    188 
    189 /*
    190  * Build a crafted fsQueryXBitmaps16Reply in a buffer.
    191  * Returns the total buffer size.  Caller must free *out_buf.
    192  *
    193  * The reply contains:
    194  *   - fsQueryXBitmaps16Reply header
    195  *   - num_chars fsOffset32 entries
    196  *   - nbytes of bitmap data
    197  */
    198 static long
    199 build_reply(char **out_buf,
    200             CARD32 num_chars, CARD32 nbytes,
    201             CARD32 off_position, CARD32 off_length)
    202 {
    203     long hdr_size = SIZEOF(fsQueryXBitmaps16Reply);
    204     long offsets_size = SIZEOF(fsOffset32) * num_chars;
    205     /* Bitmap data area: at least nbytes, but we need off_position + off_length
    206      * to be valid source, so ensure bitmap area is large enough */
    207     long bitmap_size = nbytes;
    208     long total = hdr_size + offsets_size + bitmap_size;
    209     long total_padded = (total + 3) & ~3;  /* pad to 4 bytes */
    210     char *buf;
    211     fsQueryXBitmaps16Reply *rep;
    212     fsOffset32 off;
    213     long i;
    214 
    215     buf = calloc(1, total_padded);
    216     if (!buf) {
    217         fprintf(stderr, "FAIL: calloc reply buffer\n");
    218         exit(1);
    219     }
    220 
    221     /* Fill header */
    222     rep = (fsQueryXBitmaps16Reply *)buf;
    223     rep->type = FS_Reply;    /* normal reply (0), not FS_Error (1) */
    224     rep->sequenceNumber = 0;
    225     rep->length = total_padded >> 2;   /* length in 32-bit words */
    226     rep->replies_hint = 0;
    227     rep->num_chars = num_chars;
    228     rep->nbytes = nbytes;
    229 
    230     /* Fill offset entries -- all pointing to the same source range */
    231     off.position = off_position;
    232     off.length = off_length;
    233     for (i = 0; i < (long)num_chars; i++) {
    234         memcpy(buf + hdr_size + i * SIZEOF(fsOffset32),
    235                &off, SIZEOF(fsOffset32));
    236     }
    237 
    238     /* Fill bitmap data with recognizable pattern */
    239     memset(buf + hdr_size + offsets_size, 0xAA, bitmap_size);
    240 
    241     *out_buf = buf;
    242     return total_padded;
    243 }
    244 
    245 /*
    246  * Test 1: num_chars > num_encoding
    247  *
    248  * Allocate encoding[] with 2 entries, but send a reply with
    249  * num_chars = 100.  Without the fix, this would read/write
    250  * encoding[2..99] out of bounds.
    251  */
    252 static int
    253 test_num_chars_exceeds_encoding(void)
    254 {
    255     FSFpeRec conn;
    256     struct test_font_state state;
    257     char *reply_buf;
    258     long reply_size;
    259     int result;
    260     int num_encoding = 2;
    261     CARD32 num_chars = 100;
    262     CARD32 nbytes = num_chars * 16;  /* enough bitmap data */
    263 
    264     /* Build a reply with num_chars=100 but valid source data */
    265     reply_size = build_reply(&reply_buf, num_chars, nbytes, 0, 16);
    266     setup_conn(&conn, reply_buf, reply_size);
    267     setup_font_state(&state, &conn, num_encoding);
    268 
    269     result = fs_read_glyphs(&state.fpe, &state.blockrec);
    270 
    271     cleanup_font_state(&state);
    272     cleanup_conn(&conn);
    273     free(reply_buf);
    274 
    275     if (result != Successful) {
    276         printf("ok 1 - num_chars (%u) > num_encoding (%d) rejected\n",
    277                (unsigned)num_chars, num_encoding);
    278         return 0;
    279     } else {
    280         printf("not ok 1 - num_chars (%u) > num_encoding (%d) "
    281                "should have been rejected\n",
    282                (unsigned)num_chars, num_encoding);
    283         return 1;
    284     }
    285 }
    286 
    287 /*
    288  * Test 2: cumulative glyph data overflow
    289  *
    290  * Allocate allbits with nbytes=64, but send 100 glyphs each
    291  * with offset {position:0, length:64}.  Each individual source
    292  * range is valid, but the cumulative writes total 6400 bytes
    293  * into a 64-byte buffer.
    294  */
    295 static int
    296 test_cumulative_allbits_overflow(void)
    297 {
    298     FSFpeRec conn;
    299     struct test_font_state state;
    300     char *reply_buf;
    301     long reply_size;
    302     int result;
    303     int num_encoding = 100;  /* match num_chars so encoding[] is fine */
    304     CARD32 num_chars = 100;
    305     CARD32 nbytes = 64;      /* tiny destination buffer */
    306 
    307     /* All offsets point to {position:0, length:64} -- each source
    308      * range is valid but they overlap, causing 100*64=6400 bytes
    309      * to be written to a 64-byte buffer */
    310     reply_size = build_reply(&reply_buf, num_chars, nbytes, 0, 64);
    311     setup_conn(&conn, reply_buf, reply_size);
    312     setup_font_state(&state, &conn, num_encoding);
    313 
    314     result = fs_read_glyphs(&state.fpe, &state.blockrec);
    315 
    316     cleanup_font_state(&state);
    317     cleanup_conn(&conn);
    318     free(reply_buf);
    319 
    320     if (result != Successful) {
    321         printf("ok 2 - cumulative allbits overflow (100 * 64 into 64) rejected\n");
    322         return 0;
    323     } else {
    324         printf("not ok 2 - cumulative allbits overflow (100 * 64 into 64) "
    325                "should have been rejected\n");
    326         return 1;
    327     }
    328 }
    329 
    330 /*
    331  * Test 3: legitimate reply should still be accepted
    332  *
    333  * num_chars == num_encoding, each glyph has unique non-overlapping
    334  * offsets, and total data fits in nbytes.
    335  */
    336 static int
    337 test_legitimate_reply(void)
    338 {
    339     FSFpeRec conn;
    340     struct test_font_state state;
    341     char *reply_buf;
    342     long hdr_size = SIZEOF(fsQueryXBitmaps16Reply);
    343     long offsets_size;
    344     int result;
    345     int num_encoding = 4;
    346     CARD32 num_chars = 4;
    347     CARD32 glyph_size = 16;
    348     CARD32 nbytes = num_chars * glyph_size;
    349     long total, total_padded;
    350     fsQueryXBitmaps16Reply *rep;
    351     fsOffset32 off;
    352     int i;
    353 
    354     offsets_size = SIZEOF(fsOffset32) * num_chars;
    355     total = hdr_size + offsets_size + nbytes;
    356     total_padded = (total + 3) & ~3;
    357 
    358     reply_buf = calloc(1, total_padded);
    359     if (!reply_buf) {
    360         fprintf(stderr, "FAIL: calloc\n");
    361         return 1;
    362     }
    363 
    364     rep = (fsQueryXBitmaps16Reply *)reply_buf;
    365     rep->type = FS_Reply;
    366     rep->sequenceNumber = 0;
    367     rep->length = total_padded >> 2;
    368     rep->replies_hint = 0;
    369     rep->num_chars = num_chars;
    370     rep->nbytes = nbytes;
    371 
    372     /* Each glyph gets its own non-overlapping slice */
    373     for (i = 0; i < (int)num_chars; i++) {
    374         off.position = i * glyph_size;
    375         off.length = glyph_size;
    376         memcpy(reply_buf + hdr_size + i * SIZEOF(fsOffset32),
    377                &off, SIZEOF(fsOffset32));
    378     }
    379     memset(reply_buf + hdr_size + offsets_size, 0xBB, nbytes);
    380 
    381     setup_conn(&conn, reply_buf, total_padded);
    382     setup_font_state(&state, &conn, num_encoding);
    383 
    384     result = fs_read_glyphs(&state.fpe, &state.blockrec);
    385 
    386     cleanup_font_state(&state);
    387     cleanup_conn(&conn);
    388     free(reply_buf);
    389 
    390     if (result == Successful) {
    391         printf("ok 3 - legitimate reply (4 glyphs, non-overlapping) accepted\n");
    392         return 0;
    393     } else {
    394         printf("not ok 3 - legitimate reply (4 glyphs, non-overlapping) "
    395                "rejected with error %d\n", result);
    396         return 1;
    397     }
    398 }
    399 
    400 int
    401 main(int argc, char **argv)
    402 {
    403     int failures = 0;
    404 
    405     printf("1..3\n");
    406 
    407     failures += test_num_chars_exceeds_encoding();
    408     failures += test_cumulative_allbits_overflow();
    409     failures += test_legitimate_reply();
    410 
    411     return failures ? 1 : 0;
    412 }
    413