git: 873420ca1e6e - main - libc: Add getenv_r() function.

From: Dag-Erling Smørgrav <des_at_FreeBSD.org>
Date: Sun, 27 Apr 2025 06:29:53 UTC
The branch main has been updated by des:

URL: https://cgit.FreeBSD.org/src/commit/?id=873420ca1e6e8a2459684f5b5d3e557a8ef75928

commit 873420ca1e6e8a2459684f5b5d3e557a8ef75928
Author:     Dag-Erling Smørgrav <des@FreeBSD.org>
AuthorDate: 2025-04-27 06:29:10 +0000
Commit:     Dag-Erling Smørgrav <des@FreeBSD.org>
CommitDate: 2025-04-27 06:29:32 +0000

    libc: Add getenv_r() function.
    
    This is a calque of the NetBSD function of the same name.
    
    MFC after:      never
    Relontes:       yes
    Sponsored by:   Klara, Inc.
    Reviewed by:    kevans
    Differential Revision:  https://reviews.freebsd.org/D49979
---
 include/ssp/stdlib.h                             |   4 +
 include/stdlib.h                                 |   3 +
 lib/libc/stdlib/Symbol.map                       |   4 +
 lib/libc/stdlib/getenv.3                         |  38 +++++-
 lib/libc/stdlib/getenv.c                         |  36 ++++++
 lib/libc/tests/secure/fortify_stdlib_test.c      | 147 +++++++++++++++++++++++
 lib/libc/tests/secure/generate-fortify-tests.lua |   9 ++
 lib/libc/tests/stdlib/Makefile                   |   1 +
 lib/libc/tests/stdlib/getenv_r_test.c            |  69 +++++++++++
 9 files changed, 308 insertions(+), 3 deletions(-)

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
@@ -38,6 +38,10 @@ __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))
 {
diff --git a/include/stdlib.h b/include/stdlib.h
index 162031ab393d..ba0cf4b5e88e 100644
--- a/include/stdlib.h
+++ b/include/stdlib.h
@@ -95,6 +95,9 @@ 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);
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
@@ -127,6 +127,10 @@ FBSD_1.7 {
 	secure_getenv;
 };
 
+FBSD_1.8 {
+	getenv_r;
+};
+
 FBSDprivate_1.0 {
 	__system;
 	_system;
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
@@ -29,12 +29,13 @@
 .\" 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 ,
@@ -48,6 +49,8 @@
 .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
@@ -71,7 +74,8 @@ and
 .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
@@ -79,6 +83,16 @@ to by the
 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
@@ -157,12 +171,13 @@ function returns
 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
@@ -191,6 +206,11 @@ is the first character in
 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 ,
@@ -198,6 +218,11 @@ The function
 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 ,
@@ -251,6 +276,13 @@ 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
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
@@ -28,6 +28,7 @@
 
 #include "namespace.h"
 #include <sys/types.h>
+#include <ssp/ssp.h>
 #include <errno.h>
 #include <stdbool.h>
 #include <stddef.h>
@@ -443,6 +444,41 @@ getenv(const char *name)
 }
 
 
+/*
+ * 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.
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
@@ -305,6 +305,148 @@ monitor:
 
 }
 
+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)
 {
@@ -454,6 +596,11 @@ ATF_TP_ADD_TCS(tp)
 	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);
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
@@ -584,6 +584,15 @@ local all_tests = {
 			},
 			exclude = excludes_stack_overflow,
 		},
+		{
+			func = "getenv_r",
+			arguments = {
+				"\"PATH\"",
+				"__buf",
+				"__len",
+			},
+			exclude = excludes_stack_overflow,
+		},
 		{
 			func = "realpath",
 			bufsize = "PATH_MAX",
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
@@ -3,6 +3,7 @@
 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
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 <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <atf-c.h>
+
+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());
+}