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