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
This commit is contained in:
Dag-Erling Smørgrav 2025-04-27 08:29:10 +02:00
parent 3bef9b313b
commit 873420ca1e
9 changed files with 308 additions and 3 deletions

View file

@ -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))
{

View file

@ -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);

View file

@ -127,6 +127,10 @@ FBSD_1.7 {
secure_getenv;
};
FBSD_1.8 {
getenv_r;
};
FBSDprivate_1.0 {
__system;
_system;

View file

@ -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

View file

@ -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.

View file

@ -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);

View file

@ -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",

View file

@ -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

View file

@ -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());
}