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