diff --git a/include/ssp/stdlib.h b/include/ssp/stdlib.h index f595ecbcbc0a..0a87be9f17d9 100644 --- a/include/ssp/stdlib.h +++ b/include/ssp/stdlib.h @@ -1,53 +1,57 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2024, Klara, Inc. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #ifndef _SSP_STDLIB_H_ #define _SSP_STDLIB_H_ #include #if __SSP_FORTIFY_LEVEL > 0 #include __BEGIN_DECLS __ssp_redirect(void, arc4random_buf, (void *__buf, size_t __len), (__buf, __len)); +__ssp_redirect(int, getenv_r, + (const char *name, char * _Nonnull __buf, size_t __len), + (name, __buf, __len)); + __ssp_redirect_raw_impl(char *, realpath, realpath, (const char *__restrict path, char *__restrict buf)) { if (__ssp_bos(buf) < PATH_MAX) __chk_fail(); return (__ssp_real(realpath)(path, buf)); } __END_DECLS #endif /* __SSP_FORTIFY_LEVEL > 0 */ #endif /* _SSP_STDLIB_H_ */ diff --git a/include/stdlib.h b/include/stdlib.h index 162031ab393d..ba0cf4b5e88e 100644 --- a/include/stdlib.h +++ b/include/stdlib.h @@ -1,400 +1,403 @@ /*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 1990, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef _STDLIB_H_ #define _STDLIB_H_ #include #include #include __NULLABILITY_PRAGMA_PUSH #ifndef _SIZE_T_DECLARED typedef __size_t size_t; #define _SIZE_T_DECLARED #endif #ifndef __cplusplus #ifndef _WCHAR_T_DECLARED typedef ___wchar_t wchar_t; #define _WCHAR_T_DECLARED #endif #endif typedef struct { int quot; /* quotient */ int rem; /* remainder */ } div_t; typedef struct { long quot; long rem; } ldiv_t; #define EXIT_FAILURE 1 #define EXIT_SUCCESS 0 /* * I.e., INT_MAX; rand(3) returns a signed integer but must produce output in * the range [0, RAND_MAX], so half of the possible output range is unused. */ #define RAND_MAX 0x7fffffff #if !defined(_STANDALONE) && defined(_FORTIFY_SOURCE) && _FORTIFY_SOURCE > 0 #include #endif __BEGIN_DECLS #ifdef _XLOCALE_H_ #include #endif extern int __mb_cur_max; extern int ___mb_cur_max(void); #define MB_CUR_MAX ((size_t)___mb_cur_max()) _Noreturn void abort(void) __noexcept; int abs(int) __pure2; int atexit(void (* _Nonnull)(void)) __noexcept; double atof(const char *); int atoi(const char *); long atol(const char *); void *bsearch(const void *, const void *, size_t, size_t, int (*)(const void * _Nonnull, const void *)); void *calloc(size_t, size_t) __malloc_like __result_use_check __alloc_size2(1, 2); div_t div(int, int) __pure2; _Noreturn void exit(int); void free(void *); char *getenv(const char *); +#if __BSD_VISIBLE +int getenv_r(const char *, char * _Nonnull, size_t); +#endif long labs(long) __pure2; ldiv_t ldiv(long, long) __pure2; void *malloc(size_t) __malloc_like __result_use_check __alloc_size(1); int mblen(const char *, size_t); size_t mbstowcs(wchar_t * __restrict , const char * __restrict, size_t); int mbtowc(wchar_t * __restrict, const char * __restrict, size_t); void qsort(void *, size_t, size_t, int (* _Nonnull)(const void *, const void *)); int rand(void); void *realloc(void *, size_t) __result_use_check __alloc_size(2); void srand(unsigned); double strtod(const char * __restrict, char ** __restrict); float strtof(const char * __restrict, char ** __restrict); long strtol(const char * __restrict, char ** __restrict, int); long double strtold(const char * __restrict, char ** __restrict); unsigned long strtoul(const char * __restrict, char ** __restrict, int); int system(const char *); int wctomb(char *, wchar_t); size_t wcstombs(char * __restrict, const wchar_t * __restrict, size_t); /* * Functions added in C99 which we make conditionally available in the * BSD^C89 namespace if the compiler supports `long long'. * The #if test is more complicated than it ought to be because * __BSD_VISIBLE implies __ISO_C_VISIBLE == 1999 *even if* `long long' * is not supported in the compilation environment (which therefore means * that it can't really be ISO C99). * * (The only other extension made by C99 in this header is _Exit().) */ #if __ISO_C_VISIBLE >= 1999 || defined(__cplusplus) #ifdef __LONG_LONG_SUPPORTED /* LONGLONG */ typedef struct { long long quot; long long rem; } lldiv_t; /* LONGLONG */ long long atoll(const char *); /* LONGLONG */ long long llabs(long long) __pure2; /* LONGLONG */ lldiv_t lldiv(long long, long long) __pure2; /* LONGLONG */ long long strtoll(const char * __restrict, char ** __restrict, int); /* LONGLONG */ unsigned long long strtoull(const char * __restrict, char ** __restrict, int); #endif /* __LONG_LONG_SUPPORTED */ _Noreturn void _Exit(int) __noexcept; #endif /* __ISO_C_VISIBLE >= 1999 */ /* * If we're in a mode greater than C99, expose C11 functions. */ #if __ISO_C_VISIBLE >= 2011 || __cplusplus >= 201103L void * aligned_alloc(size_t, size_t) __malloc_like __alloc_align(1) __alloc_size(2); int at_quick_exit(void (*)(void)) __noexcept; _Noreturn void quick_exit(int) /* __noexcept -- not ready ABI issues? */; #endif /* __ISO_C_VISIBLE >= 2011 */ /* * Extensions made by POSIX relative to C. */ #if __POSIX_VISIBLE >= 199506 || __XSI_VISIBLE char *realpath(const char * __restrict, char * __restrict); #endif #if __POSIX_VISIBLE >= 199506 int rand_r(unsigned *); /* (TSF) */ #endif #if __POSIX_VISIBLE >= 200112 int posix_memalign(void **, size_t, size_t); /* (ADV) */ int setenv(const char *, const char *, int); int unsetenv(const char *); #endif #if __POSIX_VISIBLE >= 200809 || __XSI_VISIBLE int getsubopt(char **, char *const *, char **); #ifndef _MKDTEMP_DECLARED char *mkdtemp(char *); #define _MKDTEMP_DECLARED #endif #ifndef _MKSTEMP_DECLARED int mkstemp(char *); #define _MKSTEMP_DECLARED #endif #endif /* __POSIX_VISIBLE >= 200809 || __XSI_VISIBLE */ /* * The only changes to the XSI namespace in revision 6 were the deletion * of the ttyslot() and valloc() functions, which FreeBSD never declared * in this header. For revision 7, ecvt(), fcvt(), and gcvt(), which * FreeBSD also does not have, and mktemp(), are to be deleted. */ #if __XSI_VISIBLE /* XXX XSI requires pollution from here. We'd rather not. */ long a64l(const char *); double drand48(void); /* char *ecvt(double, int, int * __restrict, int * __restrict); */ double erand48(unsigned short[3]); /* char *fcvt(double, int, int * __restrict, int * __restrict); */ /* char *gcvt(double, int, int * __restrict, int * __restrict); */ char *initstate(unsigned int, char *, size_t); long jrand48(unsigned short[3]); char *l64a(long); void lcong48(unsigned short[7]); long lrand48(void); #if !defined(_MKTEMP_DECLARED) && (__BSD_VISIBLE || __XSI_VISIBLE <= 600) char *mktemp(char *); #define _MKTEMP_DECLARED #endif long mrand48(void); long nrand48(unsigned short[3]); int putenv(char *); long random(void); unsigned short *seed48(unsigned short[3]); char *setstate(/* const */ char *); void srand48(long); void srandom(unsigned int); #endif /* __XSI_VISIBLE */ #if __XSI_VISIBLE int grantpt(int); int posix_openpt(int); char *ptsname(int); int unlockpt(int); #endif /* __XSI_VISIBLE */ #if __BSD_VISIBLE /* ptsname_r will be included in POSIX issue 8 */ int ptsname_r(int, char *, size_t); #endif #if __BSD_VISIBLE extern const char *malloc_conf; extern void (*malloc_message)(void *, const char *); /* * The alloca() function can't be implemented in C, and on some * platforms it can't be implemented at all as a callable function. * The GNU C compiler provides a built-in alloca() which we can use. * On platforms where alloca() is not in libc, programs which use it * will fail to link when compiled with non-GNU compilers. */ #if __GNUC__ >= 2 #undef alloca /* some GNU bits try to get cute and define this on their own */ #define alloca(sz) __builtin_alloca(sz) #endif void abort2(const char *, int, void **) __dead2; __uint32_t arc4random(void); void arc4random_buf(void *, size_t); __uint32_t arc4random_uniform(__uint32_t); #ifdef __BLOCKS__ int atexit_b(void (^ _Nonnull)(void)); void *bsearch_b(const void *, const void *, size_t, size_t, int (^ _Nonnull)(const void *, const void *)); #endif char *getbsize(int *, long *); /* getcap(3) functions */ char *cgetcap(char *, const char *, int); int cgetclose(void); int cgetent(char **, char **, const char *); int cgetfirst(char **, char **); int cgetmatch(const char *, const char *); int cgetnext(char **, char **); int cgetnum(char *, const char *, long *); int cgetset(const char *); int cgetstr(char *, const char *, char **); int cgetustr(char *, const char *, char **); int clearenv(void); int daemon(int, int); int daemonfd(int, int); char *devname(__dev_t, __mode_t); char *devname_r(__dev_t, __mode_t, char *, int); char *fdevname(int); char *fdevname_r(int, char *, int); int getloadavg(double [], int); const char * getprogname(void); int heapsort(void *, size_t, size_t, int (* _Nonnull)(const void *, const void *)); #ifdef __BLOCKS__ int heapsort_b(void *, size_t, size_t, int (^ _Nonnull)(const void *, const void *)); void qsort_b(void *, size_t, size_t, int (^ _Nonnull)(const void *, const void *)); #endif int l64a_r(long, char *, int); int mergesort(void *, size_t, size_t, int (*)(const void *, const void *)); #ifdef __BLOCKS__ int mergesort_b(void *, size_t, size_t, int (^)(const void *, const void *)); #endif int mkostemp(char *, int); int mkostemps(char *, int, int); int mkostempsat(int, char *, int, int); void qsort_r(void *, size_t, size_t, int (*)(const void *, const void *, void *), void *); int radixsort(const unsigned char **, int, const unsigned char *, unsigned); void *reallocarray(void *, size_t, size_t) __result_use_check __alloc_size2(2, 3); void *reallocf(void *, size_t) __result_use_check __alloc_size(2); int rpmatch(const char *); char *secure_getenv(const char *); void setprogname(const char *); int sradixsort(const unsigned char **, int, const unsigned char *, unsigned); void srandomdev(void); long long strtonum(const char *, long long, long long, const char **); /* Deprecated interfaces, to be removed. */ __int64_t strtoq(const char *, char **, int); __uint64_t strtouq(const char *, char **, int); /* * In FreeBSD 14, the prototype of qsort_r() was modified to comply with * POSIX. The standardized qsort_r()'s order of last two parameters was * changed, and the comparator function is now taking thunk as its last * parameter, and both are different from the ones expected by the historical * FreeBSD qsort_r() interface. * * Apply a workaround where we explicitly link against the historical interface, * qsort_r@FBSD_1.0, in case when qsort_r() is called with the last parameter * with a function pointer that exactly matches the historical FreeBSD qsort_r() * comparator signature, so applications written for the historical interface * can continue to work without modification. Toolchains that don't support * symbol versioning don't define __sym_compat, so only provide this symbol in * supported environments. */ #ifdef __sym_compat #if defined(__generic) || defined(__cplusplus) void __qsort_r_compat(void *, size_t, size_t, void *, int (*)(void *, const void *, const void *)); __sym_compat(qsort_r, __qsort_r_compat, FBSD_1.0); #endif #endif #if defined(__generic) && !defined(__cplusplus) #define qsort_r(base, nel, width, arg4, arg5) \ __generic(arg5, int (*)(void *, const void *, const void *), \ __qsort_r_compat, qsort_r)(base, nel, width, arg4, arg5) #elif defined(__cplusplus) && defined(__sym_compat) __END_DECLS extern "C++" { static inline void qsort_r(void *base, size_t nmemb, size_t size, void *thunk, int (*compar)(void *, const void *, const void *)) { __qsort_r_compat(base, nmemb, size, thunk, compar); } } __BEGIN_DECLS #endif extern char *suboptarg; /* getsubopt(3) external variable */ #endif /* __BSD_VISIBLE */ #if __EXT1_VISIBLE #ifndef _RSIZE_T_DEFINED #define _RSIZE_T_DEFINED typedef size_t rsize_t; #endif #ifndef _ERRNO_T_DEFINED #define _ERRNO_T_DEFINED typedef int errno_t; #endif /* K.3.6 */ typedef void (*constraint_handler_t)(const char * __restrict, void * __restrict, errno_t); /* K.3.6.1.1 */ constraint_handler_t set_constraint_handler_s(constraint_handler_t handler); /* K.3.6.1.2 */ _Noreturn void abort_handler_s(const char * __restrict, void * __restrict, errno_t); /* K3.6.1.3 */ void ignore_handler_s(const char * __restrict, void * __restrict, errno_t); /* K.3.6.3.2 */ errno_t qsort_s(void *, rsize_t, rsize_t, int (*)(const void *, const void *, void *), void *); #endif /* __EXT1_VISIBLE */ __END_DECLS __NULLABILITY_PRAGMA_POP #endif /* !_STDLIB_H_ */ diff --git a/lib/libc/stdlib/Symbol.map b/lib/libc/stdlib/Symbol.map index d74df869cf54..2b79ca2ece8b 100644 --- a/lib/libc/stdlib/Symbol.map +++ b/lib/libc/stdlib/Symbol.map @@ -1,136 +1,140 @@ FBSD_1.0 { _Exit; a64l; abort; abs; atexit; __cxa_atexit; __cxa_finalize; atof; atoi; atol; atoll; bsearch; div; __isthreaded; exit; getenv; opterr; optind; optopt; optreset; optarg; getopt; getopt_long; getopt_long_only; suboptarg; getsubopt; grantpt; ptsname; unlockpt; hcreate; hdestroy; hsearch; heapsort; imaxabs; imaxdiv; insque; l64a; l64a_r; labs; ldiv; llabs; lldiv; lsearch; lfind; mergesort; putenv; qsort; radixsort; sradixsort; rand_r; srandom; srandomdev; initstate; setstate; random; reallocf; realpath; remque; setenv; unsetenv; strfmon; strtoimax; strtol; strtoll; strtonum; strtoq; strtoul; strtoull; strtoumax; strtouq; system; tdelete; tfind; tsearch; twalk; }; FBSD_1.3 { at_quick_exit; atof_l; atoi_l; atol_l; atoll_l; quick_exit; strtod_l; strtof_l; strtoimax_l; strtol_l; strtold_l; strtoll_l; strtoul_l; strtoull_l; strtoumax_l; }; FBSD_1.4 { atexit_b; bsearch_b; heapsort_b; mergesort_b; qsort_b; hcreate_r; hdestroy_r; hsearch_r; reallocarray; }; FBSD_1.5 { __cxa_thread_atexit; __cxa_thread_atexit_impl; abort_handler_s; ignore_handler_s; set_constraint_handler_s; }; FBSD_1.6 { ptsname_r; qsort_s; rand; srand; }; FBSD_1.7 { clearenv; qsort_r; secure_getenv; }; +FBSD_1.8 { + getenv_r; +}; + FBSDprivate_1.0 { __system; _system; __libc_system; __cxa_thread_call_dtors; __libc_atexit; }; diff --git a/lib/libc/stdlib/getenv.3 b/lib/libc/stdlib/getenv.3 index d78dcfbef9eb..045a1831dfdc 100644 --- a/lib/libc/stdlib/getenv.3 +++ b/lib/libc/stdlib/getenv.3 @@ -1,270 +1,302 @@ .\" Copyright (c) 1988, 1991, 1993 .\" The Regents of the University of California. All rights reserved. .\" .\" This code is derived from software contributed to Berkeley by .\" the American National Standards Committee X3, on Information .\" Processing Systems. .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions .\" are met: .\" 1. Redistributions of source code must retain the above copyright .\" notice, this list of conditions and the following disclaimer. .\" 2. Redistributions in binary form must reproduce the above copyright .\" notice, this list of conditions and the following disclaimer in the .\" documentation and/or other materials provided with the distribution. .\" 3. Neither the name of the University nor the names of its contributors .\" may be used to endorse or promote products derived from this software .\" without specific prior written permission. .\" .\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND .\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE .\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE .\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE .\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL .\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS .\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) .\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT .\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd April 17, 2025 +.Dd April 22, 2025 .Dt GETENV 3 .Os .Sh NAME .Nm clearenv , .Nm getenv , +.Nm getenv_r , .Nm putenv , .Nm secure_getenv , .Nm setenv , .Nm unsetenv .Nd environment variable functions .Sh LIBRARY .Lb libc .Sh SYNOPSIS .In stdlib.h .Ft int .Fn clearenv "void" .Ft char * .Fn getenv "const char *name" +.Ft int +.Fn getenv_r "const char *name" "char *buf" "size_t len" .Ft char * .Fn secure_getenv "const char *name" .Ft int .Fn setenv "const char *name" "const char *value" "int overwrite" .Ft int .Fn putenv "char *string" .Ft int .Fn unsetenv "const char *name" .Sh DESCRIPTION These functions set, unset and fetch environment variables from the host .Em environment list . .Pp The .Fn clearenv function clears all environment variables. New variables can be added using .Fn setenv and .Fn putenv . .Pp The .Fn getenv -function obtains the current value of the environment variable, +function obtains the current value of the environment variable +designated by .Fa name . The application should not modify the string pointed to by the .Fn getenv function. .Pp The +.Fn getenv_r +function obtains the current value of the environment variable +designated by +.Fa name +and copies it into the buffer +.Fa buf +of length +.Fa len . +.Pp +The .Fn secure_getenv returns .Va NULL when the environment cannot be trusted, otherwise it acts like .Fn getenv . The environment currently is not trusted when .Xr issetugid 2 returns a non-zero value, but other conditions may be added in the future. .Pp The .Fn setenv function inserts or resets the environment variable .Fa name in the current environment list. If the variable .Fa name does not exist in the list, it is inserted with the given .Fa value . If the variable does exist, the argument .Fa overwrite is tested; if .Fa overwrite is zero, the variable is not reset, otherwise it is reset to the given .Fa value . .Pp The .Fn putenv function takes an argument of the form ``name=value'' and puts it directly into the current environment, so altering the argument shall change the environment. If the variable .Fa name does not exist in the list, it is inserted with the given .Fa value . If the variable .Fa name does exist, it is reset to the given .Fa value . .Pp The .Fn unsetenv function deletes all instances of the variable name pointed to by .Fa name from the list. .Pp If corruption (e.g., a name without a value) is detected while making a copy of environ for internal usage, then .Fn setenv , .Fn unsetenv and .Fn putenv will output a warning to stderr about the issue, drop the corrupt entry and complete the task without error. .Sh RETURN VALUES The .Fn getenv function returns the value of the environment variable as a .Dv NUL Ns -terminated string. If the variable .Fa name is not in the current environment, .Dv NULL is returned. .Pp The .Fn secure_getenv function returns .Dv NULL if the process is in "secure execution," otherwise it will call .Fn getenv . .Pp -.Rv -std clearenv setenv putenv unsetenv +.Rv -std clearenv getenv_r setenv putenv unsetenv .Sh ERRORS .Bl -tag -width Er .It Bq Er EINVAL The function .Fn getenv , +.Fn getenv_r , .Fn setenv or .Fn unsetenv failed because the .Fa name is a .Dv NULL pointer, points to an empty string, or points to a string containing an .Dq Li \&= character. .Pp The function .Fn putenv failed because .Fa string is a .Dv NULL pointer, .Fa string is without an .Dq Li \&= character or .Dq Li \&= is the first character in .Fa string . This does not follow the .Tn POSIX specification. +.It Bq Er ENOENT +The function +.Fn getenv_r +failed because the requested variable was not found in the +environment. .It Bq Er ENOMEM The function .Fn setenv , .Fn unsetenv or .Fn putenv failed because they were unable to allocate memory for the environment. +.It Bq Er ERANGE +The function +.Fn getenv_r +failed because the value of the requested variable was too long to fit +in the provided buffer. .El .Sh SEE ALSO .Xr csh 1 , .Xr sh 1 , .Xr execve 2 , .Xr environ 7 .Sh STANDARDS The .Fn getenv function conforms to .St -isoC . The .Fn setenv , .Fn putenv and .Fn unsetenv functions conforms to .St -p1003.1-2001 . The .Fn secure_getenv function conforms to .St -p1003.1-2024 . .Sh HISTORY The functions .Fn setenv and .Fn unsetenv appeared in .At v7 . The .Fn putenv function appeared in .Bx 4.3 Reno . .Pp Until .Fx 7.0 , .Fn putenv would make a copy of .Fa string and insert it into the environment using .Fn setenv . This was changed to use .Fa string as the memory location of the ``name=value'' pair to follow the .Tn POSIX specification. .Pp The .Fn clearenv and .Fn secure_getenv functions were added in .Fx 14 . +.Pp +The +.Fn getenv_r +function first appeared in +.Nx 4.0 +and was added in +.Fx 15 . .Sh BUGS Successive calls to .Fn setenv that assign a larger-sized .Fa value than any previous value to the same .Fa name will result in a memory leak. The .Fx semantics for this function (namely, that the contents of .Fa value are copied and that old values remain accessible indefinitely) make this bug unavoidable. Future versions may eliminate one or both of these semantic guarantees in order to fix the bug. diff --git a/lib/libc/stdlib/getenv.c b/lib/libc/stdlib/getenv.c index f8f59526421a..c1d0b7a559d5 100644 --- a/lib/libc/stdlib/getenv.c +++ b/lib/libc/stdlib/getenv.c @@ -1,725 +1,761 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2007-2009 Sean C. Farley * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer, * without modification, immediately at the beginning of the file. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "namespace.h" #include +#include #include #include #include #include #include #include #include "un-namespace.h" #include "libc_private.h" static const char CorruptEnvFindMsg[] = "environment corrupt; unable to find "; static const char CorruptEnvValueMsg[] = "environment corrupt; missing value for "; /* * Standard environ. environ variable is exposed to entire process. * * origEnviron: Upon cleanup on unloading of library or failure, this * allows environ to return to as it was before. * environSize: Number of variables environ can hold. Can only * increase. * intEnviron: Internally-built environ. Exposed via environ during * (re)builds of the environment. */ static char **origEnviron; static char **intEnviron = NULL; static int environSize = 0; /* * Array of environment variables built from environ. Each element records: * name: Pointer to name=value string * name length: Length of name not counting '=' character * value: Pointer to value within same string as name * value size: Size (not length) of space for value not counting the * nul character * active state: true/false value to signify whether variable is active. * Useful since multiple variables with the same name can * co-exist. At most, one variable can be active at any * one time. * putenv: Created from putenv() call. This memory must not be * reused. */ static struct envVars { size_t nameLen; size_t valueSize; char *name; char *value; bool active; bool putenv; } *envVars = NULL; /* * Environment array information. * * envActive: Number of active variables in array. * envVarsSize: Size of array. * envVarsTotal: Number of total variables in array (active or not). */ static int envActive = 0; static int envVarsSize = 0; static int envVarsTotal = 0; /* Deinitialization of new environment. */ static void __attribute__ ((destructor)) __clean_env_destructor(void); /* * A simple version of warnx() to avoid the bloat of including stdio in static * binaries. */ static void __env_warnx(const char *msg, const char *name, size_t nameLen) { static const char nl[] = "\n"; static const char progSep[] = ": "; _write(STDERR_FILENO, _getprogname(), strlen(_getprogname())); _write(STDERR_FILENO, progSep, sizeof(progSep) - 1); _write(STDERR_FILENO, msg, strlen(msg)); _write(STDERR_FILENO, name, nameLen); _write(STDERR_FILENO, nl, sizeof(nl) - 1); return; } /* * Inline strlen() for performance. Also, perform check for an equals sign. * Cheaper here than performing a strchr() later. */ static inline size_t __strleneq(const char *str) { const char *s; for (s = str; *s != '\0'; ++s) if (*s == '=') return (0); return (s - str); } /* * Comparison of an environment name=value to a name. */ static inline bool strncmpeq(const char *nameValue, const char *name, size_t nameLen) { if (strncmp(nameValue, name, nameLen) == 0 && nameValue[nameLen] == '=') return (true); return (false); } /* * Using environment, returns pointer to value associated with name, if any, * else NULL. If the onlyActive flag is set to true, only variables that are * active are returned else all are. */ static inline char * __findenv(const char *name, size_t nameLen, int *envNdx, bool onlyActive) { int ndx; /* * Find environment variable from end of array (more likely to be * active). A variable created by putenv is always active, or it is not * tracked in the array. */ for (ndx = *envNdx; ndx >= 0; ndx--) if (envVars[ndx].putenv) { if (strncmpeq(envVars[ndx].name, name, nameLen)) { *envNdx = ndx; return (envVars[ndx].name + nameLen + sizeof ("=") - 1); } } else if ((!onlyActive || envVars[ndx].active) && (envVars[ndx].nameLen == nameLen && strncmpeq(envVars[ndx].name, name, nameLen))) { *envNdx = ndx; return (envVars[ndx].value); } return (NULL); } /* * Using environ, returns pointer to value associated with name, if any, else * NULL. Used on the original environ passed into the program. */ static char * __findenv_environ(const char *name, size_t nameLen) { int envNdx; /* Find variable within environ. */ for (envNdx = 0; environ[envNdx] != NULL; envNdx++) if (strncmpeq(environ[envNdx], name, nameLen)) return (&(environ[envNdx][nameLen + sizeof("=") - 1])); return (NULL); } /* * Remove variable added by putenv() from variable tracking array. */ static void __remove_putenv(int envNdx) { envVarsTotal--; if (envVarsTotal > envNdx) memmove(&(envVars[envNdx]), &(envVars[envNdx + 1]), (envVarsTotal - envNdx) * sizeof (*envVars)); memset(&(envVars[envVarsTotal]), 0, sizeof (*envVars)); return; } /* * Deallocate the environment built from environ as well as environ then set * both to NULL. Eases debugging of memory leaks. */ static void __clean_env(bool freeVars) { int envNdx; /* Deallocate environment and environ if created by *env(). */ if (envVars != NULL) { for (envNdx = envVarsTotal - 1; envNdx >= 0; envNdx--) /* Free variables or deactivate them. */ if (envVars[envNdx].putenv) { if (!freeVars) __remove_putenv(envNdx); } else { if (freeVars) free(envVars[envNdx].name); else envVars[envNdx].active = false; } if (freeVars) { free(envVars); envVars = NULL; } else envActive = 0; /* Restore original environ if it has not updated by program. */ if (origEnviron != NULL) { if (environ == intEnviron) environ = origEnviron; free(intEnviron); intEnviron = NULL; environSize = 0; } } return; } /* * Using the environment, rebuild the environ array for use by other C library * calls that depend upon it. */ static int __rebuild_environ(int newEnvironSize) { char **tmpEnviron; int envNdx; int environNdx; int tmpEnvironSize; /* Resize environ. */ if (newEnvironSize > environSize) { tmpEnvironSize = newEnvironSize * 2; tmpEnviron = reallocarray(intEnviron, tmpEnvironSize + 1, sizeof(*intEnviron)); if (tmpEnviron == NULL) return (-1); environSize = tmpEnvironSize; intEnviron = tmpEnviron; } envActive = newEnvironSize; /* Assign active variables to environ. */ for (envNdx = envVarsTotal - 1, environNdx = 0; envNdx >= 0; envNdx--) if (envVars[envNdx].active) intEnviron[environNdx++] = envVars[envNdx].name; intEnviron[environNdx] = NULL; /* Always set environ which may have been replaced by program. */ environ = intEnviron; return (0); } /* * Enlarge new environment. */ static inline bool __enlarge_env(void) { int newEnvVarsSize; struct envVars *tmpEnvVars; envVarsTotal++; if (envVarsTotal > envVarsSize) { newEnvVarsSize = envVarsTotal * 2; tmpEnvVars = reallocarray(envVars, newEnvVarsSize, sizeof(*envVars)); if (tmpEnvVars == NULL) { envVarsTotal--; return (false); } envVarsSize = newEnvVarsSize; envVars = tmpEnvVars; } return (true); } /* * Using environ, build an environment for use by standard C library calls. */ static int __build_env(void) { char **env; int activeNdx; int envNdx; int savedErrno; size_t nameLen; /* Check for non-existant environment. */ if (environ == NULL || environ[0] == NULL) return (0); /* Count environment variables. */ for (env = environ, envVarsTotal = 0; *env != NULL; env++) envVarsTotal++; envVarsSize = envVarsTotal * 2; /* Create new environment. */ envVars = calloc(envVarsSize, sizeof(*envVars)); if (envVars == NULL) goto Failure; /* Copy environ values and keep track of them. */ for (envNdx = envVarsTotal - 1; envNdx >= 0; envNdx--) { envVars[envNdx].putenv = false; envVars[envNdx].name = strdup(environ[envVarsTotal - envNdx - 1]); if (envVars[envNdx].name == NULL) goto Failure; envVars[envNdx].value = strchr(envVars[envNdx].name, '='); if (envVars[envNdx].value != NULL) { envVars[envNdx].value++; envVars[envNdx].valueSize = strlen(envVars[envNdx].value); } else { __env_warnx(CorruptEnvValueMsg, envVars[envNdx].name, strlen(envVars[envNdx].name)); errno = EFAULT; goto Failure; } /* * Find most current version of variable to make active. This * will prevent multiple active variables from being created * during this initialization phase. */ nameLen = envVars[envNdx].value - envVars[envNdx].name - 1; envVars[envNdx].nameLen = nameLen; activeNdx = envVarsTotal - 1; if (__findenv(envVars[envNdx].name, nameLen, &activeNdx, false) == NULL) { __env_warnx(CorruptEnvFindMsg, envVars[envNdx].name, nameLen); errno = EFAULT; goto Failure; } envVars[activeNdx].active = true; } /* Create a new environ. */ origEnviron = environ; environ = NULL; if (__rebuild_environ(envVarsTotal) == 0) return (0); Failure: savedErrno = errno; __clean_env(true); errno = savedErrno; return (-1); } /* * Destructor function with default argument to __clean_env(). */ static void __clean_env_destructor(void) { __clean_env(true); return; } /* * Returns the value of a variable or NULL if none are found. */ char * getenv(const char *name) { int envNdx; size_t nameLen; /* Check for malformed name. */ if (name == NULL || (nameLen = __strleneq(name)) == 0) { errno = EINVAL; return (NULL); } /* * Variable search order: * 1. Check for an empty environ. This allows an application to clear * the environment. * 2. Search the external environ array. * 3. Search the internal environment. * * Since malloc() depends upon getenv(), getenv() must never cause the * internal environment storage to be generated. */ if (environ == NULL || environ[0] == NULL) return (NULL); else if (envVars == NULL || environ != intEnviron) return (__findenv_environ(name, nameLen)); else { envNdx = envVarsTotal - 1; return (__findenv(name, nameLen, &envNdx, true)); } } +/* + * Like getenv(), but copies the value into the provided buffer. + */ +int +__ssp_real(getenv_r)(const char *name, char *buf, size_t len) +{ + const char *val; + size_t nameLen; + int envNdx; + + if (name == NULL || (nameLen = __strleneq(name)) == 0) { + errno = EINVAL; + return (-1); + } + + if (environ == NULL || environ[0] == NULL) { + val = NULL; + } else if (envVars == NULL || environ != intEnviron) { + val = __findenv_environ(name, nameLen); + } else { + envNdx = envVarsTotal - 1; + val = __findenv(name, nameLen, &envNdx, true); + } + if (val == NULL) { + errno = ENOENT; + return (-1); + } + if (strlcpy(buf, val, len) >= len) { + errno = ERANGE; + return (-1); + } + return (0); +} + + /* * Runs getenv() unless the current process is tainted by uid or gid changes, in * which case it will return NULL. */ char * secure_getenv(const char *name) { if (issetugid()) return (NULL); return (getenv(name)); } /* * Set the value of a variable. Older settings are labeled as inactive. If an * older setting has enough room to store the new value, it will be reused. No * previous variables are ever freed here to avoid causing a segmentation fault * in a user's code. * * The variables nameLen and valueLen are passed into here to allow the caller * to calculate the length by means besides just strlen(). */ static int __setenv(const char *name, size_t nameLen, const char *value, int overwrite) { bool reuse; char *env; int envNdx; int newEnvActive; size_t valueLen; /* Find existing environment variable large enough to use. */ envNdx = envVarsTotal - 1; newEnvActive = envActive; valueLen = strlen(value); reuse = false; if (__findenv(name, nameLen, &envNdx, false) != NULL) { /* Deactivate entry if overwrite is allowed. */ if (envVars[envNdx].active) { if (overwrite == 0) return (0); envVars[envNdx].active = false; newEnvActive--; } /* putenv() created variable cannot be reused. */ if (envVars[envNdx].putenv) __remove_putenv(envNdx); /* Entry is large enough to reuse. */ else if (envVars[envNdx].valueSize >= valueLen) reuse = true; } /* Create new variable if none was found of sufficient size. */ if (! reuse) { /* Enlarge environment. */ envNdx = envVarsTotal; if (!__enlarge_env()) return (-1); /* Create environment entry. */ envVars[envNdx].name = malloc(nameLen + sizeof ("=") + valueLen); if (envVars[envNdx].name == NULL) { envVarsTotal--; return (-1); } envVars[envNdx].nameLen = nameLen; envVars[envNdx].valueSize = valueLen; /* Save name of name/value pair. */ env = stpncpy(envVars[envNdx].name, name, nameLen); *env++ = '='; } else env = envVars[envNdx].value; /* Save value of name/value pair. */ strcpy(env, value); envVars[envNdx].value = env; envVars[envNdx].active = true; newEnvActive++; /* No need to rebuild environ if an active variable was reused. */ if (reuse && newEnvActive == envActive) return (0); else return (__rebuild_environ(newEnvActive)); } /* * If the program attempts to replace the array of environment variables * (environ) environ or sets the first varible to NULL, then deactivate all * variables and merge in the new list from environ. */ static int __merge_environ(void) { char **env; char *equals; /* * Internally-built environ has been replaced or cleared (detected by * using the count of active variables against a NULL as the first value * in environ). Clean up everything. */ if (intEnviron != NULL && (environ != intEnviron || (envActive > 0 && environ[0] == NULL))) { /* Deactivate all environment variables. */ if (envActive > 0) { origEnviron = NULL; __clean_env(false); } /* * Insert new environ into existing, yet deactivated, * environment array. */ origEnviron = environ; if (origEnviron != NULL) for (env = origEnviron; *env != NULL; env++) { if ((equals = strchr(*env, '=')) == NULL) { __env_warnx(CorruptEnvValueMsg, *env, strlen(*env)); errno = EFAULT; return (-1); } if (__setenv(*env, equals - *env, equals + 1, 1) == -1) return (-1); } } return (0); } /* * The exposed setenv() that performs a few tests before calling the function * (__setenv()) that does the actual work of inserting a variable into the * environment. */ int setenv(const char *name, const char *value, int overwrite) { size_t nameLen; /* Check for malformed name. */ if (name == NULL || (nameLen = __strleneq(name)) == 0) { errno = EINVAL; return (-1); } /* Initialize environment. */ if (__merge_environ() == -1 || (envVars == NULL && __build_env() == -1)) return (-1); return (__setenv(name, nameLen, value, overwrite)); } /* * Insert a "name=value" string into the environment. Special settings must be * made to keep setenv() from reusing this memory block and unsetenv() from * allowing it to be tracked. */ int putenv(char *string) { char *equals; int envNdx; int newEnvActive; size_t nameLen; /* Check for malformed argument. */ if (string == NULL || (equals = strchr(string, '=')) == NULL || (nameLen = equals - string) == 0) { errno = EINVAL; return (-1); } /* Initialize environment. */ if (__merge_environ() == -1 || (envVars == NULL && __build_env() == -1)) return (-1); /* Deactivate previous environment variable. */ envNdx = envVarsTotal - 1; newEnvActive = envActive; if (__findenv(string, nameLen, &envNdx, true) != NULL) { /* Reuse previous putenv slot. */ if (envVars[envNdx].putenv) { envVars[envNdx].name = string; return (__rebuild_environ(envActive)); } else { newEnvActive--; envVars[envNdx].active = false; } } /* Enlarge environment. */ envNdx = envVarsTotal; if (!__enlarge_env()) return (-1); /* Create environment entry. */ envVars[envNdx].name = string; envVars[envNdx].nameLen = -1; envVars[envNdx].value = NULL; envVars[envNdx].valueSize = -1; envVars[envNdx].putenv = true; envVars[envNdx].active = true; newEnvActive++; return (__rebuild_environ(newEnvActive)); } /* * Unset variable with the same name by flagging it as inactive. No variable is * ever freed. */ int unsetenv(const char *name) { int envNdx; size_t nameLen; int newEnvActive; /* Check for malformed name. */ if (name == NULL || (nameLen = __strleneq(name)) == 0) { errno = EINVAL; return (-1); } /* Initialize environment. */ if (__merge_environ() == -1 || (envVars == NULL && __build_env() == -1)) return (-1); /* Deactivate specified variable. */ /* Remove all occurrences. */ envNdx = envVarsTotal - 1; newEnvActive = envActive; while (__findenv(name, nameLen, &envNdx, true) != NULL) { envVars[envNdx].active = false; if (envVars[envNdx].putenv) __remove_putenv(envNdx); envNdx--; newEnvActive--; } if (newEnvActive != envActive) __rebuild_environ(newEnvActive); return (0); } /* * Unset all variable by flagging them as inactive. No variable is * ever freed. */ int clearenv(void) { int ndx; /* Initialize environment. */ if (__merge_environ() == -1 || (envVars == NULL && __build_env() == -1)) return (-1); /* Remove from the end to not shuffle memory too much. */ for (ndx = envVarsTotal - 1; ndx >= 0; ndx--) { envVars[ndx].active = false; if (envVars[ndx].putenv) __remove_putenv(ndx); } __rebuild_environ(0); return (0); } diff --git a/lib/libc/tests/secure/fortify_stdlib_test.c b/lib/libc/tests/secure/fortify_stdlib_test.c index ae021e8418f7..d0b1af78da86 100644 --- a/lib/libc/tests/secure/fortify_stdlib_test.c +++ b/lib/libc/tests/secure/fortify_stdlib_test.c @@ -1,463 +1,610 @@ /* @generated by `generate-fortify-tests.lua "stdlib"` */ #define _FORTIFY_SOURCE 2 #define TMPFILE_SIZE (1024 * 32) #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static FILE * __unused new_fp(size_t __len) { static char fpbuf[LINE_MAX]; FILE *fp; ATF_REQUIRE(__len <= sizeof(fpbuf)); memset(fpbuf, 'A', sizeof(fpbuf) - 1); fpbuf[sizeof(fpbuf) - 1] = '\0'; fp = fmemopen(fpbuf, sizeof(fpbuf), "rb"); ATF_REQUIRE(fp != NULL); return (fp); } /* * Create a new symlink to use for readlink(2) style tests, we'll just use a * random target name to have something interesting to look at. */ static const char * __unused new_symlink(size_t __len) { static const char linkname[] = "link"; char target[MAXNAMLEN]; int error; ATF_REQUIRE(__len <= sizeof(target)); arc4random_buf(target, sizeof(target)); error = unlink(linkname); ATF_REQUIRE(error == 0 || errno == ENOENT); error = symlink(target, linkname); ATF_REQUIRE(error == 0); return (linkname); } /* * For our purposes, first descriptor will be the reader; we'll send both * raw data and a control message over it so that the result can be used for * any of our recv*() tests. */ static void __unused new_socket(int sock[2]) { unsigned char ctrl[CMSG_SPACE(sizeof(int))] = { 0 }; static char sockbuf[256]; ssize_t rv; size_t total = 0; struct msghdr hdr = { 0 }; struct cmsghdr *cmsg; int error, fd; error = socketpair(AF_UNIX, SOCK_STREAM, 0, sock); ATF_REQUIRE(error == 0); while (total != sizeof(sockbuf)) { rv = send(sock[1], &sockbuf[total], sizeof(sockbuf) - total, 0); ATF_REQUIRE_MSG(rv > 0, "expected bytes sent, got %zd with %zu left (size %zu, total %zu)", rv, sizeof(sockbuf) - total, sizeof(sockbuf), total); ATF_REQUIRE_MSG(total + (size_t)rv <= sizeof(sockbuf), "%zd exceeds total %zu", rv, sizeof(sockbuf)); total += rv; } hdr.msg_control = ctrl; hdr.msg_controllen = sizeof(ctrl); cmsg = CMSG_FIRSTHDR(&hdr); cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_RIGHTS; cmsg->cmsg_len = CMSG_LEN(sizeof(fd)); fd = STDIN_FILENO; memcpy(CMSG_DATA(cmsg), &fd, sizeof(fd)); error = sendmsg(sock[1], &hdr, 0); ATF_REQUIRE(error != -1); } /* * Constructs a tmpfile that we can use for testing read(2) and friends. */ static int __unused new_tmpfile(void) { char buf[1024]; ssize_t rv; size_t written; int fd; fd = open("tmpfile", O_RDWR | O_CREAT | O_TRUNC, 0644); ATF_REQUIRE(fd >= 0); written = 0; while (written < TMPFILE_SIZE) { rv = write(fd, buf, sizeof(buf)); ATF_REQUIRE(rv > 0); written += rv; } ATF_REQUIRE_EQ(0, lseek(fd, 0, SEEK_SET)); return (fd); } static void disable_coredumps(void) { struct rlimit rl = { 0 }; if (setrlimit(RLIMIT_CORE, &rl) == -1) _exit(EX_OSERR); } /* * Replaces stdin with a file that we can actually read from, for tests where * we want a FILE * or fd that we can get data from. */ static void __unused replace_stdin(void) { int fd; fd = new_tmpfile(); (void)dup2(fd, STDIN_FILENO); if (fd != STDIN_FILENO) close(fd); } ATF_TC(arc4random_buf_before_end); ATF_TC_HEAD(arc4random_buf_before_end, tc) { } ATF_TC_BODY(arc4random_buf_before_end, tc) { #define BUF &__stack.__buf struct { uint8_t padding_l; unsigned char __buf[42]; uint8_t padding_r; } __stack; const size_t __bufsz __unused = sizeof(__stack.__buf); const size_t __len = 42 - 1; const size_t __idx __unused = __len - 1; arc4random_buf(__stack.__buf, __len); #undef BUF } ATF_TC(arc4random_buf_end); ATF_TC_HEAD(arc4random_buf_end, tc) { } ATF_TC_BODY(arc4random_buf_end, tc) { #define BUF &__stack.__buf struct { uint8_t padding_l; unsigned char __buf[42]; uint8_t padding_r; } __stack; const size_t __bufsz __unused = sizeof(__stack.__buf); const size_t __len = 42; const size_t __idx __unused = __len - 1; arc4random_buf(__stack.__buf, __len); #undef BUF } ATF_TC(arc4random_buf_heap_before_end); ATF_TC_HEAD(arc4random_buf_heap_before_end, tc) { } ATF_TC_BODY(arc4random_buf_heap_before_end, tc) { #define BUF __stack.__buf struct { uint8_t padding_l; unsigned char * __buf; uint8_t padding_r; } __stack; const size_t __bufsz __unused = sizeof(*__stack.__buf) * (42); const size_t __len = 42 - 1; const size_t __idx __unused = __len - 1; __stack.__buf = malloc(__bufsz); arc4random_buf(__stack.__buf, __len); #undef BUF } ATF_TC(arc4random_buf_heap_end); ATF_TC_HEAD(arc4random_buf_heap_end, tc) { } ATF_TC_BODY(arc4random_buf_heap_end, tc) { #define BUF __stack.__buf struct { uint8_t padding_l; unsigned char * __buf; uint8_t padding_r; } __stack; const size_t __bufsz __unused = sizeof(*__stack.__buf) * (42); const size_t __len = 42; const size_t __idx __unused = __len - 1; __stack.__buf = malloc(__bufsz); arc4random_buf(__stack.__buf, __len); #undef BUF } ATF_TC(arc4random_buf_heap_after_end); ATF_TC_HEAD(arc4random_buf_heap_after_end, tc) { } ATF_TC_BODY(arc4random_buf_heap_after_end, tc) { #define BUF __stack.__buf struct { uint8_t padding_l; unsigned char * __buf; uint8_t padding_r; } __stack; const size_t __bufsz __unused = sizeof(*__stack.__buf) * (42); const size_t __len = 42 + 1; const size_t __idx __unused = __len - 1; pid_t __child; int __status; __child = fork(); ATF_REQUIRE(__child >= 0); if (__child > 0) goto monitor; /* Child */ disable_coredumps(); __stack.__buf = malloc(__bufsz); arc4random_buf(__stack.__buf, __len); _exit(EX_SOFTWARE); /* Should have aborted. */ monitor: while (waitpid(__child, &__status, 0) != __child) { ATF_REQUIRE_EQ(EINTR, errno); } if (!WIFSIGNALED(__status)) { switch (WEXITSTATUS(__status)) { case EX_SOFTWARE: atf_tc_fail("FORTIFY_SOURCE failed to abort"); break; case EX_OSERR: atf_tc_fail("setrlimit(2) failed"); break; default: atf_tc_fail("child exited with status %d", WEXITSTATUS(__status)); } } else { ATF_REQUIRE_EQ(SIGABRT, WTERMSIG(__status)); } #undef BUF } +ATF_TC(getenv_r_before_end); +ATF_TC_HEAD(getenv_r_before_end, tc) +{ +} +ATF_TC_BODY(getenv_r_before_end, tc) +{ +#define BUF &__stack.__buf + struct { + uint8_t padding_l; + unsigned char __buf[42]; + uint8_t padding_r; + } __stack; + const size_t __bufsz __unused = sizeof(__stack.__buf); + const size_t __len = 42 - 1; + const size_t __idx __unused = __len - 1; + + getenv_r("PATH", __stack.__buf, __len); +#undef BUF + +} + +ATF_TC(getenv_r_end); +ATF_TC_HEAD(getenv_r_end, tc) +{ +} +ATF_TC_BODY(getenv_r_end, tc) +{ +#define BUF &__stack.__buf + struct { + uint8_t padding_l; + unsigned char __buf[42]; + uint8_t padding_r; + } __stack; + const size_t __bufsz __unused = sizeof(__stack.__buf); + const size_t __len = 42; + const size_t __idx __unused = __len - 1; + + getenv_r("PATH", __stack.__buf, __len); +#undef BUF + +} + +ATF_TC(getenv_r_heap_before_end); +ATF_TC_HEAD(getenv_r_heap_before_end, tc) +{ +} +ATF_TC_BODY(getenv_r_heap_before_end, tc) +{ +#define BUF __stack.__buf + struct { + uint8_t padding_l; + unsigned char * __buf; + uint8_t padding_r; + } __stack; + const size_t __bufsz __unused = sizeof(*__stack.__buf) * (42); + const size_t __len = 42 - 1; + const size_t __idx __unused = __len - 1; + + __stack.__buf = malloc(__bufsz); + + getenv_r("PATH", __stack.__buf, __len); +#undef BUF + +} + +ATF_TC(getenv_r_heap_end); +ATF_TC_HEAD(getenv_r_heap_end, tc) +{ +} +ATF_TC_BODY(getenv_r_heap_end, tc) +{ +#define BUF __stack.__buf + struct { + uint8_t padding_l; + unsigned char * __buf; + uint8_t padding_r; + } __stack; + const size_t __bufsz __unused = sizeof(*__stack.__buf) * (42); + const size_t __len = 42; + const size_t __idx __unused = __len - 1; + + __stack.__buf = malloc(__bufsz); + + getenv_r("PATH", __stack.__buf, __len); +#undef BUF + +} + +ATF_TC(getenv_r_heap_after_end); +ATF_TC_HEAD(getenv_r_heap_after_end, tc) +{ +} +ATF_TC_BODY(getenv_r_heap_after_end, tc) +{ +#define BUF __stack.__buf + struct { + uint8_t padding_l; + unsigned char * __buf; + uint8_t padding_r; + } __stack; + const size_t __bufsz __unused = sizeof(*__stack.__buf) * (42); + const size_t __len = 42 + 1; + const size_t __idx __unused = __len - 1; + pid_t __child; + int __status; + + __child = fork(); + ATF_REQUIRE(__child >= 0); + if (__child > 0) + goto monitor; + + /* Child */ + disable_coredumps(); + __stack.__buf = malloc(__bufsz); + + getenv_r("PATH", __stack.__buf, __len); + _exit(EX_SOFTWARE); /* Should have aborted. */ + +monitor: + while (waitpid(__child, &__status, 0) != __child) { + ATF_REQUIRE_EQ(EINTR, errno); + } + + if (!WIFSIGNALED(__status)) { + switch (WEXITSTATUS(__status)) { + case EX_SOFTWARE: + atf_tc_fail("FORTIFY_SOURCE failed to abort"); + break; + case EX_OSERR: + atf_tc_fail("setrlimit(2) failed"); + break; + default: + atf_tc_fail("child exited with status %d", + WEXITSTATUS(__status)); + } + } else { + ATF_REQUIRE_EQ(SIGABRT, WTERMSIG(__status)); + } +#undef BUF + +} + ATF_TC(realpath_before_end); ATF_TC_HEAD(realpath_before_end, tc) { } ATF_TC_BODY(realpath_before_end, tc) { #define BUF &__stack.__buf struct { uint8_t padding_l; unsigned char __buf[PATH_MAX + 1]; uint8_t padding_r; } __stack; const size_t __bufsz __unused = sizeof(__stack.__buf); const size_t __len = PATH_MAX + 1; const size_t __idx __unused = __len - 1; realpath(".", __stack.__buf); #undef BUF } ATF_TC(realpath_end); ATF_TC_HEAD(realpath_end, tc) { } ATF_TC_BODY(realpath_end, tc) { #define BUF &__stack.__buf struct { uint8_t padding_l; unsigned char __buf[PATH_MAX]; uint8_t padding_r; } __stack; const size_t __bufsz __unused = sizeof(__stack.__buf); const size_t __len = PATH_MAX; const size_t __idx __unused = __len - 1; realpath(".", __stack.__buf); #undef BUF } ATF_TC(realpath_heap_before_end); ATF_TC_HEAD(realpath_heap_before_end, tc) { } ATF_TC_BODY(realpath_heap_before_end, tc) { #define BUF __stack.__buf struct { uint8_t padding_l; unsigned char * __buf; uint8_t padding_r; } __stack; const size_t __bufsz __unused = sizeof(*__stack.__buf) * (PATH_MAX + 1); const size_t __len = PATH_MAX + 1; const size_t __idx __unused = __len - 1; __stack.__buf = malloc(__bufsz); realpath(".", __stack.__buf); #undef BUF } ATF_TC(realpath_heap_end); ATF_TC_HEAD(realpath_heap_end, tc) { } ATF_TC_BODY(realpath_heap_end, tc) { #define BUF __stack.__buf struct { uint8_t padding_l; unsigned char * __buf; uint8_t padding_r; } __stack; const size_t __bufsz __unused = sizeof(*__stack.__buf) * (PATH_MAX); const size_t __len = PATH_MAX; const size_t __idx __unused = __len - 1; __stack.__buf = malloc(__bufsz); realpath(".", __stack.__buf); #undef BUF } ATF_TC(realpath_heap_after_end); ATF_TC_HEAD(realpath_heap_after_end, tc) { } ATF_TC_BODY(realpath_heap_after_end, tc) { #define BUF __stack.__buf struct { uint8_t padding_l; unsigned char * __buf; uint8_t padding_r; } __stack; const size_t __bufsz __unused = sizeof(*__stack.__buf) * (PATH_MAX - 1); const size_t __len = PATH_MAX - 1; const size_t __idx __unused = __len - 1; pid_t __child; int __status; __child = fork(); ATF_REQUIRE(__child >= 0); if (__child > 0) goto monitor; /* Child */ disable_coredumps(); __stack.__buf = malloc(__bufsz); realpath(".", __stack.__buf); _exit(EX_SOFTWARE); /* Should have aborted. */ monitor: while (waitpid(__child, &__status, 0) != __child) { ATF_REQUIRE_EQ(EINTR, errno); } if (!WIFSIGNALED(__status)) { switch (WEXITSTATUS(__status)) { case EX_SOFTWARE: atf_tc_fail("FORTIFY_SOURCE failed to abort"); break; case EX_OSERR: atf_tc_fail("setrlimit(2) failed"); break; default: atf_tc_fail("child exited with status %d", WEXITSTATUS(__status)); } } else { ATF_REQUIRE_EQ(SIGABRT, WTERMSIG(__status)); } #undef BUF } ATF_TP_ADD_TCS(tp) { ATF_TP_ADD_TC(tp, arc4random_buf_before_end); ATF_TP_ADD_TC(tp, arc4random_buf_end); ATF_TP_ADD_TC(tp, arc4random_buf_heap_before_end); ATF_TP_ADD_TC(tp, arc4random_buf_heap_end); ATF_TP_ADD_TC(tp, arc4random_buf_heap_after_end); + ATF_TP_ADD_TC(tp, getenv_r_before_end); + ATF_TP_ADD_TC(tp, getenv_r_end); + ATF_TP_ADD_TC(tp, getenv_r_heap_before_end); + ATF_TP_ADD_TC(tp, getenv_r_heap_end); + ATF_TP_ADD_TC(tp, getenv_r_heap_after_end); ATF_TP_ADD_TC(tp, realpath_before_end); ATF_TP_ADD_TC(tp, realpath_end); ATF_TP_ADD_TC(tp, realpath_heap_before_end); ATF_TP_ADD_TC(tp, realpath_heap_end); ATF_TP_ADD_TC(tp, realpath_heap_after_end); return (atf_no_error()); } diff --git a/lib/libc/tests/secure/generate-fortify-tests.lua b/lib/libc/tests/secure/generate-fortify-tests.lua index 36ff01af7a17..6c2a80b20609 100755 --- a/lib/libc/tests/secure/generate-fortify-tests.lua +++ b/lib/libc/tests/secure/generate-fortify-tests.lua @@ -1,1560 +1,1569 @@ #!/usr/libexec/flua -- -- SPDX-License-Identifier: BSD-2-Clause -- -- Copyright (c) 2024, Klara, Inc. -- -- Redistribution and use in source and binary forms, with or without -- modification, are permitted provided that the following conditions -- are met: -- 1. Redistributions of source code must retain the above copyright -- notice, this list of conditions and the following disclaimer. -- 2. Redistributions in binary form must reproduce the above copyright -- notice, this list of conditions and the following disclaimer in the -- documentation and/or other materials provided with the distribution. -- -- THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND -- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -- ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE -- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS -- OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -- HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -- LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY -- OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF -- SUCH DAMAGE. -- -- THEORY OF OPERATION -- -- generate-fortify-tests.lua is intended to test fortified functions as found -- mostly in the various headers in /usr/include/ssp. Each fortified function -- gets three basic tests: -- -- 1. Write just before the end of the buffer, -- 2. Write right at the end of the buffer, -- 3. Write just after the end of the buffer. -- -- Each test is actually generated twice: once with a buffer on the stack, and -- again with a buffer on the heap, to confirm that __builtin_object_size(3) can -- deduce the buffer size in both scenarios. The tests work by setting up the -- stack with our buffer (and some padding on either side to avoid tripping any -- other stack or memory protection), doing any initialization as described by -- the test definition, then calling the fortified function with the buffer as -- outlined by the test definition. -- -- For the 'before' and 'at' the end tests, we're ensuring that valid writes -- that are on the verge of being invalid aren't accidentally being detected as -- invalid. -- -- The 'after' test is the one that actually tests the functional benefit of -- _FORTIFY_SOURCE by violating a boundary that should trigger an abort. As -- such, this test differs more from the other two in that it has to fork() off -- the fortified function call so that we can monitor for a SIGABRT and -- pass/fail the test at function end appropriately. -- Some tests, like the FD_*() macros, may define these differently. For -- instance, for fd sets we're varying the index we pass and not using arbitrary -- buffers. Other tests that don't use the length in any way may physically -- vary the buffer size for each test case when we'd typically vary the length -- we're requesting a write for. local includes = { "sys/param.h", "sys/jail.h", "sys/random.h", "sys/resource.h", "sys/select.h", "sys/socket.h", "sys/time.h", "sys/uio.h", "sys/wait.h", "dirent.h", "errno.h", "fcntl.h", "limits.h", "poll.h", "signal.h", "stdio.h", "stdlib.h", "string.h", "strings.h", "sysexits.h", "unistd.h", "wchar.h", "atf-c.h", } local tests_added = {} -- Configuration for tests that want the host/domainname local hostname = "host.example.com" local domainname = "example.com" -- Some of these will need to be excluded because clang sees the wrong size when -- an array is embedded inside a struct, we'll get something that looks more -- like __builtin_object_size(ptr, 0) than it does the correct -- __builtin_object_size(ptr, 1) (i.e., includes the padding after). This is -- almost certainly a bug in llvm. local function excludes_stack_overflow(disposition, is_heap) return (not is_heap) and disposition > 0 end local poll_init = [[ for (size_t i = 0; i < howmany(__bufsz, sizeof(struct pollfd)); i++) { __stack.__buf[i].fd = -1; } ]] local printf_stackvars = "\tchar srcvar[__len + 10];\n" local printf_init = [[ memset(srcvar, 'A', sizeof(srcvar) - 1); srcvar[sizeof(srcvar) - 1] = '\0'; ]] local readv_stackvars = "\tstruct iovec iov[1];\n" local readv_init = [[ iov[0].iov_base = __stack.__buf; iov[0].iov_len = __len; replace_stdin(); ]] local socket_stackvars = "\tint sock[2] = { -1, -1 };\n" local recvfrom_sockaddr_stackvars = socket_stackvars .. [[ char data[16]; socklen_t socklen; ]] local recvmsg_stackvars = socket_stackvars .. "\tstruct msghdr msg;\n" local socket_init = [[ new_socket(sock); ]] local socket_socklen_init = socket_init .. [[ socklen = __len; ]] local stdio_init = [[ replace_stdin(); ]] local string_stackvars = "\tchar src[__len];\n" local string_init = [[ memset(__stack.__buf, 0, __len); memset(src, 'A', __len - 1); src[__len - 1] = '\0'; ]] local wstring_stackvars = "\twchar_t src[__len];\n" local wstring_init = [[ wmemset(__stack.__buf, 0, __len); wmemset(src, 'A', __len - 1); src[__len - 1] = '\0'; ]] -- Each test entry describes how to test a given function. We need to know how -- to construct the buffer, we need to know the argument set we're dealing with, -- and we need to know what we're passing to each argument. We could be passing -- fixed values, or we could be passing the __buf under test. -- -- definition: -- func: name of the function under test to call -- bufsize: size of buffer to generate, defaults to 42 -- buftype: type of buffer to generate, defaults to unsigned char[] -- arguments: __buf, __len, or the name of a variable placed on the stack -- exclude: a function(disposition, is_heap) that returns true if this combo -- should be excluded. -- stackvars: extra variables to be placed on the stack, should be a string -- optionally formatted with tabs and newlines -- init: extra code to inject just before the function call for initialization -- of the buffer or any of the above-added stackvars; also a string -- uses_len: bool-ish, necessary if arguments doesn't include either __idx or -- or __len so that the test generator doesn't try to vary the size of the -- buffer instead of just manipulating __idx/__len to try and induce an -- overflow. -- -- Most tests will just use the default bufsize/buftype, but under some -- circumstances it's useful to use a different type (e.g., for alignment -- requirements). local all_tests = { random = { -- { func = "getrandom", arguments = { "__buf", "__len", "0", }, exclude = excludes_stack_overflow, }, }, select = { -- { func = "FD_SET", bufsize = "FD_SETSIZE", buftype = "fd_set", arguments = { "__idx", "__buf", }, }, { func = "FD_CLR", bufsize = "FD_SETSIZE", buftype = "fd_set", arguments = { "__idx", "__buf", }, }, { func = "FD_ISSET", bufsize = "FD_SETSIZE", buftype = "fd_set", arguments = { "__idx", "__buf", }, }, }, socket = { -- { func = "getpeername", buftype = "struct sockaddr", bufsize = "sizeof(struct sockaddr)", arguments = { "sock[0]", "__buf", "&socklen", }, exclude = excludes_stack_overflow, stackvars = socket_stackvars .. "\tsocklen_t socklen;", init = socket_socklen_init, uses_len = true, }, { func = "getsockname", buftype = "struct sockaddr", bufsize = "sizeof(struct sockaddr)", arguments = { "sock[0]", "__buf", "&socklen", }, exclude = excludes_stack_overflow, stackvars = socket_stackvars .. "\tsocklen_t socklen;", init = socket_socklen_init, uses_len = true, }, { func = "recv", arguments = { "sock[0]", "__buf", "__len", "0", }, exclude = excludes_stack_overflow, stackvars = socket_stackvars, init = socket_init, }, { func = "recvfrom", arguments = { "sock[0]", "__buf", "__len", "0", "NULL", "NULL", }, exclude = excludes_stack_overflow, stackvars = socket_stackvars, init = socket_init, }, { func = "recvfrom", variant = "sockaddr", buftype = "struct sockaddr", bufsize = "sizeof(struct sockaddr)", arguments = { "sock[0]", "data", "sizeof(data)", "0", "__buf", "&socklen", }, exclude = excludes_stack_overflow, stackvars = recvfrom_sockaddr_stackvars, init = socket_socklen_init, uses_len = true, }, { func = "recvmsg", variant = "msg_name", buftype = "struct sockaddr", bufsize = "sizeof(struct sockaddr)", arguments = { "sock[0]", "&msg", "0", }, exclude = excludes_stack_overflow, stackvars = recvmsg_stackvars, init = [[ memset(&msg, 0, sizeof(msg)); msg.msg_name = BUF; msg.msg_namelen = __len; ]], uses_len = true, }, { func = "recvmsg", variant = "msg_iov", arguments = { "sock[0]", "&msg", "0", }, exclude = excludes_stack_overflow, stackvars = recvmsg_stackvars .. "\tstruct iovec iov[2];\n", init = [[ memset(&msg, 0, sizeof(msg)); memset(&iov[0], 0, sizeof(iov)); /* * We position the buffer second just so that we can confirm that the * fortification bits are traversing the iovec correctly. */ iov[1].iov_base = BUF; iov[1].iov_len = __len; msg.msg_iov = &iov[0]; msg.msg_iovlen = nitems(iov); ]], uses_len = true, }, { func = "recvmsg", variant = "msg_control", bufsize = "CMSG_SPACE(sizeof(int))", arguments = { "sock[0]", "&msg", "0", }, exclude = excludes_stack_overflow, stackvars = recvmsg_stackvars, init = [[ memset(&msg, 0, sizeof(msg)); msg.msg_control = BUF; msg.msg_controllen = __len; ]], uses_len = true, }, { func = "recvmmsg", variant = "msgvec", buftype = "struct mmsghdr[]", bufsize = "2", arguments = { "sock[0]", "__buf", "__len", "0", "NULL", }, stackvars = socket_stackvars, }, { -- We'll assume that recvmsg is covering msghdr -- validation thoroughly enough, we'll just try tossing -- an error in the second element of a msgvec to try and -- make sure that each one is being validated. func = "recvmmsg", variant = "msghdr", arguments = { "sock[0]", "&msgvec[0]", "nitems(msgvec)", "0", "NULL", }, exclude = excludes_stack_overflow, stackvars = socket_stackvars .. "\tstruct mmsghdr msgvec[2];\n", init = [[ memset(&msgvec[0], 0, sizeof(msgvec)); /* * Same as above, make sure fortification isn't ignoring n > 1 elements * of the msgvec. */ msgvec[1].msg_hdr.msg_control = BUF; msgvec[1].msg_hdr.msg_controllen = __len; ]], uses_len = true, }, }, uio = { -- { func = "readv", buftype = "struct iovec[]", bufsize = 2, arguments = { "STDIN_FILENO", "__buf", "__len", }, }, { func = "readv", variant = "iov", arguments = { "STDIN_FILENO", "iov", "nitems(iov)", }, exclude = excludes_stack_overflow, stackvars = readv_stackvars, init = readv_init, uses_len = true, }, { func = "preadv", buftype = "struct iovec[]", bufsize = 2, arguments = { "STDIN_FILENO", "__buf", "__len", "0", }, }, { func = "preadv", variant = "iov", arguments = { "STDIN_FILENO", "iov", "nitems(iov)", "0", }, exclude = excludes_stack_overflow, stackvars = readv_stackvars, init = readv_init, uses_len = true, }, }, poll = { -- { func = "poll", bufsize = "4", buftype = "struct pollfd[]", arguments = { "__buf", "__len", "0", }, init = poll_init, }, { func = "ppoll", bufsize = "4", buftype = "struct pollfd[]", arguments = { "__buf", "__len", "&tv", "NULL", }, stackvars = "\tstruct timespec tv = { 0 };\n", init = poll_init, }, }, stdio = { -- { func = "ctermid", bufsize = "L_ctermid", arguments = { "__buf", }, exclude = excludes_stack_overflow, }, { func = "ctermid_r", bufsize = "L_ctermid", arguments = { "__buf", }, exclude = excludes_stack_overflow, }, { func = "fread", arguments = { "__buf", "__len", "1", "stdin", }, exclude = excludes_stack_overflow, init = stdio_init, }, { func = "fread_unlocked", arguments = { "__buf", "__len", "1", "stdin", }, exclude = excludes_stack_overflow, init = stdio_init, }, { func = "gets_s", arguments = { "__buf", "__len", }, exclude = excludes_stack_overflow, init = stdio_init, }, { func = "sprintf", arguments = { "__buf", "\"%.*s\"", "(int)__len - 1", -- - 1 for NUL terminator "srcvar", }, exclude = excludes_stack_overflow, stackvars = printf_stackvars, init = printf_init, }, { func = "snprintf", arguments = { "__buf", "__len", "\"%.*s\"", "(int)__len - 1", -- - 1 for NUL terminator "srcvar", }, exclude = excludes_stack_overflow, stackvars = printf_stackvars, init = printf_init, }, { func = "tmpnam", bufsize = "L_tmpnam", arguments = { "__buf", }, exclude = excludes_stack_overflow, }, { func = "fgets", arguments = { "__buf", "__len", "fp", }, exclude = excludes_stack_overflow, stackvars = "\tFILE *fp;\n", init = [[ fp = new_fp(__len); ]], }, }, stdlib = { -- { func = "arc4random_buf", arguments = { "__buf", "__len", }, exclude = excludes_stack_overflow, }, + { + func = "getenv_r", + arguments = { + "\"PATH\"", + "__buf", + "__len", + }, + exclude = excludes_stack_overflow, + }, { func = "realpath", bufsize = "PATH_MAX", arguments = { "\".\"", "__buf", }, exclude = excludes_stack_overflow, }, }, string = { -- { func = "memcpy", arguments = { "__buf", "src", "__len", }, exclude = excludes_stack_overflow, stackvars = "\tchar src[__len + 10];\n", }, { func = "mempcpy", arguments = { "__buf", "src", "__len", }, exclude = excludes_stack_overflow, stackvars = "\tchar src[__len + 10];\n", }, { func = "memmove", arguments = { "__buf", "src", "__len", }, exclude = excludes_stack_overflow, stackvars = "\tchar src[__len + 10];\n", }, { func = "memset", arguments = { "__buf", "0", "__len", }, exclude = excludes_stack_overflow, }, { func = "memset_explicit", arguments = { "__buf", "0", "__len", }, exclude = excludes_stack_overflow, }, { func = "stpcpy", arguments = { "__buf", "src", }, exclude = excludes_stack_overflow, stackvars = string_stackvars, init = string_init, uses_len = true, }, { func = "stpncpy", arguments = { "__buf", "src", "__len", }, exclude = excludes_stack_overflow, stackvars = string_stackvars, init = string_init, }, { func = "strcat", arguments = { "__buf", "src", }, exclude = excludes_stack_overflow, stackvars = string_stackvars, init = string_init, uses_len = true, }, { func = "strlcat", arguments = { "__buf", "src", "__len", }, exclude = excludes_stack_overflow, stackvars = string_stackvars, init = string_init, }, { func = "strncat", arguments = { "__buf", "src", "__len", }, exclude = excludes_stack_overflow, stackvars = string_stackvars, init = string_init, }, { func = "strcpy", arguments = { "__buf", "src", }, exclude = excludes_stack_overflow, stackvars = string_stackvars, init = string_init, uses_len = true, }, { func = "strlcpy", arguments = { "__buf", "src", "__len", }, exclude = excludes_stack_overflow, stackvars = string_stackvars, init = string_init, }, { func = "strncpy", arguments = { "__buf", "src", "__len", }, exclude = excludes_stack_overflow, stackvars = string_stackvars, init = string_init, }, }, strings = { -- { func = "bcopy", arguments = { "src", "__buf", "__len", }, exclude = excludes_stack_overflow, stackvars = "\tchar src[__len + 10];\n", }, { func = "bzero", arguments = { "__buf", "__len", }, exclude = excludes_stack_overflow, }, { func = "explicit_bzero", arguments = { "__buf", "__len", }, exclude = excludes_stack_overflow, }, }, unistd = { -- { func = "getcwd", bufsize = "8", arguments = { "__buf", "__len", }, exclude = excludes_stack_overflow, }, { func = "getgrouplist", bufsize = "4", buftype = "gid_t[]", arguments = { "\"root\"", "0", "__buf", "&intlen", }, exclude = excludes_stack_overflow, stackvars = "\tint intlen = (int)__len;\n", uses_len = true, }, { func = "getgroups", bufsize = "4", buftype = "gid_t[]", arguments = { "__len", "__buf", }, exclude = excludes_stack_overflow, }, { func = "getloginclass", arguments = { "__buf", "__len", }, exclude = excludes_stack_overflow, }, { func = "pread", bufsize = "41", arguments = { "fd", "__buf", "__len", "0", }, exclude = excludes_stack_overflow, stackvars = "\tint fd;\n", init = [[ fd = new_tmpfile(); /* Cannot fail */ ]], }, { func = "read", bufsize = "41", arguments = { "fd", "__buf", "__len", }, exclude = excludes_stack_overflow, stackvars = "\tint fd;\n", init = [[ fd = new_tmpfile(); /* Cannot fail */ ]], }, { func = "readlink", arguments = { "path", "__buf", "__len", }, exclude = excludes_stack_overflow, stackvars = "\tconst char *path;\n", init = [[ path = new_symlink(__len); /* Cannot fail */ ]], }, { func = "readlinkat", arguments = { "AT_FDCWD", "path", "__buf", "__len", }, exclude = excludes_stack_overflow, stackvars = "\tconst char *path;\n", init = [[ path = new_symlink(__len); /* Cannot fail */ ]], }, { func = "getdomainname", bufsize = #domainname + 1, arguments = { "__buf", "__len", }, need_root = true, exclude = excludes_stack_overflow, early_init = " dhost_jail();", }, { func = "getentropy", arguments = { "__buf", "__len", }, exclude = excludes_stack_overflow, }, { func = "gethostname", bufsize = #hostname + 1, arguments = { "__buf", "__len", }, need_root = true, exclude = excludes_stack_overflow, early_init = " dhost_jail();", }, { func = "getlogin_r", bufsize = "MAXLOGNAME + 1", arguments = { "__buf", "__len", }, exclude = excludes_stack_overflow, }, { func = "ttyname_r", arguments = { "fd", "__buf", "__len", }, exclude = excludes_stack_overflow, stackvars = "\tint fd;\n", early_init = [[ fd = STDIN_FILENO; if (!isatty(fd)) atf_tc_skip("stdin is not an fd"); ]] }, }, wchar = { -- { func = "wmemcpy", buftype = "wchar_t[]", arguments = { "__buf", "src", "__len", }, exclude = excludes_stack_overflow, stackvars = "\twchar_t src[__len + 10];\n", }, { func = "wmempcpy", buftype = "wchar_t[]", arguments = { "__buf", "src", "__len", }, exclude = excludes_stack_overflow, stackvars = "\twchar_t src[__len + 10];\n", }, { func = "wmemmove", buftype = "wchar_t[]", arguments = { "__buf", "src", "__len", }, exclude = excludes_stack_overflow, stackvars = "\twchar_t src[__len + 10];\n", }, { func = "wmemset", buftype = "wchar_t[]", arguments = { "__buf", "L'0'", "__len", }, exclude = excludes_stack_overflow, }, { func = "wcpcpy", buftype = "wchar_t[]", arguments = { "__buf", "src", }, exclude = excludes_stack_overflow, stackvars = wstring_stackvars, init = wstring_init, uses_len = true, }, { func = "wcpncpy", buftype = "wchar_t[]", arguments = { "__buf", "src", "__len", }, exclude = excludes_stack_overflow, stackvars = wstring_stackvars, init = wstring_init, }, { func = "wcscat", buftype = "wchar_t[]", arguments = { "__buf", "src", }, exclude = excludes_stack_overflow, stackvars = wstring_stackvars, init = wstring_init, uses_len = true, }, { func = "wcslcat", buftype = "wchar_t[]", arguments = { "__buf", "src", "__len", }, exclude = excludes_stack_overflow, stackvars = wstring_stackvars, init = wstring_init, }, { func = "wcsncat", buftype = "wchar_t[]", arguments = { "__buf", "src", "__len", }, exclude = excludes_stack_overflow, stackvars = wstring_stackvars, init = wstring_init, }, { func = "wcscpy", buftype = "wchar_t[]", arguments = { "__buf", "src", }, exclude = excludes_stack_overflow, stackvars = wstring_stackvars, init = wstring_init, uses_len = true, }, { func = "wcslcpy", buftype = "wchar_t[]", arguments = { "__buf", "src", "__len", }, exclude = excludes_stack_overflow, stackvars = wstring_stackvars, init = wstring_init, }, { func = "wcsncpy", buftype = "wchar_t[]", arguments = { "__buf", "src", "__len", }, exclude = excludes_stack_overflow, stackvars = wstring_stackvars, init = wstring_init, }, }, } local function write_test_boilerplate(fh, name, body, def) fh:write("ATF_TC(" .. name .. ");\n") fh:write("ATF_TC_HEAD(" .. name .. ", tc)\n") fh:write("{\n") if def.need_root then fh:write(" atf_tc_set_md_var(tc, \"require.user\", \"root\");\n") end fh:write("}\n") fh:write("ATF_TC_BODY(" .. name .. ", tc)\n") fh:write("{\n" .. body .. "\n}\n\n") return name end local function generate_test_name(func, variant, disposition, heap) local basename = func if variant then basename = basename .. "_" .. variant end if heap then basename = basename .. "_heap" end if disposition < 0 then return basename .. "_before_end" elseif disposition == 0 then return basename .. "_end" else return basename .. "_after_end" end end local function array_type(buftype) if not buftype:match("%[%]") then return nil end return buftype:gsub("%[%]", "") end local function configurable(def, idx) local cfgitem = def[idx] if not cfgitem then return nil end if type(cfgitem) == "function" then return cfgitem() end return cfgitem end local function generate_stackframe(buftype, bufsize, disposition, heap, def) local function len_offset(inverted) -- Tests that don't use __len in their arguments may use an -- inverted sense because we can't just specify a length that -- would induce an access just after the end. Instead, we have -- to manipulate the buffer size to be too short so that the -- function under test would write one too many. if disposition < 0 then return ((inverted and " + ") or " - ") .. "1" elseif disposition == 0 then return "" else return ((inverted and " - ") or " + ") .. "1" end end local function test_uses_len() if def.uses_len then return true end for _, arg in ipairs(def.arguments) do if arg:match("__len") or arg:match("__idx") then return true end end return false end -- This is perhaps a little convoluted, but we toss the buffer into a -- struct on the stack to guarantee that we have at least one valid -- byte on either side of the buffer -- a measure to make sure that -- we're tripping _FORTIFY_SOURCE specifically in the buffer + 1 case, -- rather than some other stack or memory protection. local vars = "\tstruct {\n" vars = vars .. "\t\tuint8_t padding_l;\n" local uses_len = test_uses_len() local bufsize_offset = len_offset(not uses_len) local buftype_elem = array_type(buftype) local size_expr = bufsize if not uses_len then -- If the length isn't in use, we have to vary the buffer size -- since the fortified function likely has some internal size -- constraint that it's supposed to be checking. size_expr = size_expr .. bufsize_offset end if not heap and buftype_elem then -- Array type: size goes after identifier vars = vars .. "\t\t" .. buftype_elem .. " __buf[" .. size_expr .. "];\n" else local basic_type = buftype_elem or buftype -- Heap tests obviously just put a pointer on the stack that -- points to our new allocation, but we leave it in the padded -- struct just to simplify our generator. if heap then basic_type = basic_type .. " *" end vars = vars .. "\t\t" .. basic_type .. " __buf;\n" end -- padding_r is our just-past-the-end padding that we use to make sure -- that there's a valid portion after the buffer that isn't being -- included in our function calls. If we didn't have it, then we'd have -- a hard time feeling confident that an abort on the just-after tests -- isn't maybe from some other memory or stack protection. vars = vars .. "\t\tuint8_t padding_r;\n" vars = vars .. "\t} __stack;\n" -- Not all tests will use __bufsz, but some do for, e.g., clearing -- memory.. vars = vars .. "\tconst size_t __bufsz __unused = " if heap then local scalar = 1 if buftype_elem then scalar = size_expr end vars = vars .. "sizeof(*__stack.__buf) * (" .. scalar .. ");\n" else vars = vars .. "sizeof(__stack.__buf);\n" end vars = vars .. "\tconst size_t __len = " .. bufsize .. bufsize_offset .. ";\n" vars = vars .. "\tconst size_t __idx __unused = __len - 1;\n" -- For overflow testing, we need to fork() because we're expecting the -- test to ultimately abort()/_exit(). Then we can collect the exit -- status and report appropriately. if disposition > 0 then vars = vars .. "\tpid_t __child;\n" vars = vars .. "\tint __status;\n" end -- Any other stackvars defined by the test get placed after everything -- else. vars = vars .. (configurable(def, "stackvars") or "") return vars end local function write_test(fh, func, disposition, heap, def) local testname = generate_test_name(func, def.variant, disposition, heap) local buftype = def.buftype or "unsigned char[]" local bufsize = def.bufsize or 42 local body = "" if def.exclude and def.exclude(disposition, heap) then return end local function need_addr() return not (buftype:match("%[%]") or buftype:match("%*")) end if heap then body = body .. "#define BUF __stack.__buf\n" else body = body .. "#define BUF &__stack.__buf\n" end -- Setup the buffer body = body .. generate_stackframe(buftype, bufsize, disposition, heap, def) .. "\n" -- Any early initialization goes before we would fork for the just-after -- tests, because they may want to skip the test based on some criteria -- and we can't propagate that up very easily once we're forked. local early_init = configurable(def, "early_init") body = body .. (early_init or "") if early_init then body = body .. "\n" end -- Fork off, iff we're testing some access past the end of the buffer. if disposition > 0 then body = body .. [[ __child = fork(); ATF_REQUIRE(__child >= 0); if (__child > 0) goto monitor; /* Child */ disable_coredumps(); ]] end local bufvar = "__stack.__buf" if heap then -- Buffer needs to be initialized because it's a heap allocation. body = body .. "\t" .. bufvar .. " = malloc(__bufsz);\n" end -- Non-early init happens just after the fork in the child, not the -- monitor. This is used to setup any other buffers we may need, for -- instance. local extra_init = configurable(def, "init") body = body .. (extra_init or "") if heap or extra_init then body = body .. "\n" end -- Setup the function call with arguments as described in the test -- definition. body = body .. "\t" .. func .. "(" for idx, arg in ipairs(def.arguments) do if idx > 1 then body = body .. ", " end if arg == "__buf" then if not heap and need_addr() then body = body .. "&" end body = body .. bufvar else local argname = arg if def.value_of then argname = argname or def.value_of(arg) end body = body .. argname end end body = body .. ");\n" -- Monitor stuff follows, for OOB access. if disposition <= 0 then goto skip end body = body .. [[ _exit(EX_SOFTWARE); /* Should have aborted. */ monitor: while (waitpid(__child, &__status, 0) != __child) { ATF_REQUIRE_EQ(EINTR, errno); } if (!WIFSIGNALED(__status)) { switch (WEXITSTATUS(__status)) { case EX_SOFTWARE: atf_tc_fail("FORTIFY_SOURCE failed to abort"); break; case EX_OSERR: atf_tc_fail("setrlimit(2) failed"); break; default: atf_tc_fail("child exited with status %d", WEXITSTATUS(__status)); } } else { ATF_REQUIRE_EQ(SIGABRT, WTERMSIG(__status)); } ]] ::skip:: body = body .. "#undef BUF\n" return write_test_boilerplate(fh, testname, body, def) end -- main() local tests local tcat = assert(arg[1], "usage: generate-fortify-tests.lua ") for k, defs in pairs(all_tests) do if k == tcat then tests = defs break end end assert(tests, "category " .. tcat .. " not found") local fh = io.stdout fh:write("/* @" .. "generated" .. " by `generate-fortify-tests.lua \"" .. tcat .. "\"` */\n\n") fh:write("#define _FORTIFY_SOURCE 2\n") fh:write("#define TMPFILE_SIZE (1024 * 32)\n") fh:write("\n") for _, inc in ipairs(includes) do fh:write("#include <" .. inc .. ">\n") end fh:write([[ static FILE * __unused new_fp(size_t __len) { static char fpbuf[LINE_MAX]; FILE *fp; ATF_REQUIRE(__len <= sizeof(fpbuf)); memset(fpbuf, 'A', sizeof(fpbuf) - 1); fpbuf[sizeof(fpbuf) - 1] = '\0'; fp = fmemopen(fpbuf, sizeof(fpbuf), "rb"); ATF_REQUIRE(fp != NULL); return (fp); } /* * Create a new symlink to use for readlink(2) style tests, we'll just use a * random target name to have something interesting to look at. */ static const char * __unused new_symlink(size_t __len) { static const char linkname[] = "link"; char target[MAXNAMLEN]; int error; ATF_REQUIRE(__len <= sizeof(target)); arc4random_buf(target, sizeof(target)); error = unlink(linkname); ATF_REQUIRE(error == 0 || errno == ENOENT); error = symlink(target, linkname); ATF_REQUIRE(error == 0); return (linkname); } /* * For our purposes, first descriptor will be the reader; we'll send both * raw data and a control message over it so that the result can be used for * any of our recv*() tests. */ static void __unused new_socket(int sock[2]) { unsigned char ctrl[CMSG_SPACE(sizeof(int))] = { 0 }; static char sockbuf[256]; ssize_t rv; size_t total = 0; struct msghdr hdr = { 0 }; struct cmsghdr *cmsg; int error, fd; error = socketpair(AF_UNIX, SOCK_STREAM, 0, sock); ATF_REQUIRE(error == 0); while (total != sizeof(sockbuf)) { rv = send(sock[1], &sockbuf[total], sizeof(sockbuf) - total, 0); ATF_REQUIRE_MSG(rv > 0, "expected bytes sent, got %zd with %zu left (size %zu, total %zu)", rv, sizeof(sockbuf) - total, sizeof(sockbuf), total); ATF_REQUIRE_MSG(total + (size_t)rv <= sizeof(sockbuf), "%zd exceeds total %zu", rv, sizeof(sockbuf)); total += rv; } hdr.msg_control = ctrl; hdr.msg_controllen = sizeof(ctrl); cmsg = CMSG_FIRSTHDR(&hdr); cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_RIGHTS; cmsg->cmsg_len = CMSG_LEN(sizeof(fd)); fd = STDIN_FILENO; memcpy(CMSG_DATA(cmsg), &fd, sizeof(fd)); error = sendmsg(sock[1], &hdr, 0); ATF_REQUIRE(error != -1); } /* * Constructs a tmpfile that we can use for testing read(2) and friends. */ static int __unused new_tmpfile(void) { char buf[1024]; ssize_t rv; size_t written; int fd; fd = open("tmpfile", O_RDWR | O_CREAT | O_TRUNC, 0644); ATF_REQUIRE(fd >= 0); written = 0; while (written < TMPFILE_SIZE) { rv = write(fd, buf, sizeof(buf)); ATF_REQUIRE(rv > 0); written += rv; } ATF_REQUIRE_EQ(0, lseek(fd, 0, SEEK_SET)); return (fd); } static void disable_coredumps(void) { struct rlimit rl = { 0 }; if (setrlimit(RLIMIT_CORE, &rl) == -1) _exit(EX_OSERR); } /* * Replaces stdin with a file that we can actually read from, for tests where * we want a FILE * or fd that we can get data from. */ static void __unused replace_stdin(void) { int fd; fd = new_tmpfile(); (void)dup2(fd, STDIN_FILENO); if (fd != STDIN_FILENO) close(fd); } ]]) if tcat == "unistd" then fh:write("#define JAIL_HOSTNAME \"" .. hostname .. "\"\n") fh:write("#define JAIL_DOMAINNAME \"" .. domainname .. "\"\n") fh:write([[ static void dhost_jail(void) { struct iovec iov[4]; int jid; iov[0].iov_base = __DECONST(char *, "host.hostname"); iov[0].iov_len = sizeof("host.hostname"); iov[1].iov_base = __DECONST(char *, JAIL_HOSTNAME); iov[1].iov_len = sizeof(JAIL_HOSTNAME); iov[2].iov_base = __DECONST(char *, "host.domainname"); iov[2].iov_len = sizeof("host.domainname"); iov[3].iov_base = __DECONST(char *, JAIL_DOMAINNAME); iov[3].iov_len = sizeof(JAIL_DOMAINNAME); jid = jail_set(iov, nitems(iov), JAIL_CREATE | JAIL_ATTACH); ATF_REQUIRE_MSG(jid > 0, "Jail creation failed: %s", strerror(errno)); } ]]) end for _, def in pairs(tests) do local func = def.func local function write_tests(heap) -- Dispositions here are relative to the buffer size prescribed -- by the test definition. local dispositions = def.dispositions or { -1, 0, 1 } for _, disposition in ipairs(dispositions) do tests_added[#tests_added + 1] = write_test(fh, func, disposition, heap, def) end end write_tests(false) write_tests(true) end fh:write("ATF_TP_ADD_TCS(tp)\n") fh:write("{\n") for idx = 1, #tests_added do fh:write("\tATF_TP_ADD_TC(tp, " .. tests_added[idx] .. ");\n") end fh:write("\treturn (atf_no_error());\n") fh:write("}\n") diff --git a/lib/libc/tests/stdlib/Makefile b/lib/libc/tests/stdlib/Makefile index 08e356fc8706..50726a5d8af6 100644 --- a/lib/libc/tests/stdlib/Makefile +++ b/lib/libc/tests/stdlib/Makefile @@ -1,83 +1,84 @@ .include ATF_TESTS_C+= clearenv_test ATF_TESTS_C+= cxa_atexit_test ATF_TESTS_C+= dynthr_test +ATF_TESTS_C+= getenv_r_test ATF_TESTS_C+= heapsort_test ATF_TESTS_C+= libc_exit_test ATF_TESTS_C+= mergesort_test ATF_TESTS_C+= qsort_test .if ${COMPILER_FEATURES:Mblocks} ATF_TESTS_C+= qsort_b_test .endif ATF_TESTS_C+= qsort_r_compat_test ATF_TESTS_C+= qsort_r_test ATF_TESTS_C+= qsort_s_test ATF_TESTS_C+= set_constraint_handler_s_test ATF_TESTS_C+= strfmon_test ATF_TESTS_C+= tsearch_test ATF_TESTS_CXX+= cxa_thread_atexit_test ATF_TESTS_CXX+= cxa_thread_atexit_nothr_test # All architectures on FreeBSD have fenv.h CFLAGS+= -D__HAVE_FENV # Define __HAVE_LONG_DOUBLE for architectures whose long double has greater # precision than their double. .if ${MACHINE_CPUARCH} == "aarch64" || \ ${MACHINE_CPUARCH} == "amd64" || \ ${MACHINE_CPUARCH} == "i386" || \ ${MACHINE_CPUARCH} == "riscv" CFLAGS+= -D__HAVE_LONG_DOUBLE .endif # TODO: t_getenv_thread, t_mi_vector_hash, t_strtoi NETBSD_ATF_TESTS_C+= abs_test NETBSD_ATF_TESTS_C+= atoi_test NETBSD_ATF_TESTS_C+= div_test NETBSD_ATF_TESTS_C+= getenv_test NETBSD_ATF_TESTS_C+= exit_test NETBSD_ATF_TESTS_C+= hsearch_test NETBSD_ATF_TESTS_C+= posix_memalign_test NETBSD_ATF_TESTS_C+= random_test NETBSD_ATF_TESTS_C+= strtod_test NETBSD_ATF_TESTS_C+= strtol_test NETBSD_ATF_TESTS_C+= system_test # TODO: need to come up with a correct explanation of what the patch pho does # with h_atexit #ATF_TESTS_SH= atexit_test NETBSD_ATF_TESTS_SH= getopt_test .include "../Makefile.netbsd-tests" BINDIR= ${TESTSDIR} # TODO: see comment above #PROGS+= h_atexit PROGS+= h_getopt h_getopt_long CFLAGS+= -I${.CURDIR} LIBADD.cxa_thread_atexit_test+= pthread # Tests that require blocks support .for t in qsort_b_test CFLAGS.${t}.c+= -fblocks LIBADD.${t}+= BlocksRuntime .endfor .for t in h_getopt h_getopt_long CFLAGS.$t+= -I${LIBNETBSD_SRCDIR} -I${SRCTOP}/contrib/netbsd-tests LDFLAGS.$t+= -L${LIBNETBSD_OBJDIR} LIBADD.${t}+= netbsd util .endfor LIBADD.libc_exit_test+= pthread LIBADD.strtod_test+= m SUBDIR+= dynthr_mod SUBDIR+= libatexit .include diff --git a/lib/libc/tests/stdlib/getenv_r_test.c b/lib/libc/tests/stdlib/getenv_r_test.c new file mode 100644 index 000000000000..7935ead6c495 --- /dev/null +++ b/lib/libc/tests/stdlib/getenv_r_test.c @@ -0,0 +1,69 @@ +/*- + * Copyright (c) 2025 Klara, Inc. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +#include + +ATF_TC_WITHOUT_HEAD(getenv_r_ok); +ATF_TC_BODY(getenv_r_ok, tc) +{ + const char *ident = atf_tc_get_ident(tc); + char buf[256]; + + ATF_REQUIRE_EQ(0, setenv("ATF_TC_IDENT", ident, 1)); + ATF_REQUIRE_EQ(0, getenv_r("ATF_TC_IDENT", buf, sizeof(buf))); + ATF_REQUIRE_STREQ(ident, buf); +} + +ATF_TC_WITHOUT_HEAD(getenv_r_einval); +ATF_TC_BODY(getenv_r_einval, tc) +{ + char buf[256]; + + errno = 0; + ATF_REQUIRE_EQ(-1, getenv_r(NULL, buf, sizeof(buf))); + ATF_REQUIRE_EQ(EINVAL, errno); + errno = 0; + ATF_REQUIRE_EQ(-1, getenv_r("", buf, sizeof(buf))); + ATF_REQUIRE_EQ(EINVAL, errno); + errno = 0; + ATF_REQUIRE_EQ(-1, getenv_r("A=B", buf, sizeof(buf))); + ATF_REQUIRE_EQ(EINVAL, errno); +} + +ATF_TC_WITHOUT_HEAD(getenv_r_enoent); +ATF_TC_BODY(getenv_r_enoent, tc) +{ + char buf[256]; + + errno = 0; + ATF_REQUIRE_EQ(-1, getenv_r("no such variable", buf, sizeof(buf))); + ATF_REQUIRE_EQ(ENOENT, errno); +} + +ATF_TC_WITHOUT_HEAD(getenv_r_erange); +ATF_TC_BODY(getenv_r_erange, tc) +{ + const char *ident = atf_tc_get_ident(tc); + char buf[256]; + + ATF_REQUIRE_EQ(0, setenv("ATF_TC_IDENT", ident, 1)); + errno = 0; + ATF_REQUIRE_EQ(-1, getenv_r(NULL, buf, strlen(ident))); + ATF_REQUIRE_EQ(ERANGE, errno); +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, getenv_r_ok); + ATF_TP_ADD_TC(tp, getenv_r_einval); + ATF_TP_ADD_TC(tp, getenv_r_enoent); + ATF_TP_ADD_TC(tp, getenv_r_erange); + return (atf_no_error()); +}