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 stdio = { 482 -- <stdio.h> 483 { 484 func = "ctermid", 485 bufsize = "L_ctermid", 486 arguments = { 487 "__buf", 488 }, 489 exclude = excludes_stack_overflow, 490 }, 491 { 492 func = "ctermid_r", 493 bufsize = "L_ctermid", 494 arguments = { 495 "__buf", 496 }, 497 exclude = excludes_stack_overflow, 498 }, 499 { 500 func = "fread", 501 arguments = { 502 "__buf", 503 "__len", 504 "1", 505 "stdin", 506 }, 507 exclude = excludes_stack_overflow, 508 init = stdio_init, 509 }, 510 { 511 func = "fread_unlocked", 512 arguments = { 513 "__buf", 514 "__len", 515 "1", 516 "stdin", 517 }, 518 exclude = excludes_stack_overflow, 519 init = stdio_init, 520 }, 521 { 522 func = "gets_s", 523 arguments = { 524 "__buf", 525 "__len", 526 }, 527 exclude = excludes_stack_overflow, 528 init = stdio_init, 529 }, 530 { 531 func = "sprintf", 532 arguments = { 533 "__buf", 534 "\"%.*s\"", 535 "(int)__len - 1", -- - 1 for NUL terminator 536 "srcvar", 537 }, 538 exclude = excludes_stack_overflow, 539 stackvars = printf_stackvars, 540 init = printf_init, 541 }, 542 { 543 func = "snprintf", 544 arguments = { 545 "__buf", 546 "__len", 547 "\"%.*s\"", 548 "(int)__len - 1", -- - 1 for NUL terminator 549 "srcvar", 550 }, 551 exclude = excludes_stack_overflow, 552 stackvars = printf_stackvars, 553 init = printf_init, 554 }, 555 { 556 func = "tmpnam", 557 bufsize = "L_tmpnam", 558 arguments = { 559 "__buf", 560 }, 561 exclude = excludes_stack_overflow, 562 }, 563 { 564 func = "fgets", 565 arguments = { 566 "__buf", 567 "__len", 568 "fp", 569 }, 570 exclude = excludes_stack_overflow, 571 stackvars = "\tFILE *fp;\n", 572 init = [[ 573 fp = new_fp(__len); 574]], 575 }, 576 }, 577 stdlib = { 578 -- <stdlib.h> 579 { 580 func = "arc4random_buf", 581 arguments = { 582 "__buf", 583 "__len", 584 }, 585 exclude = excludes_stack_overflow, 586 }, 587 { 588 func = "realpath", 589 bufsize = "PATH_MAX", 590 arguments = { 591 "\".\"", 592 "__buf", 593 }, 594 exclude = excludes_stack_overflow, 595 }, 596 }, 597 string = { 598 -- <string.h> 599 { 600 func = "memcpy", 601 arguments = { 602 "__buf", 603 "src", 604 "__len", 605 }, 606 exclude = excludes_stack_overflow, 607 stackvars = "\tchar src[__len + 10];\n", 608 }, 609 { 610 func = "mempcpy", 611 arguments = { 612 "__buf", 613 "src", 614 "__len", 615 }, 616 exclude = excludes_stack_overflow, 617 stackvars = "\tchar src[__len + 10];\n", 618 }, 619 { 620 func = "memmove", 621 arguments = { 622 "__buf", 623 "src", 624 "__len", 625 }, 626 exclude = excludes_stack_overflow, 627 stackvars = "\tchar src[__len + 10];\n", 628 }, 629 { 630 func = "memset", 631 arguments = { 632 "__buf", 633 "0", 634 "__len", 635 }, 636 exclude = excludes_stack_overflow, 637 }, 638 { 639 func = "memset_explicit", 640 arguments = { 641 "__buf", 642 "0", 643 "__len", 644 }, 645 exclude = excludes_stack_overflow, 646 }, 647 { 648 func = "stpcpy", 649 arguments = { 650 "__buf", 651 "src", 652 }, 653 exclude = excludes_stack_overflow, 654 stackvars = string_stackvars, 655 init = string_init, 656 uses_len = true, 657 }, 658 { 659 func = "stpncpy", 660 arguments = { 661 "__buf", 662 "src", 663 "__len", 664 }, 665 exclude = excludes_stack_overflow, 666 stackvars = string_stackvars, 667 init = string_init, 668 }, 669 { 670 func = "strcat", 671 arguments = { 672 "__buf", 673 "src", 674 }, 675 exclude = excludes_stack_overflow, 676 stackvars = string_stackvars, 677 init = string_init, 678 uses_len = true, 679 }, 680 { 681 func = "strlcat", 682 arguments = { 683 "__buf", 684 "src", 685 "__len", 686 }, 687 exclude = excludes_stack_overflow, 688 stackvars = string_stackvars, 689 init = string_init, 690 }, 691 { 692 func = "strncat", 693 arguments = { 694 "__buf", 695 "src", 696 "__len", 697 }, 698 exclude = excludes_stack_overflow, 699 stackvars = string_stackvars, 700 init = string_init, 701 }, 702 { 703 func = "strcpy", 704 arguments = { 705 "__buf", 706 "src", 707 }, 708 exclude = excludes_stack_overflow, 709 stackvars = string_stackvars, 710 init = string_init, 711 uses_len = true, 712 }, 713 { 714 func = "strlcpy", 715 arguments = { 716 "__buf", 717 "src", 718 "__len", 719 }, 720 exclude = excludes_stack_overflow, 721 stackvars = string_stackvars, 722 init = string_init, 723 }, 724 { 725 func = "strncpy", 726 arguments = { 727 "__buf", 728 "src", 729 "__len", 730 }, 731 exclude = excludes_stack_overflow, 732 stackvars = string_stackvars, 733 init = string_init, 734 }, 735 }, 736 strings = { 737 -- <strings.h> 738 { 739 func = "bcopy", 740 arguments = { 741 "src", 742 "__buf", 743 "__len", 744 }, 745 exclude = excludes_stack_overflow, 746 stackvars = "\tchar src[__len + 10];\n", 747 }, 748 { 749 func = "bzero", 750 arguments = { 751 "__buf", 752 "__len", 753 }, 754 exclude = excludes_stack_overflow, 755 }, 756 { 757 func = "explicit_bzero", 758 arguments = { 759 "__buf", 760 "__len", 761 }, 762 exclude = excludes_stack_overflow, 763 }, 764 }, 765 unistd = { 766 -- <unistd.h> 767 { 768 func = "getcwd", 769 bufsize = "8", 770 arguments = { 771 "__buf", 772 "__len", 773 }, 774 exclude = excludes_stack_overflow, 775 }, 776 { 777 func = "getgrouplist", 778 bufsize = "4", 779 buftype = "gid_t[]", 780 arguments = { 781 "\"root\"", 782 "0", 783 "__buf", 784 "&intlen", 785 }, 786 exclude = excludes_stack_overflow, 787 stackvars = "\tint intlen = (int)__len;\n", 788 uses_len = true, 789 }, 790 { 791 func = "getgroups", 792 bufsize = "4", 793 buftype = "gid_t[]", 794 arguments = { 795 "__len", 796 "__buf", 797 }, 798 exclude = excludes_stack_overflow, 799 }, 800 { 801 func = "getloginclass", 802 arguments = { 803 "__buf", 804 "__len", 805 }, 806 exclude = excludes_stack_overflow, 807 }, 808 { 809 func = "pread", 810 bufsize = "41", 811 arguments = { 812 "fd", 813 "__buf", 814 "__len", 815 "0", 816 }, 817 exclude = excludes_stack_overflow, 818 stackvars = "\tint fd;\n", 819 init = [[ 820 fd = new_tmpfile(); /* Cannot fail */ 821]], 822 }, 823 { 824 func = "read", 825 bufsize = "41", 826 arguments = { 827 "fd", 828 "__buf", 829 "__len", 830 }, 831 exclude = excludes_stack_overflow, 832 stackvars = "\tint fd;\n", 833 init = [[ 834 fd = new_tmpfile(); /* Cannot fail */ 835]], 836 }, 837 { 838 func = "readlink", 839 arguments = { 840 "path", 841 "__buf", 842 "__len", 843 }, 844 exclude = excludes_stack_overflow, 845 stackvars = "\tconst char *path;\n", 846 init = [[ 847 path = new_symlink(__len); /* Cannot fail */ 848]], 849 }, 850 { 851 func = "readlinkat", 852 arguments = { 853 "AT_FDCWD", 854 "path", 855 "__buf", 856 "__len", 857 }, 858 exclude = excludes_stack_overflow, 859 stackvars = "\tconst char *path;\n", 860 init = [[ 861 path = new_symlink(__len); /* Cannot fail */ 862]], 863 }, 864 { 865 func = "getdomainname", 866 bufsize = #domainname + 1, 867 arguments = { 868 "__buf", 869 "__len", 870 }, 871 need_root = true, 872 exclude = excludes_stack_overflow, 873 early_init = " dhost_jail();", 874 }, 875 { 876 func = "getentropy", 877 arguments = { 878 "__buf", 879 "__len", 880 }, 881 exclude = excludes_stack_overflow, 882 }, 883 { 884 func = "gethostname", 885 bufsize = #hostname + 1, 886 arguments = { 887 "__buf", 888 "__len", 889 }, 890 need_root = true, 891 exclude = excludes_stack_overflow, 892 early_init = " dhost_jail();", 893 }, 894 { 895 func = "getlogin_r", 896 bufsize = "MAXLOGNAME + 1", 897 arguments = { 898 "__buf", 899 "__len", 900 }, 901 exclude = excludes_stack_overflow, 902 }, 903 { 904 func = "ttyname_r", 905 arguments = { 906 "fd", 907 "__buf", 908 "__len", 909 }, 910 exclude = excludes_stack_overflow, 911 stackvars = "\tint fd;\n", 912 early_init = [[ 913 fd = STDIN_FILENO; 914 if (!isatty(fd)) 915 atf_tc_skip("stdin is not an fd"); 916]] 917 }, 918 }, 919 wchar = { 920 -- <wchar.h> 921 { 922 func = "wmemcpy", 923 buftype = "wchar_t[]", 924 arguments = { 925 "__buf", 926 "src", 927 "__len", 928 }, 929 exclude = excludes_stack_overflow, 930 stackvars = "\twchar_t src[__len + 10];\n", 931 }, 932 { 933 func = "wmempcpy", 934 buftype = "wchar_t[]", 935 arguments = { 936 "__buf", 937 "src", 938 "__len", 939 }, 940 exclude = excludes_stack_overflow, 941 stackvars = "\twchar_t src[__len + 10];\n", 942 }, 943 { 944 func = "wmemmove", 945 buftype = "wchar_t[]", 946 arguments = { 947 "__buf", 948 "src", 949 "__len", 950 }, 951 exclude = excludes_stack_overflow, 952 stackvars = "\twchar_t src[__len + 10];\n", 953 }, 954 { 955 func = "wmemset", 956 buftype = "wchar_t[]", 957 arguments = { 958 "__buf", 959 "L'0'", 960 "__len", 961 }, 962 exclude = excludes_stack_overflow, 963 }, 964 { 965 func = "wcpcpy", 966 buftype = "wchar_t[]", 967 arguments = { 968 "__buf", 969 "src", 970 }, 971 exclude = excludes_stack_overflow, 972 stackvars = wstring_stackvars, 973 init = wstring_init, 974 uses_len = true, 975 }, 976 { 977 func = "wcpncpy", 978 buftype = "wchar_t[]", 979 arguments = { 980 "__buf", 981 "src", 982 "__len", 983 }, 984 exclude = excludes_stack_overflow, 985 stackvars = wstring_stackvars, 986 init = wstring_init, 987 }, 988 { 989 func = "wcscat", 990 buftype = "wchar_t[]", 991 arguments = { 992 "__buf", 993 "src", 994 }, 995 exclude = excludes_stack_overflow, 996 stackvars = wstring_stackvars, 997 init = wstring_init, 998 uses_len = true, 999 }, 1000 { 1001 func = "wcslcat", 1002 buftype = "wchar_t[]", 1003 arguments = { 1004 "__buf", 1005 "src", 1006 "__len", 1007 }, 1008 exclude = excludes_stack_overflow, 1009 stackvars = wstring_stackvars, 1010 init = wstring_init, 1011 }, 1012 { 1013 func = "wcsncat", 1014 buftype = "wchar_t[]", 1015 arguments = { 1016 "__buf", 1017 "src", 1018 "__len", 1019 }, 1020 exclude = excludes_stack_overflow, 1021 stackvars = wstring_stackvars, 1022 init = wstring_init, 1023 }, 1024 { 1025 func = "wcscpy", 1026 buftype = "wchar_t[]", 1027 arguments = { 1028 "__buf", 1029 "src", 1030 }, 1031 exclude = excludes_stack_overflow, 1032 stackvars = wstring_stackvars, 1033 init = wstring_init, 1034 uses_len = true, 1035 }, 1036 { 1037 func = "wcslcpy", 1038 buftype = "wchar_t[]", 1039 arguments = { 1040 "__buf", 1041 "src", 1042 "__len", 1043 }, 1044 exclude = excludes_stack_overflow, 1045 stackvars = wstring_stackvars, 1046 init = wstring_init, 1047 }, 1048 { 1049 func = "wcsncpy", 1050 buftype = "wchar_t[]", 1051 arguments = { 1052 "__buf", 1053 "src", 1054 "__len", 1055 }, 1056 exclude = excludes_stack_overflow, 1057 stackvars = wstring_stackvars, 1058 init = wstring_init, 1059 }, 1060 }, 1061} 1062 1063local function write_test_boilerplate(fh, name, body, def) 1064 fh:write("ATF_TC(" .. name .. ");\n") 1065 fh:write("ATF_TC_HEAD(" .. name .. ", tc)\n") 1066 fh:write("{\n") 1067 if def.need_root then 1068 fh:write(" atf_tc_set_md_var(tc, \"require.user\", \"root\");\n") 1069 end 1070 fh:write("}\n") 1071 1072 fh:write("ATF_TC_BODY(" .. name .. ", tc)\n") 1073 fh:write("{\n" .. body .. "\n}\n\n") 1074 return name 1075end 1076 1077local function generate_test_name(func, variant, disposition, heap) 1078 local basename = func 1079 if variant then 1080 basename = basename .. "_" .. variant 1081 end 1082 if heap then 1083 basename = basename .. "_heap" 1084 end 1085 if disposition < 0 then 1086 return basename .. "_before_end" 1087 elseif disposition == 0 then 1088 return basename .. "_end" 1089 else 1090 return basename .. "_after_end" 1091 end 1092end 1093 1094local function array_type(buftype) 1095 if not buftype:match("%[%]") then 1096 return nil 1097 end 1098 1099 return buftype:gsub("%[%]", "") 1100end 1101 1102local function configurable(def, idx) 1103 local cfgitem = def[idx] 1104 1105 if not cfgitem then 1106 return nil 1107 end 1108 1109 if type(cfgitem) == "function" then 1110 return cfgitem() 1111 end 1112 1113 return cfgitem 1114end 1115 1116local function generate_stackframe(buftype, bufsize, disposition, heap, def) 1117 local function len_offset(inverted) 1118 -- Tests that don't use __len in their arguments may use an 1119 -- inverted sense because we can't just specify a length that 1120 -- would induce an access just after the end. Instead, we have 1121 -- to manipulate the buffer size to be too short so that the 1122 -- function under test would write one too many. 1123 if disposition < 0 then 1124 return ((inverted and " + ") or " - ") .. "1" 1125 elseif disposition == 0 then 1126 return "" 1127 else 1128 return ((inverted and " - ") or " + ") .. "1" 1129 end 1130 end 1131 1132 local function test_uses_len() 1133 if def.uses_len then 1134 return true 1135 end 1136 1137 for _, arg in ipairs(def.arguments) do 1138 if arg:match("__len") or arg:match("__idx") then 1139 return true 1140 end 1141 end 1142 1143 return false 1144 end 1145 1146 1147 -- This is perhaps a little convoluted, but we toss the buffer into a 1148 -- struct on the stack to guarantee that we have at least one valid 1149 -- byte on either side of the buffer -- a measure to make sure that 1150 -- we're tripping _FORTIFY_SOURCE specifically in the buffer + 1 case, 1151 -- rather than some other stack or memory protection. 1152 local vars = "\tstruct {\n" 1153 vars = vars .. "\t\tuint8_t padding_l;\n" 1154 1155 local uses_len = test_uses_len() 1156 local bufsize_offset = len_offset(not uses_len) 1157 local buftype_elem = array_type(buftype) 1158 local size_expr = bufsize 1159 1160 if not uses_len then 1161 -- If the length isn't in use, we have to vary the buffer size 1162 -- since the fortified function likely has some internal size 1163 -- constraint that it's supposed to be checking. 1164 size_expr = size_expr .. bufsize_offset 1165 end 1166 1167 if not heap and buftype_elem then 1168 -- Array type: size goes after identifier 1169 vars = vars .. "\t\t" .. buftype_elem .. 1170 " __buf[" .. size_expr .. "];\n" 1171 else 1172 local basic_type = buftype_elem or buftype 1173 1174 -- Heap tests obviously just put a pointer on the stack that 1175 -- points to our new allocation, but we leave it in the padded 1176 -- struct just to simplify our generator. 1177 if heap then 1178 basic_type = basic_type .. " *" 1179 end 1180 vars = vars .. "\t\t" .. basic_type .. " __buf;\n" 1181 end 1182 1183 -- padding_r is our just-past-the-end padding that we use to make sure 1184 -- that there's a valid portion after the buffer that isn't being 1185 -- included in our function calls. If we didn't have it, then we'd have 1186 -- a hard time feeling confident that an abort on the just-after tests 1187 -- isn't maybe from some other memory or stack protection. 1188 vars = vars .. "\t\tuint8_t padding_r;\n" 1189 vars = vars .. "\t} __stack;\n" 1190 1191 -- Not all tests will use __bufsz, but some do for, e.g., clearing 1192 -- memory.. 1193 vars = vars .. "\tconst size_t __bufsz __unused = " 1194 if heap then 1195 local scalar = 1 1196 if buftype_elem then 1197 scalar = size_expr 1198 end 1199 1200 vars = vars .. "sizeof(*__stack.__buf) * (" .. scalar .. ");\n" 1201 else 1202 vars = vars .. "sizeof(__stack.__buf);\n" 1203 end 1204 1205 vars = vars .. "\tconst size_t __len = " .. bufsize .. 1206 bufsize_offset .. ";\n" 1207 vars = vars .. "\tconst size_t __idx __unused = __len - 1;\n" 1208 1209 -- For overflow testing, we need to fork() because we're expecting the 1210 -- test to ultimately abort()/_exit(). Then we can collect the exit 1211 -- status and report appropriately. 1212 if disposition > 0 then 1213 vars = vars .. "\tpid_t __child;\n" 1214 vars = vars .. "\tint __status;\n" 1215 end 1216 1217 -- Any other stackvars defined by the test get placed after everything 1218 -- else. 1219 vars = vars .. (configurable(def, "stackvars") or "") 1220 1221 return vars 1222end 1223 1224local function write_test(fh, func, disposition, heap, def) 1225 local testname = generate_test_name(func, def.variant, disposition, heap) 1226 local buftype = def.buftype or "unsigned char[]" 1227 local bufsize = def.bufsize or 42 1228 local body = "" 1229 1230 if def.exclude and def.exclude(disposition, heap) then 1231 return 1232 end 1233 1234 local function need_addr() 1235 return not (buftype:match("%[%]") or buftype:match("%*")) 1236 end 1237 1238 if heap then 1239 body = body .. "#define BUF __stack.__buf\n" 1240 else 1241 body = body .. "#define BUF &__stack.__buf\n" 1242 end 1243 1244 -- Setup the buffer 1245 body = body .. generate_stackframe(buftype, bufsize, disposition, heap, def) .. 1246 "\n" 1247 1248 -- Any early initialization goes before we would fork for the just-after 1249 -- tests, because they may want to skip the test based on some criteria 1250 -- and we can't propagate that up very easily once we're forked. 1251 local early_init = configurable(def, "early_init") 1252 body = body .. (early_init or "") 1253 if early_init then 1254 body = body .. "\n" 1255 end 1256 1257 -- Fork off, iff we're testing some access past the end of the buffer. 1258 if disposition > 0 then 1259 body = body .. [[ 1260 __child = fork(); 1261 ATF_REQUIRE(__child >= 0); 1262 if (__child > 0) 1263 goto monitor; 1264 1265 /* Child */ 1266 disable_coredumps(); 1267]] 1268 end 1269 1270 local bufvar = "__stack.__buf" 1271 if heap then 1272 -- Buffer needs to be initialized because it's a heap allocation. 1273 body = body .. "\t" .. bufvar .. " = malloc(__bufsz);\n" 1274 end 1275 1276 -- Non-early init happens just after the fork in the child, not the 1277 -- monitor. This is used to setup any other buffers we may need, for 1278 -- instance. 1279 local extra_init = configurable(def, "init") 1280 body = body .. (extra_init or "") 1281 1282 if heap or extra_init then 1283 body = body .. "\n" 1284 end 1285 1286 -- Setup the function call with arguments as described in the test 1287 -- definition. 1288 body = body .. "\t" .. func .. "(" 1289 1290 for idx, arg in ipairs(def.arguments) do 1291 if idx > 1 then 1292 body = body .. ", " 1293 end 1294 1295 if arg == "__buf" then 1296 if not heap and need_addr() then 1297 body = body .. "&" 1298 end 1299 1300 body = body .. bufvar 1301 else 1302 local argname = arg 1303 1304 if def.value_of then 1305 argname = argname or def.value_of(arg) 1306 end 1307 1308 body = body .. argname 1309 end 1310 end 1311 1312 body = body .. ");\n" 1313 1314 -- Monitor stuff follows, for OOB access. 1315 if disposition <= 0 then 1316 goto skip 1317 end 1318 1319 body = body .. [[ 1320 _exit(EX_SOFTWARE); /* Should have aborted. */ 1321 1322monitor: 1323 while (waitpid(__child, &__status, 0) != __child) { 1324 ATF_REQUIRE_EQ(EINTR, errno); 1325 } 1326 1327 if (!WIFSIGNALED(__status)) { 1328 switch (WEXITSTATUS(__status)) { 1329 case EX_SOFTWARE: 1330 atf_tc_fail("FORTIFY_SOURCE failed to abort"); 1331 break; 1332 case EX_OSERR: 1333 atf_tc_fail("setrlimit(2) failed"); 1334 break; 1335 default: 1336 atf_tc_fail("child exited with status %d", 1337 WEXITSTATUS(__status)); 1338 } 1339 } else { 1340 ATF_REQUIRE_EQ(SIGABRT, WTERMSIG(__status)); 1341 } 1342]] 1343 1344::skip:: 1345 body = body .. "#undef BUF\n" 1346 return write_test_boilerplate(fh, testname, body, def) 1347end 1348 1349-- main() 1350local tests 1351local tcat = assert(arg[1], "usage: generate-fortify-tests.lua <category>") 1352for k, defs in pairs(all_tests) do 1353 if k == tcat then 1354 tests = defs 1355 break 1356 end 1357end 1358 1359assert(tests, "category " .. tcat .. " not found") 1360 1361local fh = io.stdout 1362fh:write("/* @" .. "generated" .. " by `generate-fortify-tests.lua \"" .. 1363 tcat .. "\"` */\n\n") 1364fh:write("#define _FORTIFY_SOURCE 2\n") 1365fh:write("#define TMPFILE_SIZE (1024 * 32)\n") 1366 1367fh:write("\n") 1368for _, inc in ipairs(includes) do 1369 fh:write("#include <" .. inc .. ">\n") 1370end 1371 1372fh:write([[ 1373 1374static FILE * __unused 1375new_fp(size_t __len) 1376{ 1377 static char fpbuf[LINE_MAX]; 1378 FILE *fp; 1379 1380 ATF_REQUIRE(__len <= sizeof(fpbuf)); 1381 1382 memset(fpbuf, 'A', sizeof(fpbuf) - 1); 1383 fpbuf[sizeof(fpbuf) - 1] = '\0'; 1384 1385 fp = fmemopen(fpbuf, sizeof(fpbuf), "rb"); 1386 ATF_REQUIRE(fp != NULL); 1387 1388 return (fp); 1389} 1390 1391/* 1392 * Create a new symlink to use for readlink(2) style tests, we'll just use a 1393 * random target name to have something interesting to look at. 1394 */ 1395static const char * __unused 1396new_symlink(size_t __len) 1397{ 1398 static const char linkname[] = "link"; 1399 char target[MAXNAMLEN]; 1400 int error; 1401 1402 ATF_REQUIRE(__len <= sizeof(target)); 1403 1404 arc4random_buf(target, sizeof(target)); 1405 1406 error = unlink(linkname); 1407 ATF_REQUIRE(error == 0 || errno == ENOENT); 1408 1409 error = symlink(target, linkname); 1410 ATF_REQUIRE(error == 0); 1411 1412 return (linkname); 1413} 1414 1415/* 1416 * For our purposes, first descriptor will be the reader; we'll send both 1417 * raw data and a control message over it so that the result can be used for 1418 * any of our recv*() tests. 1419 */ 1420static void __unused 1421new_socket(int sock[2]) 1422{ 1423 unsigned char ctrl[CMSG_SPACE(sizeof(int))] = { 0 }; 1424 static char sockbuf[256]; 1425 ssize_t rv; 1426 size_t total = 0; 1427 struct msghdr hdr = { 0 }; 1428 struct cmsghdr *cmsg; 1429 int error, fd; 1430 1431 error = socketpair(AF_UNIX, SOCK_STREAM, 0, sock); 1432 ATF_REQUIRE(error == 0); 1433 1434 while (total != sizeof(sockbuf)) { 1435 rv = send(sock[1], &sockbuf[total], sizeof(sockbuf) - total, 0); 1436 1437 ATF_REQUIRE_MSG(rv > 0, 1438 "expected bytes sent, got %zd with %zu left (size %zu, total %zu)", 1439 rv, sizeof(sockbuf) - total, sizeof(sockbuf), total); 1440 ATF_REQUIRE_MSG(total + (size_t)rv <= sizeof(sockbuf), 1441 "%zd exceeds total %zu", rv, sizeof(sockbuf)); 1442 total += rv; 1443 } 1444 1445 hdr.msg_control = ctrl; 1446 hdr.msg_controllen = sizeof(ctrl); 1447 1448 cmsg = CMSG_FIRSTHDR(&hdr); 1449 cmsg->cmsg_level = SOL_SOCKET; 1450 cmsg->cmsg_type = SCM_RIGHTS; 1451 cmsg->cmsg_len = CMSG_LEN(sizeof(fd)); 1452 fd = STDIN_FILENO; 1453 memcpy(CMSG_DATA(cmsg), &fd, sizeof(fd)); 1454 1455 error = sendmsg(sock[1], &hdr, 0); 1456 ATF_REQUIRE(error != -1); 1457} 1458 1459/* 1460 * Constructs a tmpfile that we can use for testing read(2) and friends. 1461 */ 1462static int __unused 1463new_tmpfile(void) 1464{ 1465 char buf[1024]; 1466 ssize_t rv; 1467 size_t written; 1468 int fd; 1469 1470 fd = open("tmpfile", O_RDWR | O_CREAT | O_TRUNC, 0644); 1471 ATF_REQUIRE(fd >= 0); 1472 1473 written = 0; 1474 while (written < TMPFILE_SIZE) { 1475 rv = write(fd, buf, sizeof(buf)); 1476 ATF_REQUIRE(rv > 0); 1477 1478 written += rv; 1479 } 1480 1481 ATF_REQUIRE_EQ(0, lseek(fd, 0, SEEK_SET)); 1482 return (fd); 1483} 1484 1485static void 1486disable_coredumps(void) 1487{ 1488 struct rlimit rl = { 0 }; 1489 1490 if (setrlimit(RLIMIT_CORE, &rl) == -1) 1491 _exit(EX_OSERR); 1492} 1493 1494/* 1495 * Replaces stdin with a file that we can actually read from, for tests where 1496 * we want a FILE * or fd that we can get data from. 1497 */ 1498static void __unused 1499replace_stdin(void) 1500{ 1501 int fd; 1502 1503 fd = new_tmpfile(); 1504 1505 (void)dup2(fd, STDIN_FILENO); 1506 if (fd != STDIN_FILENO) 1507 close(fd); 1508} 1509 1510]]) 1511 1512if tcat == "unistd" then 1513 fh:write("#define JAIL_HOSTNAME \"" .. hostname .. "\"\n") 1514 fh:write("#define JAIL_DOMAINNAME \"" .. domainname .. "\"\n") 1515 fh:write([[ 1516static void 1517dhost_jail(void) 1518{ 1519 struct iovec iov[4]; 1520 int jid; 1521 1522 iov[0].iov_base = __DECONST(char *, "host.hostname"); 1523 iov[0].iov_len = sizeof("host.hostname"); 1524 iov[1].iov_base = __DECONST(char *, JAIL_HOSTNAME); 1525 iov[1].iov_len = sizeof(JAIL_HOSTNAME); 1526 iov[2].iov_base = __DECONST(char *, "host.domainname"); 1527 iov[2].iov_len = sizeof("host.domainname"); 1528 iov[3].iov_base = __DECONST(char *, JAIL_DOMAINNAME); 1529 iov[3].iov_len = sizeof(JAIL_DOMAINNAME); 1530 1531 jid = jail_set(iov, nitems(iov), JAIL_CREATE | JAIL_ATTACH); 1532 ATF_REQUIRE_MSG(jid > 0, "Jail creation failed: %s", strerror(errno)); 1533} 1534 1535]]) 1536end 1537 1538for _, def in pairs(tests) do 1539 local func = def.func 1540 local function write_tests(heap) 1541 -- Dispositions here are relative to the buffer size prescribed 1542 -- by the test definition. 1543 local dispositions = def.dispositions or { -1, 0, 1 } 1544 1545 for _, disposition in ipairs(dispositions) do 1546 tests_added[#tests_added + 1] = write_test(fh, func, disposition, heap, def) 1547 end 1548 end 1549 1550 write_tests(false) 1551 write_tests(true) 1552end 1553 1554fh:write("ATF_TP_ADD_TCS(tp)\n") 1555fh:write("{\n") 1556for idx = 1, #tests_added do 1557 fh:write("\tATF_TP_ADD_TC(tp, " .. tests_added[idx] .. ");\n") 1558end 1559fh:write("\treturn (atf_no_error());\n") 1560fh:write("}\n") 1561