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