zfs/lib/libzfs/os/freebsd/libzfs_compat.c

371 lines
8.4 KiB
C
Raw Normal View History

/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or https://opensource.org/licenses/CDDL-1.0.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright (c) 2013 Martin Matuska <mm@FreeBSD.org>. All rights reserved.
*/
#include "../../libzfs_impl.h"
#include <libzfs.h>
#include <libzutil.h>
#include <sys/sysctl.h>
#include <libintl.h>
#include <sys/linker.h>
#include <sys/module.h>
#include <sys/stat.h>
#include <sys/param.h>
#ifdef IN_BASE
#define ZFS_KMOD "zfs"
#else
#define ZFS_KMOD "openzfs"
#endif
static int
execvPe(const char *name, const char *path, char * const *argv,
char * const *envp)
{
const char **memp;
size_t cnt, lp, ln;
int eacces, save_errno;
char buf[MAXPATHLEN];
const char *bp, *np, *op, *p;
struct stat sb;
eacces = 0;
/* If it's an absolute or relative path name, it's easy. */
if (strchr(name, '/')) {
bp = name;
op = NULL;
goto retry;
}
bp = buf;
/* If it's an empty path name, fail in the usual POSIX way. */
if (*name == '\0') {
errno = ENOENT;
return (-1);
}
op = path;
ln = strlen(name);
while (op != NULL) {
np = strchrnul(op, ':');
/*
* It's a SHELL path -- double, leading and trailing colons
* mean the current directory.
*/
if (np == op) {
/* Empty component. */
p = ".";
lp = 1;
} else {
/* Non-empty component. */
p = op;
lp = np - op;
}
/* Advance to the next component or terminate after this. */
if (*np == '\0')
op = NULL;
else
op = np + 1;
/*
* If the path is too long complain. This is a possible
* security issue; given a way to make the path too long
* the user may execute the wrong program.
*/
if (lp + ln + 2 > sizeof (buf)) {
(void) write(STDERR_FILENO, "execvP: ", 8);
(void) write(STDERR_FILENO, p, lp);
(void) write(STDERR_FILENO, ": path too long\n",
16);
continue;
}
memcpy(buf, p, lp);
buf[lp] = '/';
memcpy(buf + lp + 1, name, ln);
buf[lp + ln + 1] = '\0';
retry: (void) execve(bp, argv, envp);
switch (errno) {
case E2BIG:
goto done;
case ELOOP:
case ENAMETOOLONG:
case ENOENT:
break;
case ENOEXEC:
for (cnt = 0; argv[cnt]; ++cnt)
;
/*
* cnt may be 0 above; always allocate at least
* 3 entries so that we can at least fit "sh", bp, and
* the NULL terminator. We can rely on cnt to take into
* account the NULL terminator in all other scenarios,
* as we drop argv[0].
*/
memp = alloca(MAX(3, cnt + 2) * sizeof (char *));
if (memp == NULL) {
/* errno = ENOMEM; XXX override ENOEXEC? */
goto done;
}
if (cnt > 0) {
memp[0] = argv[0];
memp[1] = bp;
memcpy(memp + 2, argv + 1,
cnt * sizeof (char *));
} else {
memp[0] = "sh";
memp[1] = bp;
memp[2] = NULL;
}
(void) execve(_PATH_BSHELL,
__DECONST(char **, memp), envp);
goto done;
case ENOMEM:
goto done;
case ENOTDIR:
break;
case ETXTBSY:
/*
* We used to retry here, but sh(1) doesn't.
*/
goto done;
default:
/*
* EACCES may be for an inaccessible directory or
* a non-executable file. Call stat() to decide
* which. This also handles ambiguities for EFAULT
* and EIO, and undocumented errors like ESTALE.
* We hope that the race for a stat() is unimportant.
*/
save_errno = errno;
if (stat(bp, &sb) != 0)
break;
if (save_errno == EACCES) {
eacces = 1;
continue;
}
errno = save_errno;
goto done;
}
}
if (eacces)
errno = EACCES;
else
errno = ENOENT;
done:
return (-1);
}
int
execvpe(const char *name, char * const argv[], char * const envp[])
{
const char *path;
/* Get the path we're searching. */
if ((path = getenv("PATH")) == NULL)
path = _PATH_DEFPATH;
return (execvPe(name, path, argv, envp));
}
static __thread char errbuf[ERRBUFLEN];
const char *
libzfs_error_init(int error)
{
char *msg = errbuf;
size_t msglen = sizeof (errbuf);
if (modfind("zfs") < 0) {
Introduce kmem_scnprintf() `snprintf()` is meant to protect against buffer overflows, but operating on the buffer using its return value, possibly by calling it again, can cause a buffer overflow, because it will return how many characters it would have written if it had enough space even when it did not. In a number of places, we repeatedly call snprintf() by successively incrementing a buffer offset and decrementing a buffer length, by its return value. This is a potentially unsafe usage of `snprintf()` whenever the buffer length is reached. CodeQL complained about this. To fix this, we introduce `kmem_scnprintf()`, which will return 0 when the buffer is zero or the number of written characters, minus 1 to exclude the NULL character, when the buffer was too small. In all other cases, it behaves like snprintf(). The name is inspired by the Linux and XNU kernels' `scnprintf()`. The implementation was written before I thought to look at `scnprintf()` and had a good name for it, but it turned out to have identical semantics to the Linux kernel version. That lead to the name, `kmem_scnprintf()`. CodeQL only catches this issue in loops, so repeated use of snprintf() outside of a loop was not caught. As a result, a thorough audit of the codebase was done to examine all instances of `snprintf()` usage for potential problems and a few were caught. Fixes for them are included in this patch. Unfortunately, ZED is one of the places where `snprintf()` is potentially used incorrectly. Since using `kmem_scnprintf()` in it would require changing how it is linked, we modify its usage to make it safe, no matter what buffer length is used. In addition, there was a bug in the use of the return value where the NULL format character was not being written by pwrite(). That has been fixed. Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov> Signed-off-by: Richard Yao <richard.yao@alumni.stonybrook.edu> Closes #14098
2022-10-27 18:16:04 +00:00
size_t len = kmem_scnprintf(msg, msglen, dgettext(TEXT_DOMAIN,
"Failed to load %s module: "), ZFS_KMOD);
msg += len;
msglen -= len;
}
(void) snprintf(msg, msglen, "%s", strerror(error));
return (errbuf);
}
int
zfs_ioctl(libzfs_handle_t *hdl, int request, zfs_cmd_t *zc)
{
return (lzc_ioctl_fd(hdl->libzfs_fd, request, zc));
}
/*
* Verify the required ZFS_DEV device is available and optionally attempt
* to load the ZFS modules. Under normal circumstances the modules
* should already have been loaded by some external mechanism.
*/
int
libzfs_load_module(void)
{
/*
* XXX: kldfind(ZFS_KMOD) would be nice here, but we retain
* modfind("zfs") so out-of-base openzfs userland works with the
* in-base module.
*/
if (modfind("zfs") < 0) {
/* Not present in kernel, try loading it. */
if (kldload(ZFS_KMOD) < 0 && errno != EEXIST) {
return (errno);
}
}
return (0);
}
int
zpool_relabel_disk(libzfs_handle_t *hdl, const char *path, const char *msg)
{
(void) hdl, (void) path, (void) msg;
return (0);
}
int
zpool_label_disk(libzfs_handle_t *hdl, zpool_handle_t *zhp, const char *name)
{
(void) hdl, (void) zhp, (void) name;
return (0);
}
int
find_shares_object(differ_info_t *di)
{
(void) di;
return (0);
}
int
zfs_destroy_snaps_nvl_os(libzfs_handle_t *hdl, nvlist_t *snaps)
{
(void) hdl, (void) snaps;
return (0);
}
/*
* Attach/detach the given filesystem to/from the given jail.
*/
int
zfs_jail(zfs_handle_t *zhp, int jailid, int attach)
{
libzfs_handle_t *hdl = zhp->zfs_hdl;
zfs_cmd_t zc = {"\0"};
unsigned long cmd;
int ret;
if (attach) {
(void) snprintf(errbuf, sizeof (errbuf),
dgettext(TEXT_DOMAIN, "cannot jail '%s'"), zhp->zfs_name);
} else {
(void) snprintf(errbuf, sizeof (errbuf),
dgettext(TEXT_DOMAIN, "cannot unjail '%s'"), zhp->zfs_name);
}
switch (zhp->zfs_type) {
case ZFS_TYPE_VOLUME:
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
"volumes can not be jailed"));
return (zfs_error(hdl, EZFS_BADTYPE, errbuf));
case ZFS_TYPE_SNAPSHOT:
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
"snapshots can not be jailed"));
return (zfs_error(hdl, EZFS_BADTYPE, errbuf));
case ZFS_TYPE_BOOKMARK:
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
"bookmarks can not be jailed"));
return (zfs_error(hdl, EZFS_BADTYPE, errbuf));
case ZFS_TYPE_VDEV:
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
"vdevs can not be jailed"));
return (zfs_error(hdl, EZFS_BADTYPE, errbuf));
case ZFS_TYPE_INVALID:
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
"invalid zfs_type_t: ZFS_TYPE_INVALID"));
return (zfs_error(hdl, EZFS_BADTYPE, errbuf));
case ZFS_TYPE_POOL:
case ZFS_TYPE_FILESYSTEM:
/* OK */
;
}
assert(zhp->zfs_type == ZFS_TYPE_FILESYSTEM);
(void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name));
zc.zc_objset_type = DMU_OST_ZFS;
zc.zc_zoneid = jailid;
cmd = attach ? ZFS_IOC_JAIL : ZFS_IOC_UNJAIL;
if ((ret = zfs_ioctl(hdl, cmd, &zc)) != 0)
zfs_standard_error(hdl, errno, errbuf);
return (ret);
}
/*
* Set loader options for next boot.
*/
int
zpool_nextboot(libzfs_handle_t *hdl, uint64_t pool_guid, uint64_t dev_guid,
const char *command)
{
zfs_cmd_t zc = {"\0"};
nvlist_t *args;
args = fnvlist_alloc();
fnvlist_add_uint64(args, ZPOOL_CONFIG_POOL_GUID, pool_guid);
fnvlist_add_uint64(args, ZPOOL_CONFIG_GUID, dev_guid);
fnvlist_add_string(args, "command", command);
zcmd_write_src_nvlist(hdl, &zc, args);
int error = zfs_ioctl(hdl, ZFS_IOC_NEXTBOOT, &zc);
zcmd_free_nvlists(&zc);
nvlist_free(args);
return (error);
}
/*
* Return allocated loaded module version, or NULL on error (with errno set)
*/
char *
zfs_version_kernel(void)
{
size_t l;
if (sysctlbyname("vfs.zfs.version.module",
NULL, &l, NULL, 0) == -1)
return (NULL);
char *version = malloc(l);
if (version == NULL)
return (NULL);
if (sysctlbyname("vfs.zfs.version.module",
version, &l, NULL, 0) == -1) {
free(version);
return (NULL);
}
return (version);
}