xref: /freebsd/lib/libc/tests/secure/generate-fortify-tests.lua (revision b0334450aa527ccbac7a3c37d1ba75ef96c7be91)
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