1#!/usr/libexec/flua 2-- 3-- SPDX-License-Identifier: BSD-2-Clause 4-- 5-- Copyright (c) 2024, Klara, Inc. 6-- 7-- Redistribution and use in source and binary forms, with or without 8-- modification, are permitted provided that the following conditions 9-- are met: 10-- 1. Redistributions of source code must retain the above copyright 11-- notice, this list of conditions and the following disclaimer. 12-- 2. Redistributions in binary form must reproduce the above copyright 13-- notice, this list of conditions and the following disclaimer in the 14-- documentation and/or other materials provided with the distribution. 15-- 16-- THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17-- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18-- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19-- ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20-- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21-- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22-- OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23-- HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24-- LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25-- OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26-- SUCH DAMAGE. 27-- 28 29-- THEORY OF OPERATION 30-- 31-- generate-fortify-tests.lua is intended to test fortified functions as found 32-- mostly in the various headers in /usr/include/ssp. Each fortified function 33-- gets three basic tests: 34-- 35-- 1. Write just before the end of the buffer, 36-- 2. Write right at the end of the buffer, 37-- 3. Write just after the end of the buffer. 38-- 39-- Each test is actually generated twice: once with a buffer on the stack, and 40-- again with a buffer on the heap, to confirm that __builtin_object_size(3) can 41-- deduce the buffer size in both scenarios. The tests work by setting up the 42-- stack with our buffer (and some padding on either side to avoid tripping any 43-- other stack or memory protection), doing any initialization as described by 44-- the test definition, then calling the fortified function with the buffer as 45-- outlined by the test definition. 46-- 47-- For the 'before' and 'at' the end tests, we're ensuring that valid writes 48-- that are on the verge of being invalid aren't accidentally being detected as 49-- invalid. 50-- 51-- The 'after' test is the one that actually tests the functional benefit of 52-- _FORTIFY_SOURCE by violating a boundary that should trigger an abort. As 53-- such, this test differs more from the other two in that it has to fork() off 54-- the fortified function call so that we can monitor for a SIGABRT and 55-- pass/fail the test at function end appropriately. 56 57-- Some tests, like the FD_*() macros, may define these differently. For 58-- instance, for fd sets we're varying the index we pass and not using arbitrary 59-- buffers. Other tests that don't use the length in any way may physically 60-- vary the buffer size for each test case when we'd typically vary the length 61-- we're requesting a write for. 62 63local includes = { 64 "sys/param.h", 65 "sys/jail.h", 66 "sys/random.h", 67 "sys/resource.h", 68 "sys/select.h", 69 "sys/socket.h", 70 "sys/time.h", 71 "sys/uio.h", 72 "sys/wait.h", 73 "dirent.h", 74 "errno.h", 75 "fcntl.h", 76 "limits.h", 77 "poll.h", 78 "signal.h", 79 "stdio.h", 80 "stdlib.h", 81 "string.h", 82 "strings.h", 83 "sysexits.h", 84 "unistd.h", 85 "wchar.h", 86 "atf-c.h", 87} 88 89local tests_added = {} 90 91-- Configuration for tests that want the host/domainname 92local hostname = "host.example.com" 93local domainname = "example.com" 94 95-- Some of these will need to be excluded because clang sees the wrong size when 96-- an array is embedded inside a struct, we'll get something that looks more 97-- like __builtin_object_size(ptr, 0) than it does the correct 98-- __builtin_object_size(ptr, 1) (i.e., includes the padding after). This is 99-- almost certainly a bug in llvm. 100local function excludes_stack_overflow(disposition, is_heap) 101 return (not is_heap) and disposition > 0 102end 103 104local poll_init = [[ 105 for (size_t i = 0; i < howmany(__bufsz, sizeof(struct pollfd)); i++) { 106 __stack.__buf[i].fd = -1; 107 } 108]] 109 110local printf_stackvars = "\tchar srcvar[__len + 10];\n" 111local printf_init = [[ 112 memset(srcvar, 'A', sizeof(srcvar) - 1); 113 srcvar[sizeof(srcvar) - 1] = '\0'; 114]] 115 116local readv_stackvars = "\tstruct iovec iov[1];\n" 117local readv_init = [[ 118 iov[0].iov_base = __stack.__buf; 119 iov[0].iov_len = __len; 120 121 replace_stdin(); 122]] 123 124local socket_stackvars = "\tint sock[2] = { -1, -1 };\n" 125local recvfrom_sockaddr_stackvars = socket_stackvars .. [[ 126 char data[16]; 127 socklen_t socklen; 128]] 129local recvmsg_stackvars = socket_stackvars .. "\tstruct msghdr msg;\n" 130local socket_init = [[ 131 new_socket(sock); 132]] 133local socket_socklen_init = socket_init .. [[ 134 socklen = __len; 135]] 136 137local stdio_init = [[ 138 replace_stdin(); 139]] 140 141local string_stackvars = "\tchar src[__len];\n" 142local string_init = [[ 143 memset(__stack.__buf, 0, __len); 144 memset(src, 'A', __len - 1); 145 src[__len - 1] = '\0'; 146]] 147 148local wstring_stackvars = "\twchar_t src[__len];\n" 149local wstring_init = [[ 150 wmemset(__stack.__buf, 0, __len); 151 wmemset(src, 'A', __len - 1); 152 src[__len - 1] = '\0'; 153]] 154 155-- Each test entry describes how to test a given function. We need to know how 156-- to construct the buffer, we need to know the argument set we're dealing with, 157-- and we need to know what we're passing to each argument. We could be passing 158-- fixed values, or we could be passing the __buf under test. 159-- 160-- definition: 161-- func: name of the function under test to call 162-- bufsize: size of buffer to generate, defaults to 42 163-- buftype: type of buffer to generate, defaults to unsigned char[] 164-- arguments: __buf, __len, or the name of a variable placed on the stack 165-- exclude: a function(disposition, is_heap) that returns true if this combo 166-- should be excluded. 167-- stackvars: extra variables to be placed on the stack, should be a string 168-- optionally formatted with tabs and newlines 169-- init: extra code to inject just before the function call for initialization 170-- of the buffer or any of the above-added stackvars; also a string 171-- uses_len: bool-ish, necessary if arguments doesn't include either __idx or 172-- or __len so that the test generator doesn't try to vary the size of the 173-- buffer instead of just manipulating __idx/__len to try and induce an 174-- overflow. 175-- 176-- Most tests will just use the default bufsize/buftype, but under some 177-- circumstances it's useful to use a different type (e.g., for alignment 178-- requirements). 179local all_tests = { 180 random = { 181 -- <sys/random.h> 182 { 183 func = "getrandom", 184 arguments = { 185 "__buf", 186 "__len", 187 "0", 188 }, 189 exclude = excludes_stack_overflow, 190 }, 191 }, 192 select = { 193 -- <sys/select.h> 194 { 195 func = "FD_SET", 196 bufsize = "FD_SETSIZE", 197 buftype = "fd_set", 198 arguments = { 199 "__idx", 200 "__buf", 201 }, 202 }, 203 { 204 func = "FD_CLR", 205 bufsize = "FD_SETSIZE", 206 buftype = "fd_set", 207 arguments = { 208 "__idx", 209 "__buf", 210 }, 211 }, 212 { 213 func = "FD_ISSET", 214 bufsize = "FD_SETSIZE", 215 buftype = "fd_set", 216 arguments = { 217 "__idx", 218 "__buf", 219 }, 220 }, 221 }, 222 socket = { 223 -- <sys/socket.h> 224 { 225 func = "getpeername", 226 buftype = "struct sockaddr", 227 bufsize = "sizeof(struct sockaddr)", 228 arguments = { 229 "sock[0]", 230 "__buf", 231 "&socklen", 232 }, 233 exclude = excludes_stack_overflow, 234 stackvars = socket_stackvars .. "\tsocklen_t socklen;", 235 init = socket_socklen_init, 236 uses_len = true, 237 }, 238 { 239 func = "getsockname", 240 buftype = "struct sockaddr", 241 bufsize = "sizeof(struct sockaddr)", 242 arguments = { 243 "sock[0]", 244 "__buf", 245 "&socklen", 246 }, 247 exclude = excludes_stack_overflow, 248 stackvars = socket_stackvars .. "\tsocklen_t socklen;", 249 init = socket_socklen_init, 250 uses_len = true, 251 }, 252 { 253 func = "recv", 254 arguments = { 255 "sock[0]", 256 "__buf", 257 "__len", 258 "0", 259 }, 260 exclude = excludes_stack_overflow, 261 stackvars = socket_stackvars, 262 init = socket_init, 263 }, 264 { 265 func = "recvfrom", 266 arguments = { 267 "sock[0]", 268 "__buf", 269 "__len", 270 "0", 271 "NULL", 272 "NULL", 273 }, 274 exclude = excludes_stack_overflow, 275 stackvars = socket_stackvars, 276 init = socket_init, 277 }, 278 { 279 func = "recvfrom", 280 variant = "sockaddr", 281 buftype = "struct sockaddr", 282 bufsize = "sizeof(struct sockaddr)", 283 arguments = { 284 "sock[0]", 285 "data", 286 "sizeof(data)", 287 "0", 288 "__buf", 289 "&socklen", 290 }, 291 exclude = excludes_stack_overflow, 292 stackvars = recvfrom_sockaddr_stackvars, 293 init = socket_socklen_init, 294 uses_len = true, 295 }, 296 { 297 func = "recvmsg", 298 variant = "msg_name", 299 buftype = "struct sockaddr", 300 bufsize = "sizeof(struct sockaddr)", 301 arguments = { 302 "sock[0]", 303 "&msg", 304 "0", 305 }, 306 exclude = excludes_stack_overflow, 307 stackvars = recvmsg_stackvars, 308 init = [[ 309 memset(&msg, 0, sizeof(msg)); 310 msg.msg_name = BUF; 311 msg.msg_namelen = __len; 312]], 313 uses_len = true, 314 }, 315 { 316 func = "recvmsg", 317 variant = "msg_iov", 318 arguments = { 319 "sock[0]", 320 "&msg", 321 "0", 322 }, 323 exclude = excludes_stack_overflow, 324 stackvars = recvmsg_stackvars .. "\tstruct iovec iov[2];\n", 325 init = [[ 326 memset(&msg, 0, sizeof(msg)); 327 memset(&iov[0], 0, sizeof(iov)); 328 329 /* 330 * We position the buffer second just so that we can confirm that the 331 * fortification bits are traversing the iovec correctly. 332 */ 333 iov[1].iov_base = BUF; 334 iov[1].iov_len = __len; 335 336 msg.msg_iov = &iov[0]; 337 msg.msg_iovlen = nitems(iov); 338]], 339 uses_len = true, 340 }, 341 { 342 func = "recvmsg", 343 variant = "msg_control", 344 bufsize = "CMSG_SPACE(sizeof(int))", 345 arguments = { 346 "sock[0]", 347 "&msg", 348 "0", 349 }, 350 exclude = excludes_stack_overflow, 351 stackvars = recvmsg_stackvars, 352 init = [[ 353 memset(&msg, 0, sizeof(msg)); 354 355 msg.msg_control = BUF; 356 msg.msg_controllen = __len; 357]], 358 uses_len = true, 359 }, 360 { 361 func = "recvmmsg", 362 variant = "msgvec", 363 buftype = "struct mmsghdr[]", 364 bufsize = "2", 365 arguments = { 366 "sock[0]", 367 "__buf", 368 "__len", 369 "0", 370 "NULL", 371 }, 372 stackvars = socket_stackvars, 373 }, 374 { 375 -- We'll assume that recvmsg is covering msghdr 376 -- validation thoroughly enough, we'll just try tossing 377 -- an error in the second element of a msgvec to try and 378 -- make sure that each one is being validated. 379 func = "recvmmsg", 380 variant = "msghdr", 381 arguments = { 382 "sock[0]", 383 "&msgvec[0]", 384 "nitems(msgvec)", 385 "0", 386 "NULL", 387 }, 388 exclude = excludes_stack_overflow, 389 stackvars = socket_stackvars .. "\tstruct mmsghdr msgvec[2];\n", 390 init = [[ 391 memset(&msgvec[0], 0, sizeof(msgvec)); 392 393 /* 394 * Same as above, make sure fortification isn't ignoring n > 1 elements 395 * of the msgvec. 396 */ 397 msgvec[1].msg_hdr.msg_control = BUF; 398 msgvec[1].msg_hdr.msg_controllen = __len; 399]], 400 uses_len = true, 401 }, 402 }, 403 uio = { 404 -- <sys/uio.h> 405 { 406 func = "readv", 407 buftype = "struct iovec[]", 408 bufsize = 2, 409 arguments = { 410 "STDIN_FILENO", 411 "__buf", 412 "__len", 413 }, 414 }, 415 { 416 func = "readv", 417 variant = "iov", 418 arguments = { 419 "STDIN_FILENO", 420 "iov", 421 "nitems(iov)", 422 }, 423 exclude = excludes_stack_overflow, 424 stackvars = readv_stackvars, 425 init = readv_init, 426 uses_len = true, 427 }, 428 { 429 func = "preadv", 430 buftype = "struct iovec[]", 431 bufsize = 2, 432 arguments = { 433 "STDIN_FILENO", 434 "__buf", 435 "__len", 436 "0", 437 }, 438 }, 439 { 440 func = "preadv", 441 variant = "iov", 442 arguments = { 443 "STDIN_FILENO", 444 "iov", 445 "nitems(iov)", 446 "0", 447 }, 448 exclude = excludes_stack_overflow, 449 stackvars = readv_stackvars, 450 init = readv_init, 451 uses_len = true, 452 }, 453 }, 454 poll = { 455 -- <poll.h> 456 { 457 func = "poll", 458 bufsize = "4", 459 buftype = "struct pollfd[]", 460 arguments = { 461 "__buf", 462 "__len", 463 "0", 464 }, 465 init = poll_init, 466 }, 467 { 468 func = "ppoll", 469 bufsize = "4", 470 buftype = "struct pollfd[]", 471 arguments = { 472 "__buf", 473 "__len", 474 "&tv", 475 "NULL", 476 }, 477 stackvars = "\tstruct timespec tv = { 0 };\n", 478 init = poll_init, 479 }, 480 }, 481 signal = { 482 -- <signal.h> 483 { 484 func = "sig2str", 485 bufsize = "SIG2STR_MAX", 486 arguments = { 487 "1", 488 "__buf", 489 }, 490 exclude = excludes_stack_overflow, 491 }, 492 }, 493 stdio = { 494 -- <stdio.h> 495 { 496 func = "ctermid", 497 bufsize = "L_ctermid", 498 arguments = { 499 "__buf", 500 }, 501 exclude = excludes_stack_overflow, 502 }, 503 { 504 func = "ctermid_r", 505 bufsize = "L_ctermid", 506 arguments = { 507 "__buf", 508 }, 509 exclude = excludes_stack_overflow, 510 }, 511 { 512 func = "fread", 513 arguments = { 514 "__buf", 515 "__len", 516 "1", 517 "stdin", 518 }, 519 exclude = excludes_stack_overflow, 520 init = stdio_init, 521 }, 522 { 523 func = "fread_unlocked", 524 arguments = { 525 "__buf", 526 "__len", 527 "1", 528 "stdin", 529 }, 530 exclude = excludes_stack_overflow, 531 init = stdio_init, 532 }, 533 { 534 func = "gets_s", 535 arguments = { 536 "__buf", 537 "__len", 538 }, 539 exclude = excludes_stack_overflow, 540 init = stdio_init, 541 }, 542 { 543 func = "sprintf", 544 arguments = { 545 "__buf", 546 "\"%.*s\"", 547 "(int)__len - 1", -- - 1 for NUL terminator 548 "srcvar", 549 }, 550 exclude = excludes_stack_overflow, 551 stackvars = printf_stackvars, 552 init = printf_init, 553 }, 554 { 555 func = "snprintf", 556 arguments = { 557 "__buf", 558 "__len", 559 "\"%.*s\"", 560 "(int)__len - 1", -- - 1 for NUL terminator 561 "srcvar", 562 }, 563 exclude = excludes_stack_overflow, 564 stackvars = printf_stackvars, 565 init = printf_init, 566 }, 567 { 568 func = "tmpnam", 569 bufsize = "L_tmpnam", 570 arguments = { 571 "__buf", 572 }, 573 exclude = excludes_stack_overflow, 574 }, 575 { 576 func = "fgets", 577 arguments = { 578 "__buf", 579 "__len", 580 "fp", 581 }, 582 exclude = excludes_stack_overflow, 583 stackvars = "\tFILE *fp;\n", 584 init = [[ 585 fp = new_fp(__len); 586]], 587 }, 588 }, 589 stdlib = { 590 -- <stdlib.h> 591 { 592 func = "arc4random_buf", 593 arguments = { 594 "__buf", 595 "__len", 596 }, 597 exclude = excludes_stack_overflow, 598 }, 599 { 600 func = "getenv_r", 601 arguments = { 602 "\"PATH\"", 603 "__buf", 604 "__len", 605 }, 606 exclude = excludes_stack_overflow, 607 }, 608 { 609 func = "realpath", 610 bufsize = "PATH_MAX", 611 arguments = { 612 "\".\"", 613 "__buf", 614 }, 615 exclude = excludes_stack_overflow, 616 }, 617 }, 618 string = { 619 -- <string.h> 620 { 621 func = "memcpy", 622 arguments = { 623 "__buf", 624 "src", 625 "__len", 626 }, 627 exclude = excludes_stack_overflow, 628 stackvars = "\tchar src[__len + 10];\n", 629 }, 630 { 631 func = "mempcpy", 632 arguments = { 633 "__buf", 634 "src", 635 "__len", 636 }, 637 exclude = excludes_stack_overflow, 638 stackvars = "\tchar src[__len + 10];\n", 639 }, 640 { 641 func = "memmove", 642 arguments = { 643 "__buf", 644 "src", 645 "__len", 646 }, 647 exclude = excludes_stack_overflow, 648 stackvars = "\tchar src[__len + 10];\n", 649 }, 650 { 651 func = "memset", 652 arguments = { 653 "__buf", 654 "0", 655 "__len", 656 }, 657 exclude = excludes_stack_overflow, 658 }, 659 { 660 func = "memset_explicit", 661 arguments = { 662 "__buf", 663 "0", 664 "__len", 665 }, 666 exclude = excludes_stack_overflow, 667 }, 668 { 669 func = "stpcpy", 670 arguments = { 671 "__buf", 672 "src", 673 }, 674 exclude = excludes_stack_overflow, 675 stackvars = string_stackvars, 676 init = string_init, 677 uses_len = true, 678 }, 679 { 680 func = "stpncpy", 681 arguments = { 682 "__buf", 683 "src", 684 "__len", 685 }, 686 exclude = excludes_stack_overflow, 687 stackvars = string_stackvars, 688 init = string_init, 689 }, 690 { 691 func = "strcat", 692 arguments = { 693 "__buf", 694 "src", 695 }, 696 exclude = excludes_stack_overflow, 697 stackvars = string_stackvars, 698 init = string_init, 699 uses_len = true, 700 }, 701 { 702 func = "strlcat", 703 arguments = { 704 "__buf", 705 "src", 706 "__len", 707 }, 708 exclude = excludes_stack_overflow, 709 stackvars = string_stackvars, 710 init = string_init, 711 }, 712 { 713 func = "strncat", 714 arguments = { 715 "__buf", 716 "src", 717 "__len", 718 }, 719 exclude = excludes_stack_overflow, 720 stackvars = string_stackvars, 721 init = string_init, 722 }, 723 { 724 func = "strcpy", 725 arguments = { 726 "__buf", 727 "src", 728 }, 729 exclude = excludes_stack_overflow, 730 stackvars = string_stackvars, 731 init = string_init, 732 uses_len = true, 733 }, 734 { 735 func = "strlcpy", 736 arguments = { 737 "__buf", 738 "src", 739 "__len", 740 }, 741 exclude = excludes_stack_overflow, 742 stackvars = string_stackvars, 743 init = string_init, 744 }, 745 { 746 func = "strncpy", 747 arguments = { 748 "__buf", 749 "src", 750 "__len", 751 }, 752 exclude = excludes_stack_overflow, 753 stackvars = string_stackvars, 754 init = string_init, 755 }, 756 }, 757 strings = { 758 -- <strings.h> 759 { 760 func = "bcopy", 761 arguments = { 762 "src", 763 "__buf", 764 "__len", 765 }, 766 exclude = excludes_stack_overflow, 767 stackvars = "\tchar src[__len + 10];\n", 768 }, 769 { 770 func = "bzero", 771 arguments = { 772 "__buf", 773 "__len", 774 }, 775 exclude = excludes_stack_overflow, 776 }, 777 { 778 func = "explicit_bzero", 779 arguments = { 780 "__buf", 781 "__len", 782 }, 783 exclude = excludes_stack_overflow, 784 }, 785 }, 786 unistd = { 787 -- <unistd.h> 788 { 789 func = "getcwd", 790 bufsize = "8", 791 arguments = { 792 "__buf", 793 "__len", 794 }, 795 exclude = excludes_stack_overflow, 796 }, 797 { 798 func = "getgrouplist", 799 bufsize = "4", 800 buftype = "gid_t[]", 801 arguments = { 802 "\"root\"", 803 "0", 804 "__buf", 805 "&intlen", 806 }, 807 exclude = excludes_stack_overflow, 808 stackvars = "\tint intlen = (int)__len;\n", 809 uses_len = true, 810 }, 811 { 812 func = "getgroups", 813 bufsize = "4", 814 buftype = "gid_t[]", 815 arguments = { 816 "__len", 817 "__buf", 818 }, 819 exclude = excludes_stack_overflow, 820 }, 821 { 822 func = "getloginclass", 823 arguments = { 824 "__buf", 825 "__len", 826 }, 827 exclude = excludes_stack_overflow, 828 }, 829 { 830 func = "pread", 831 bufsize = "41", 832 arguments = { 833 "fd", 834 "__buf", 835 "__len", 836 "0", 837 }, 838 exclude = excludes_stack_overflow, 839 stackvars = "\tint fd;\n", 840 init = [[ 841 fd = new_tmpfile(); /* Cannot fail */ 842]], 843 }, 844 { 845 func = "read", 846 bufsize = "41", 847 arguments = { 848 "fd", 849 "__buf", 850 "__len", 851 }, 852 exclude = excludes_stack_overflow, 853 stackvars = "\tint fd;\n", 854 init = [[ 855 fd = new_tmpfile(); /* Cannot fail */ 856]], 857 }, 858 { 859 func = "readlink", 860 arguments = { 861 "path", 862 "__buf", 863 "__len", 864 }, 865 exclude = excludes_stack_overflow, 866 stackvars = "\tconst char *path;\n", 867 init = [[ 868 path = new_symlink(__len); /* Cannot fail */ 869]], 870 }, 871 { 872 func = "readlinkat", 873 arguments = { 874 "AT_FDCWD", 875 "path", 876 "__buf", 877 "__len", 878 }, 879 exclude = excludes_stack_overflow, 880 stackvars = "\tconst char *path;\n", 881 init = [[ 882 path = new_symlink(__len); /* Cannot fail */ 883]], 884 }, 885 { 886 func = "getdomainname", 887 bufsize = #domainname + 1, 888 arguments = { 889 "__buf", 890 "__len", 891 }, 892 need_root = true, 893 exclude = excludes_stack_overflow, 894 early_init = " dhost_jail();", 895 }, 896 { 897 func = "getentropy", 898 arguments = { 899 "__buf", 900 "__len", 901 }, 902 exclude = excludes_stack_overflow, 903 }, 904 { 905 func = "gethostname", 906 bufsize = #hostname + 1, 907 arguments = { 908 "__buf", 909 "__len", 910 }, 911 need_root = true, 912 exclude = excludes_stack_overflow, 913 early_init = " dhost_jail();", 914 }, 915 { 916 func = "getlogin_r", 917 bufsize = "MAXLOGNAME + 1", 918 arguments = { 919 "__buf", 920 "__len", 921 }, 922 exclude = excludes_stack_overflow, 923 }, 924 { 925 func = "ttyname_r", 926 arguments = { 927 "fd", 928 "__buf", 929 "__len", 930 }, 931 exclude = excludes_stack_overflow, 932 stackvars = "\tint fd;\n", 933 early_init = [[ 934 fd = STDIN_FILENO; 935 if (!isatty(fd)) 936 atf_tc_skip("stdin is not an fd"); 937]] 938 }, 939 }, 940 wchar = { 941 -- <wchar.h> 942 { 943 func = "wmemcpy", 944 buftype = "wchar_t[]", 945 arguments = { 946 "__buf", 947 "src", 948 "__len", 949 }, 950 exclude = excludes_stack_overflow, 951 stackvars = "\twchar_t src[__len + 10];\n", 952 }, 953 { 954 func = "wmempcpy", 955 buftype = "wchar_t[]", 956 arguments = { 957 "__buf", 958 "src", 959 "__len", 960 }, 961 exclude = excludes_stack_overflow, 962 stackvars = "\twchar_t src[__len + 10];\n", 963 }, 964 { 965 func = "wmemmove", 966 buftype = "wchar_t[]", 967 arguments = { 968 "__buf", 969 "src", 970 "__len", 971 }, 972 exclude = excludes_stack_overflow, 973 stackvars = "\twchar_t src[__len + 10];\n", 974 }, 975 { 976 func = "wmemset", 977 buftype = "wchar_t[]", 978 arguments = { 979 "__buf", 980 "L'0'", 981 "__len", 982 }, 983 exclude = excludes_stack_overflow, 984 }, 985 { 986 func = "wcpcpy", 987 buftype = "wchar_t[]", 988 arguments = { 989 "__buf", 990 "src", 991 }, 992 exclude = excludes_stack_overflow, 993 stackvars = wstring_stackvars, 994 init = wstring_init, 995 uses_len = true, 996 }, 997 { 998 func = "wcpncpy", 999 buftype = "wchar_t[]", 1000 arguments = { 1001 "__buf", 1002 "src", 1003 "__len", 1004 }, 1005 exclude = excludes_stack_overflow, 1006 stackvars = wstring_stackvars, 1007 init = wstring_init, 1008 }, 1009 { 1010 func = "wcscat", 1011 buftype = "wchar_t[]", 1012 arguments = { 1013 "__buf", 1014 "src", 1015 }, 1016 exclude = excludes_stack_overflow, 1017 stackvars = wstring_stackvars, 1018 init = wstring_init, 1019 uses_len = true, 1020 }, 1021 { 1022 func = "wcslcat", 1023 buftype = "wchar_t[]", 1024 arguments = { 1025 "__buf", 1026 "src", 1027 "__len", 1028 }, 1029 exclude = excludes_stack_overflow, 1030 stackvars = wstring_stackvars, 1031 init = wstring_init, 1032 }, 1033 { 1034 func = "wcsncat", 1035 buftype = "wchar_t[]", 1036 arguments = { 1037 "__buf", 1038 "src", 1039 "__len", 1040 }, 1041 exclude = excludes_stack_overflow, 1042 stackvars = wstring_stackvars, 1043 init = wstring_init, 1044 }, 1045 { 1046 func = "wcscpy", 1047 buftype = "wchar_t[]", 1048 arguments = { 1049 "__buf", 1050 "src", 1051 }, 1052 exclude = excludes_stack_overflow, 1053 stackvars = wstring_stackvars, 1054 init = wstring_init, 1055 uses_len = true, 1056 }, 1057 { 1058 func = "wcslcpy", 1059 buftype = "wchar_t[]", 1060 arguments = { 1061 "__buf", 1062 "src", 1063 "__len", 1064 }, 1065 exclude = excludes_stack_overflow, 1066 stackvars = wstring_stackvars, 1067 init = wstring_init, 1068 }, 1069 { 1070 func = "wcsncpy", 1071 buftype = "wchar_t[]", 1072 arguments = { 1073 "__buf", 1074 "src", 1075 "__len", 1076 }, 1077 exclude = excludes_stack_overflow, 1078 stackvars = wstring_stackvars, 1079 init = wstring_init, 1080 }, 1081 }, 1082} 1083 1084local function write_test_boilerplate(fh, name, body, def) 1085 fh:write("ATF_TC(" .. name .. ");\n") 1086 fh:write("ATF_TC_HEAD(" .. name .. ", tc)\n") 1087 fh:write("{\n") 1088 if def.need_root then 1089 fh:write(" atf_tc_set_md_var(tc, \"require.user\", \"root\");\n") 1090 end 1091 fh:write("}\n") 1092 1093 fh:write("ATF_TC_BODY(" .. name .. ", tc)\n") 1094 fh:write("{\n" .. body .. "\n}\n\n") 1095 return name 1096end 1097 1098local function generate_test_name(func, variant, disposition, heap) 1099 local basename = func 1100 if variant then 1101 basename = basename .. "_" .. variant 1102 end 1103 if heap then 1104 basename = basename .. "_heap" 1105 end 1106 if disposition < 0 then 1107 return basename .. "_before_end" 1108 elseif disposition == 0 then 1109 return basename .. "_end" 1110 else 1111 return basename .. "_after_end" 1112 end 1113end 1114 1115local function array_type(buftype) 1116 if not buftype:match("%[%]") then 1117 return nil 1118 end 1119 1120 return buftype:gsub("%[%]", "") 1121end 1122 1123local function configurable(def, idx) 1124 local cfgitem = def[idx] 1125 1126 if not cfgitem then 1127 return nil 1128 end 1129 1130 if type(cfgitem) == "function" then 1131 return cfgitem() 1132 end 1133 1134 return cfgitem 1135end 1136 1137local function generate_stackframe(buftype, bufsize, disposition, heap, def) 1138 local function len_offset(inverted) 1139 -- Tests that don't use __len in their arguments may use an 1140 -- inverted sense because we can't just specify a length that 1141 -- would induce an access just after the end. Instead, we have 1142 -- to manipulate the buffer size to be too short so that the 1143 -- function under test would write one too many. 1144 if disposition < 0 then 1145 return ((inverted and " + ") or " - ") .. "1" 1146 elseif disposition == 0 then 1147 return "" 1148 else 1149 return ((inverted and " - ") or " + ") .. "1" 1150 end 1151 end 1152 1153 local function test_uses_len() 1154 if def.uses_len then 1155 return true 1156 end 1157 1158 for _, arg in ipairs(def.arguments) do 1159 if arg:match("__len") or arg:match("__idx") then 1160 return true 1161 end 1162 end 1163 1164 return false 1165 end 1166 1167 1168 -- This is perhaps a little convoluted, but we toss the buffer into a 1169 -- struct on the stack to guarantee that we have at least one valid 1170 -- byte on either side of the buffer -- a measure to make sure that 1171 -- we're tripping _FORTIFY_SOURCE specifically in the buffer + 1 case, 1172 -- rather than some other stack or memory protection. 1173 local vars = "\tstruct {\n" 1174 vars = vars .. "\t\tuint8_t padding_l;\n" 1175 1176 local uses_len = test_uses_len() 1177 local bufsize_offset = len_offset(not uses_len) 1178 local buftype_elem = array_type(buftype) 1179 local size_expr = bufsize 1180 1181 if not uses_len then 1182 -- If the length isn't in use, we have to vary the buffer size 1183 -- since the fortified function likely has some internal size 1184 -- constraint that it's supposed to be checking. 1185 size_expr = size_expr .. bufsize_offset 1186 end 1187 1188 if not heap and buftype_elem then 1189 -- Array type: size goes after identifier 1190 vars = vars .. "\t\t" .. buftype_elem .. 1191 " __buf[" .. size_expr .. "];\n" 1192 else 1193 local basic_type = buftype_elem or buftype 1194 1195 -- Heap tests obviously just put a pointer on the stack that 1196 -- points to our new allocation, but we leave it in the padded 1197 -- struct just to simplify our generator. 1198 if heap then 1199 basic_type = basic_type .. " *" 1200 end 1201 vars = vars .. "\t\t" .. basic_type .. " __buf;\n" 1202 end 1203 1204 -- padding_r is our just-past-the-end padding that we use to make sure 1205 -- that there's a valid portion after the buffer that isn't being 1206 -- included in our function calls. If we didn't have it, then we'd have 1207 -- a hard time feeling confident that an abort on the just-after tests 1208 -- isn't maybe from some other memory or stack protection. 1209 vars = vars .. "\t\tuint8_t padding_r;\n" 1210 vars = vars .. "\t} __stack;\n" 1211 1212 -- Not all tests will use __bufsz, but some do for, e.g., clearing 1213 -- memory.. 1214 vars = vars .. "\tconst size_t __bufsz __unused = " 1215 if heap then 1216 local scalar = 1 1217 if buftype_elem then 1218 scalar = size_expr 1219 end 1220 1221 vars = vars .. "sizeof(*__stack.__buf) * (" .. scalar .. ");\n" 1222 else 1223 vars = vars .. "sizeof(__stack.__buf);\n" 1224 end 1225 1226 vars = vars .. "\tconst size_t __len = " .. bufsize .. 1227 bufsize_offset .. ";\n" 1228 vars = vars .. "\tconst size_t __idx __unused = __len - 1;\n" 1229 1230 -- For overflow testing, we need to fork() because we're expecting the 1231 -- test to ultimately abort()/_exit(). Then we can collect the exit 1232 -- status and report appropriately. 1233 if disposition > 0 then 1234 vars = vars .. "\tpid_t __child;\n" 1235 vars = vars .. "\tint __status;\n" 1236 end 1237 1238 -- Any other stackvars defined by the test get placed after everything 1239 -- else. 1240 vars = vars .. (configurable(def, "stackvars") or "") 1241 1242 return vars 1243end 1244 1245local function write_test(fh, func, disposition, heap, def) 1246 local testname = generate_test_name(func, def.variant, disposition, heap) 1247 local buftype = def.buftype or "unsigned char[]" 1248 local bufsize = def.bufsize or 42 1249 local body = "" 1250 1251 if def.exclude and def.exclude(disposition, heap) then 1252 return 1253 end 1254 1255 local function need_addr() 1256 return not (buftype:match("%[%]") or buftype:match("%*")) 1257 end 1258 1259 if heap then 1260 body = body .. "#define BUF __stack.__buf\n" 1261 else 1262 body = body .. "#define BUF &__stack.__buf\n" 1263 end 1264 1265 -- Setup the buffer 1266 body = body .. generate_stackframe(buftype, bufsize, disposition, heap, def) .. 1267 "\n" 1268 1269 -- Any early initialization goes before we would fork for the just-after 1270 -- tests, because they may want to skip the test based on some criteria 1271 -- and we can't propagate that up very easily once we're forked. 1272 local early_init = configurable(def, "early_init") 1273 body = body .. (early_init or "") 1274 if early_init then 1275 body = body .. "\n" 1276 end 1277 1278 -- Fork off, iff we're testing some access past the end of the buffer. 1279 if disposition > 0 then 1280 body = body .. [[ 1281 __child = fork(); 1282 ATF_REQUIRE(__child >= 0); 1283 if (__child > 0) 1284 goto monitor; 1285 1286 /* Child */ 1287 disable_coredumps(); 1288]] 1289 end 1290 1291 local bufvar = "__stack.__buf" 1292 if heap then 1293 -- Buffer needs to be initialized because it's a heap allocation. 1294 body = body .. "\t" .. bufvar .. " = malloc(__bufsz);\n" 1295 end 1296 1297 -- Non-early init happens just after the fork in the child, not the 1298 -- monitor. This is used to setup any other buffers we may need, for 1299 -- instance. 1300 local extra_init = configurable(def, "init") 1301 body = body .. (extra_init or "") 1302 1303 if heap or extra_init then 1304 body = body .. "\n" 1305 end 1306 1307 -- Setup the function call with arguments as described in the test 1308 -- definition. 1309 body = body .. "\t" .. func .. "(" 1310 1311 for idx, arg in ipairs(def.arguments) do 1312 if idx > 1 then 1313 body = body .. ", " 1314 end 1315 1316 if arg == "__buf" then 1317 if not heap and need_addr() then 1318 body = body .. "&" 1319 end 1320 1321 body = body .. bufvar 1322 else 1323 local argname = arg 1324 1325 if def.value_of then 1326 argname = argname or def.value_of(arg) 1327 end 1328 1329 body = body .. argname 1330 end 1331 end 1332 1333 body = body .. ");\n" 1334 1335 -- Monitor stuff follows, for OOB access. 1336 if disposition <= 0 then 1337 goto skip 1338 end 1339 1340 body = body .. [[ 1341 _exit(EX_SOFTWARE); /* Should have aborted. */ 1342 1343monitor: 1344 while (waitpid(__child, &__status, 0) != __child) { 1345 ATF_REQUIRE_EQ(EINTR, errno); 1346 } 1347 1348 if (!WIFSIGNALED(__status)) { 1349 switch (WEXITSTATUS(__status)) { 1350 case EX_SOFTWARE: 1351 atf_tc_fail("FORTIFY_SOURCE failed to abort"); 1352 break; 1353 case EX_OSERR: 1354 atf_tc_fail("setrlimit(2) failed"); 1355 break; 1356 default: 1357 atf_tc_fail("child exited with status %d", 1358 WEXITSTATUS(__status)); 1359 } 1360 } else { 1361 ATF_REQUIRE_EQ(SIGABRT, WTERMSIG(__status)); 1362 } 1363]] 1364 1365::skip:: 1366 body = body .. "#undef BUF\n" 1367 return write_test_boilerplate(fh, testname, body, def) 1368end 1369 1370-- main() 1371local tests 1372local tcat = assert(arg[1], "usage: generate-fortify-tests.lua <category>") 1373for k, defs in pairs(all_tests) do 1374 if k == tcat then 1375 tests = defs 1376 break 1377 end 1378end 1379 1380assert(tests, "category " .. tcat .. " not found") 1381 1382local fh = io.stdout 1383fh:write("/* @" .. "generated" .. " by `generate-fortify-tests.lua \"" .. 1384 tcat .. "\"` */\n\n") 1385fh:write("#define _FORTIFY_SOURCE 2\n") 1386fh:write("#define TMPFILE_SIZE (1024 * 32)\n") 1387 1388fh:write("\n") 1389for _, inc in ipairs(includes) do 1390 fh:write("#include <" .. inc .. ">\n") 1391end 1392 1393fh:write([[ 1394 1395static FILE * __unused 1396new_fp(size_t __len) 1397{ 1398 static char fpbuf[LINE_MAX]; 1399 FILE *fp; 1400 1401 ATF_REQUIRE(__len <= sizeof(fpbuf)); 1402 1403 memset(fpbuf, 'A', sizeof(fpbuf) - 1); 1404 fpbuf[sizeof(fpbuf) - 1] = '\0'; 1405 1406 fp = fmemopen(fpbuf, sizeof(fpbuf), "rb"); 1407 ATF_REQUIRE(fp != NULL); 1408 1409 return (fp); 1410} 1411 1412/* 1413 * Create a new symlink to use for readlink(2) style tests, we'll just use a 1414 * random target name to have something interesting to look at. 1415 */ 1416static const char * __unused 1417new_symlink(size_t __len) 1418{ 1419 static const char linkname[] = "link"; 1420 char target[MAXNAMLEN]; 1421 int error; 1422 1423 ATF_REQUIRE(__len <= sizeof(target)); 1424 1425 arc4random_buf(target, sizeof(target)); 1426 1427 error = unlink(linkname); 1428 ATF_REQUIRE(error == 0 || errno == ENOENT); 1429 1430 error = symlink(target, linkname); 1431 ATF_REQUIRE(error == 0); 1432 1433 return (linkname); 1434} 1435 1436/* 1437 * For our purposes, first descriptor will be the reader; we'll send both 1438 * raw data and a control message over it so that the result can be used for 1439 * any of our recv*() tests. 1440 */ 1441static void __unused 1442new_socket(int sock[2]) 1443{ 1444 unsigned char ctrl[CMSG_SPACE(sizeof(int))] = { 0 }; 1445 static char sockbuf[256]; 1446 ssize_t rv; 1447 size_t total = 0; 1448 struct msghdr hdr = { 0 }; 1449 struct cmsghdr *cmsg; 1450 int error, fd; 1451 1452 error = socketpair(AF_UNIX, SOCK_STREAM, 0, sock); 1453 ATF_REQUIRE(error == 0); 1454 1455 while (total != sizeof(sockbuf)) { 1456 rv = send(sock[1], &sockbuf[total], sizeof(sockbuf) - total, 0); 1457 1458 ATF_REQUIRE_MSG(rv > 0, 1459 "expected bytes sent, got %zd with %zu left (size %zu, total %zu)", 1460 rv, sizeof(sockbuf) - total, sizeof(sockbuf), total); 1461 ATF_REQUIRE_MSG(total + (size_t)rv <= sizeof(sockbuf), 1462 "%zd exceeds total %zu", rv, sizeof(sockbuf)); 1463 total += rv; 1464 } 1465 1466 hdr.msg_control = ctrl; 1467 hdr.msg_controllen = sizeof(ctrl); 1468 1469 cmsg = CMSG_FIRSTHDR(&hdr); 1470 cmsg->cmsg_level = SOL_SOCKET; 1471 cmsg->cmsg_type = SCM_RIGHTS; 1472 cmsg->cmsg_len = CMSG_LEN(sizeof(fd)); 1473 fd = STDIN_FILENO; 1474 memcpy(CMSG_DATA(cmsg), &fd, sizeof(fd)); 1475 1476 error = sendmsg(sock[1], &hdr, 0); 1477 ATF_REQUIRE(error != -1); 1478} 1479 1480/* 1481 * Constructs a tmpfile that we can use for testing read(2) and friends. 1482 */ 1483static int __unused 1484new_tmpfile(void) 1485{ 1486 char buf[1024]; 1487 ssize_t rv; 1488 size_t written; 1489 int fd; 1490 1491 fd = open("tmpfile", O_RDWR | O_CREAT | O_TRUNC, 0644); 1492 ATF_REQUIRE(fd >= 0); 1493 1494 written = 0; 1495 while (written < TMPFILE_SIZE) { 1496 rv = write(fd, buf, sizeof(buf)); 1497 ATF_REQUIRE(rv > 0); 1498 1499 written += rv; 1500 } 1501 1502 ATF_REQUIRE_EQ(0, lseek(fd, 0, SEEK_SET)); 1503 return (fd); 1504} 1505 1506static void 1507disable_coredumps(void) 1508{ 1509 struct rlimit rl = { 0 }; 1510 1511 if (setrlimit(RLIMIT_CORE, &rl) == -1) 1512 _exit(EX_OSERR); 1513} 1514 1515/* 1516 * Replaces stdin with a file that we can actually read from, for tests where 1517 * we want a FILE * or fd that we can get data from. 1518 */ 1519static void __unused 1520replace_stdin(void) 1521{ 1522 int fd; 1523 1524 fd = new_tmpfile(); 1525 1526 (void)dup2(fd, STDIN_FILENO); 1527 if (fd != STDIN_FILENO) 1528 close(fd); 1529} 1530 1531]]) 1532 1533if tcat == "unistd" then 1534 fh:write("#define JAIL_HOSTNAME \"" .. hostname .. "\"\n") 1535 fh:write("#define JAIL_DOMAINNAME \"" .. domainname .. "\"\n") 1536 fh:write([[ 1537static void 1538dhost_jail(void) 1539{ 1540 struct iovec iov[4]; 1541 int jid; 1542 1543 iov[0].iov_base = __DECONST(char *, "host.hostname"); 1544 iov[0].iov_len = sizeof("host.hostname"); 1545 iov[1].iov_base = __DECONST(char *, JAIL_HOSTNAME); 1546 iov[1].iov_len = sizeof(JAIL_HOSTNAME); 1547 iov[2].iov_base = __DECONST(char *, "host.domainname"); 1548 iov[2].iov_len = sizeof("host.domainname"); 1549 iov[3].iov_base = __DECONST(char *, JAIL_DOMAINNAME); 1550 iov[3].iov_len = sizeof(JAIL_DOMAINNAME); 1551 1552 jid = jail_set(iov, nitems(iov), JAIL_CREATE | JAIL_ATTACH); 1553 ATF_REQUIRE_MSG(jid > 0, "Jail creation failed: %s", strerror(errno)); 1554} 1555 1556]]) 1557end 1558 1559for _, def in pairs(tests) do 1560 local func = def.func 1561 local function write_tests(heap) 1562 -- Dispositions here are relative to the buffer size prescribed 1563 -- by the test definition. 1564 local dispositions = def.dispositions or { -1, 0, 1 } 1565 1566 for _, disposition in ipairs(dispositions) do 1567 tests_added[#tests_added + 1] = write_test(fh, func, disposition, heap, def) 1568 end 1569 end 1570 1571 write_tests(false) 1572 write_tests(true) 1573end 1574 1575fh:write("ATF_TP_ADD_TCS(tp)\n") 1576fh:write("{\n") 1577for idx = 1, #tests_added do 1578 fh:write("\tATF_TP_ADD_TC(tp, " .. tests_added[idx] .. ");\n") 1579end 1580fh:write("\treturn (atf_no_error());\n") 1581fh:write("}\n") 1582