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