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