Date: Tue, 21 Jul 2026 11:34:28 +0000 From: Dag-Erling=?utf-8?Q? Sm=C3=B8rg?=rav <des@FreeBSD.org> To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org Subject: git: c93f3b55e67f - main - libutil: Reimplement getlocalbase() Message-ID: <6a5f5944.3a8ff.647c3596@gitrepo.freebsd.org>
index | next in thread | raw e-mail
The branch main has been updated by des: URL: https://cgit.FreeBSD.org/src/commit/?id=c93f3b55e67fe465d93f3f54ae4408f131fec3aa commit c93f3b55e67fe465d93f3f54ae4408f131fec3aa Author: Dag-Erling Smørgrav <des@FreeBSD.org> AuthorDate: 2026-07-21 08:31:26 +0000 Commit: Dag-Erling Smørgrav <des@FreeBSD.org> CommitDate: 2026-07-21 08:31:49 +0000 libutil: Reimplement getlocalbase() * Get rid of the pointless LOCALBASE_CTL_LEN mechanism * Apply minimal normalization to the paths obtained from the environment or sysctl variable * Turn the manual page into a manual page * Add tests MFC after: 1 week Reviewed by: se Differential Revision: https://reviews.freebsd.org/D58362 --- lib/libutil/getlocalbase.3 | 111 ++++++++++++++++------------------ lib/libutil/getlocalbase.c | 100 ++++++++++++++++++------------ lib/libutil/tests/Makefile | 1 + lib/libutil/tests/getlocalbase_test.c | 92 ++++++++++++++++++++++++++++ 4 files changed, 206 insertions(+), 98 deletions(-) diff --git a/lib/libutil/getlocalbase.3 b/lib/libutil/getlocalbase.3 index 5c71a87be8a3..ea02ae801bbb 100644 --- a/lib/libutil/getlocalbase.3 +++ b/lib/libutil/getlocalbase.3 @@ -3,6 +3,7 @@ .\" .\" Copyright 2020 Scott Long .\" Copyright 2020 Stefan Eßer +.\" Copyright (c) 2026 Dag-Erling Smørgrav .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions @@ -25,97 +26,89 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd July 11, 2023 +.Dd July 20, 2026 .Dt GETLOCALBASE 3 .Os .Sh NAME .Nm getlocalbase -.Nd "return the path to the local software directory" +.Nd "return the path to the local software base directory" .Sh LIBRARY .Lb libutil .Sh SYNOPSIS .In libutil.h -.Ft const char* +.Ft "const char *" .Fn getlocalbase "void" .Sh DESCRIPTION The -.Fn getlocalbase -function returns the path to the local software base directory. -Normally this is the -.Pa /usr/local -directory. +.Nm +function returns the path to the local software base directory, +normally +.Pa /usr/local . +.Pp First the .Ev LOCALBASE environment variable is checked. -If that does not exist then the +If that variable is undefined or empty, the .Va user.localbase sysctl is checked. -If that also does not exist then the value of the -.Dv LOCALBASE_PATH -compile-time variable is used. -If that is undefined then the system default, -.Pa _PATH_LOCALBASE -is used. +If that returns an empty string or an error occurs, the value of +.Dv _PATH_LOCALBASE +is used as a last resort. .Pp -The contents of the string returned by the -.Fn getlocalbase -function shall not be modified. -.Sh IMPLEMENTATION NOTES -Calls to -.Fn getlocalbase -will perform a setugid check on the running binary before checking the -environment. +If the value obtained through these means is not a valid absolute path +shorter than +.Dv MAXPATHLEN , +a constant string which has been deliberately chosen to cause any file +system operation using it to fail is returned instead. .Pp -The address returned by -.Fn getlocalbase -will point into the executing processes environment if it is the result of -.Fn getenv "LOCALBASE" , -to a static buffer if it is the result of -.Fn sysctl "user.localbase" , -and to a constant string if the compiled in default value is returned. +The contents of the string returned by +.Nm +shall not be modified by the caller. +.Sh IMPLEMENTATION NOTES +The +.Ev LOCALBASE +environment variable will only be used if the process calling +.Nm +is not setugid. .Pp -The same value will be returned on successive calls during the run-time -of the program, ignoring any changes to the environment variable or the -sysctl value that might have been made. +Successive calls to +.Nm +will return the same value throughout the lifetime of the process, +regardless of any subsequent changes to the environment or sysctl +variables. .Pp The -.Fn getlocalbase -function can be compiled with a non-default value of LOCALBASE_CTL_LEN. -A value of 0 will disable fetching of the sysctl value, a value less than -MAXPATHLEN will put a limit on the maximum string length supported for -this sysctl value. -If built with a non-default value of LOCALBASE_CTL_LEN, a value of the -user.localbase sysctl variable longer than this value will make -.Fn getlocalbase -return a valid string that is not a valid path prefix in any filesystem. +.Nm +function is thread-safe if and only if it has been called at least +once already. .Sh RETURN VALUES The .Fn getlocalbase -function returns a pointer to a string, whose length may exceed MAXPATHLEN, -if it has been obtained from the environment. +function returns a pointer to a null-terminated string. .Sh ENVIRONMENT -The -.Fn getlocalbase -library function retrieves the -.Ev LOCALBASE -environment variable. -.Sh ERRORS -The -.Fn getlocalbase -function always succeeds and returns a valid pointer to a string. +.Bl -tag -width ".Ev LOCALBASE" +.It Ev LOCALBASE +Path to the local software base directory +.El .Sh SEE ALSO -.Xr env 1 , -.Xr src.conf 5 , +.Xr environ 7 , .Xr sysctl 8 .Sh HISTORY The .Nm -library function first appeared in +function first appeared in .Fx 13.0 . .Sh AUTHORS .An -nosplit -This -manual page was written by +The +.Nm +function was originally written by +.An Stefan Eßer Aq Mt se@FreeBSD.org +and was later reimplemented by +.An Dag-Erling Sm\(/orgrav Aq Mt des@FreeBSD.org . +This manual page was originally written by .An Scott Long Aq Mt scottl@FreeBSD.org and -.An Stefan Eßer Aq Mt se@FreeBSD.org . +.An Stefan Eßer Aq Mt se@FreeBSD.org +and was later substantially rewritten by +.An Dag-Erling Sm\(/orgrav Aq Mt des@FreeBSD.org . diff --git a/lib/libutil/getlocalbase.c b/lib/libutil/getlocalbase.c index e8aabbab1e14..d48e0dbb33fd 100644 --- a/lib/libutil/getlocalbase.c +++ b/lib/libutil/getlocalbase.c @@ -1,7 +1,7 @@ -/*- +/* * SPDX-License-Identifier: BSD-2-Clause * - * Copyright 2020 Stefan Eßer <se@freebsd.org> + * Copyright (c) 2026 Dag-Erling Smørgrav * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -27,61 +27,83 @@ #include <sys/param.h> #include <sys/sysctl.h> -#include <sys/limits.h> + #include <errno.h> -#include <stdlib.h> -#include <paths.h> #include <libutil.h> +#include <paths.h> +#include <stdbool.h> +#include <stdlib.h> #include <unistd.h> -#ifndef LOCALBASE_PATH -#define LOCALBASE_PATH _PATH_LOCALBASE +#ifndef _PATH_LOCALBASE +#define _PATH_LOCALBASE "/usr/local" #endif -#ifndef LOCALBASE_CTL_LEN -#define LOCALBASE_CTL_LEN MAXPATHLEN -#endif +/* + * Used in case of error; guaranteed to not be the start of a valid path + * name. Deliberately includes a trailing path separator so that any + * access will fail with ENOTDIR. + */ +#define INVALID_PREFIX "/dev/null/" -/* Any prefix guaranteed to not be the start of a valid path name */ -#define ILLEGAL_PREFIX "/dev/null/" +/* + * Copies a path from src to dst, which may point to the same buffer, + * while deduplicating path separators. Returns false if the path is not + * absolute or the result (including the terminating NUL) exceeds len + * characters. + */ +static bool +normalize(const char *src, char *dst, size_t len) +{ + if (*src != '/' || len == 0) + return (false); + while (*src == '/') + src++; + do { + *dst++ = '/'; + if (--len == 0) + return (false); + while (*src != '\0' && *src != '/') { + *dst++ = *src++; + if (--len == 0) + return (false); + } + while (*src == '/') + src++; + } while (*src != '\0'); + *dst = '\0'; + return (true); +} const char * getlocalbase(void) { -#if LOCALBASE_CTL_LEN > 0 - int localbase_oid[2] = {CTL_USER, USER_LOCALBASE}; - static char localpath[LOCALBASE_CTL_LEN]; - size_t localpathlen = LOCALBASE_CTL_LEN; -#endif - char *tmppath; + static const int oid[2] = { CTL_USER, USER_LOCALBASE }; + static char buf[MAXPATHLEN]; static const char *localbase = NULL; + char *path; + size_t len = sizeof(buf); if (localbase != NULL) return (localbase); - if (issetugid() == 0) { - tmppath = getenv("LOCALBASE"); - if (tmppath != NULL && tmppath[0] != '\0') { - localbase = tmppath; - return (localbase); - } + /* If not privileged, try the environment first */ + if ((path = secure_getenv("LOCALBASE")) != NULL && *path != '\0') { + if (!normalize(path, buf, sizeof(buf))) + return (localbase = INVALID_PREFIX); + return (localbase = buf); } -#if LOCALBASE_CTL_LEN > 0 - if (sysctl(localbase_oid, 2, localpath, &localpathlen, NULL, 0) != 0) { - if (errno != ENOMEM) - localbase = LOCALBASE_PATH; - else - localbase = ILLEGAL_PREFIX; - } else { - if (localpath[0] != '\0') - localbase = localpath; - else - localbase = LOCALBASE_PATH; + /* Next, try the sysctl variable */ + if (sysctl(oid, 2, buf, &len, NULL, 0) != 0) { + if (errno == ENOMEM) + return (localbase = INVALID_PREFIX); + } else if (*buf != '\0') { + if (!normalize(buf, buf, sizeof(buf))) + return (localbase = INVALID_PREFIX); + return (localbase = buf); } -#else - localbase = LOCALBASE_PATH; -#endif - return (localbase); + /* Fall back to the hardcoded default */ + return (localbase = _PATH_LOCALBASE); } diff --git a/lib/libutil/tests/Makefile b/lib/libutil/tests/Makefile index cdcf3466aec8..2523c02eb33d 100644 --- a/lib/libutil/tests/Makefile +++ b/lib/libutil/tests/Makefile @@ -7,6 +7,7 @@ TAP_TESTS_C+= trimdomain-nodomain_test ATF_TESTS_C+= cpuset_test ATF_TESTS_C+= expand_number_test ATF_TESTS_C+= forkpty_test +ATF_TESTS_C+= getlocalbase_test WARNS?= 2 LIBADD+= util diff --git a/lib/libutil/tests/getlocalbase_test.c b/lib/libutil/tests/getlocalbase_test.c new file mode 100644 index 000000000000..4f90ddf26cc5 --- /dev/null +++ b/lib/libutil/tests/getlocalbase_test.c @@ -0,0 +1,92 @@ +/*- + * Copyright (c) 2026 Dag-Erling Smørgrav + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <sys/param.h> +#include <sys/wait.h> + +#include <libutil.h> +#include <paths.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <atf-c.h> + +#define INVALID_PREFIX "/dev/null/" + +#define X31 "/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +#define X32 "/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +#define X64 X32 X32 +#define X128 X64 X64 +#define X256 X128 X128 +#define X512 X256 X256 +#define X1024 X512 X512 +#define X1023 X512 X256 X128 X64 X32 X31 + +static struct { + const char *in, *out; +} tests[] = { + { "", _PATH_LOCALBASE }, + { "/", "/" }, + { "//", "/" }, + { "///", "/" }, + { "foo", INVALID_PREFIX }, + { "/foo", "/foo" }, + { "//foo", "/foo" }, + { "/foo/", "/foo" }, + { "/foo//", "/foo" }, + { "//foo//", "/foo" }, + { "/foo/bar", "/foo/bar" }, + { "/foo/bar/", "/foo/bar" }, + { "/foo//bar", "/foo/bar" }, + { "/foo//bar/", "/foo/bar" }, + { "//foo/bar", "/foo/bar" }, + { "//foo/bar/", "/foo/bar" }, + { "//foo//bar", "/foo/bar" }, + { "//foo//bar/", "/foo/bar" }, + { _PATH_LOCALBASE, _PATH_LOCALBASE }, + { X31, X31 }, + { X32, X32 }, + { X64, X64 }, + { X128, X128 }, + { X256, X256 }, + { X512, X512 }, + { X1023, X1023 }, + { X1024, INVALID_PREFIX }, + { 0 } +}; +_Static_assert(sizeof(X1023) == MAXPATHLEN, "Unexpected MAXPATHLEN value"); + +ATF_TC(environment); +ATF_TC_HEAD(environment, tc) +{ + atf_tc_set_md_var(tc, "descr", "Tests getting the local base from the " + "environment and path normalization"); +} + +ATF_TC_BODY(environment, tc) +{ + pid_t pid; + int i, wstatus; + + for (i = 0; tests[i].in != NULL; i++) { + ATF_REQUIRE((pid = fork()) >= 0); + if (pid == 0) { + ATF_REQUIRE_EQ(setenv("LOCALBASE", tests[i].in, 1), 0); + ATF_REQUIRE_STREQ(getlocalbase(), tests[i].out); + _exit(0); + } + ATF_REQUIRE_EQ(waitpid(0, &wstatus, 0), pid); + ATF_CHECK(WIFEXITED(wstatus)); + ATF_CHECK_EQ(WEXITSTATUS(wstatus), 0); + } +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, environment); + return (atf_no_error()); +}home | help
Want to link to this message? Use this
URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?6a5f5944.3a8ff.647c3596>
