2020-06-25 01:45:44 +00:00
|
|
|
/*
|
|
|
|
* Redistribution and use in source and binary forms, with or without
|
|
|
|
* modification, are permitted provided that the following conditions are met:
|
|
|
|
* * Redistributions of source code must retain the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer.
|
|
|
|
* * 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.
|
|
|
|
* * Neither the name of the <organization> 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 COPYRIGHT HOLDERS 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 <COPYRIGHT HOLDER> 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.
|
|
|
|
*
|
|
|
|
* Copyright (c) 2020, Felix Dörre
|
|
|
|
* All rights reserved.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <sys/dsl_crypt.h>
|
|
|
|
#include <sys/byteorder.h>
|
|
|
|
#include <libzfs.h>
|
|
|
|
|
|
|
|
#include <syslog.h>
|
|
|
|
|
|
|
|
#include <sys/zio_crypt.h>
|
|
|
|
#include <openssl/evp.h>
|
|
|
|
|
|
|
|
#define PAM_SM_AUTH
|
|
|
|
#define PAM_SM_PASSWORD
|
|
|
|
#define PAM_SM_SESSION
|
|
|
|
#include <security/pam_modules.h>
|
|
|
|
|
|
|
|
#if defined(__linux__)
|
|
|
|
#include <security/pam_ext.h>
|
2021-10-21 10:17:47 +00:00
|
|
|
#define MAP_FLAGS MAP_PRIVATE | MAP_ANONYMOUS
|
2020-06-25 01:45:44 +00:00
|
|
|
#elif defined(__FreeBSD__)
|
|
|
|
#include <security/pam_appl.h>
|
|
|
|
static void
|
|
|
|
pam_syslog(pam_handle_t *pamh, int loglevel, const char *fmt, ...)
|
|
|
|
{
|
2022-02-16 12:09:27 +00:00
|
|
|
(void) pamh;
|
2020-06-25 01:45:44 +00:00
|
|
|
va_list args;
|
|
|
|
va_start(args, fmt);
|
|
|
|
vsyslog(loglevel, fmt, args);
|
|
|
|
va_end(args);
|
|
|
|
}
|
2021-10-21 10:17:47 +00:00
|
|
|
#define MAP_FLAGS MAP_PRIVATE | MAP_ANON | MAP_NOCORE
|
2020-06-25 01:45:44 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
#include <string.h>
|
2021-10-21 04:06:55 +00:00
|
|
|
#include <unistd.h>
|
2020-06-25 01:45:44 +00:00
|
|
|
#include <fcntl.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <sys/file.h>
|
|
|
|
#include <sys/wait.h>
|
|
|
|
#include <pwd.h>
|
|
|
|
|
|
|
|
#include <sys/mman.h>
|
|
|
|
|
|
|
|
static const char PASSWORD_VAR_NAME[] = "pam_zfs_key_authtok";
|
|
|
|
|
|
|
|
static libzfs_handle_t *g_zfs;
|
|
|
|
|
|
|
|
static void destroy_pw(pam_handle_t *pamh, void *data, int errcode);
|
|
|
|
|
2021-10-21 04:06:55 +00:00
|
|
|
typedef int (*mlock_func_t) (const void *, size_t);
|
|
|
|
|
2020-06-25 01:45:44 +00:00
|
|
|
typedef struct {
|
|
|
|
size_t len;
|
|
|
|
char *value;
|
|
|
|
} pw_password_t;
|
|
|
|
|
2021-10-21 04:06:55 +00:00
|
|
|
/*
|
2021-10-21 10:17:47 +00:00
|
|
|
* Try to mlock(2) or munlock(2) addr while handling EAGAIN by retrying ten
|
|
|
|
* times and sleeping 10 milliseconds in between for a total of 0.1
|
|
|
|
* seconds. lock_func must point to either mlock(2) or munlock(2).
|
2021-10-21 04:06:55 +00:00
|
|
|
*/
|
|
|
|
static int
|
|
|
|
try_lock(mlock_func_t lock_func, const void *addr, size_t len)
|
|
|
|
{
|
|
|
|
int err;
|
|
|
|
int retries = 10;
|
|
|
|
useconds_t sleep_dur = 10 * 1000;
|
|
|
|
|
|
|
|
if ((err = (*lock_func)(addr, len)) != EAGAIN) {
|
|
|
|
return (err);
|
|
|
|
}
|
|
|
|
for (int i = retries; i > 0; --i) {
|
|
|
|
(void) usleep(sleep_dur);
|
|
|
|
if ((err = (*lock_func)(addr, len)) != EAGAIN) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return (err);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-06-25 01:45:44 +00:00
|
|
|
static pw_password_t *
|
|
|
|
alloc_pw_size(size_t len)
|
|
|
|
{
|
|
|
|
pw_password_t *pw = malloc(sizeof (pw_password_t));
|
|
|
|
if (!pw) {
|
|
|
|
return (NULL);
|
|
|
|
}
|
|
|
|
pw->len = len;
|
2021-06-23 23:57:06 +00:00
|
|
|
/*
|
2021-10-21 10:17:47 +00:00
|
|
|
* We use mmap(2) rather than malloc(3) since later on we mlock(2) the
|
|
|
|
* memory region. Since mlock(2) and munlock(2) operate on whole memory
|
|
|
|
* pages we should allocate a whole page here as mmap(2) does. Further
|
|
|
|
* this ensures that the addresses passed to mlock(2) an munlock(2) are
|
|
|
|
* on a page boundary as suggested by FreeBSD and required by some
|
|
|
|
* other implementations. Finally we avoid inadvertently munlocking
|
|
|
|
* memory mlocked by an concurrently running instance of us.
|
2021-06-23 23:57:06 +00:00
|
|
|
*/
|
2021-10-21 10:17:47 +00:00
|
|
|
pw->value = mmap(NULL, pw->len, PROT_READ | PROT_WRITE, MAP_FLAGS,
|
|
|
|
-1, 0);
|
|
|
|
|
|
|
|
if (pw->value == MAP_FAILED) {
|
2020-06-25 01:45:44 +00:00
|
|
|
free(pw);
|
|
|
|
return (NULL);
|
|
|
|
}
|
2021-10-21 04:06:55 +00:00
|
|
|
if (try_lock(mlock, pw->value, pw->len) != 0) {
|
2021-10-21 10:17:47 +00:00
|
|
|
(void) munmap(pw->value, pw->len);
|
2021-10-21 04:06:55 +00:00
|
|
|
free(pw);
|
2021-10-21 10:17:47 +00:00
|
|
|
return (NULL);
|
2021-10-21 04:06:55 +00:00
|
|
|
}
|
2020-06-25 01:45:44 +00:00
|
|
|
return (pw);
|
|
|
|
}
|
|
|
|
|
|
|
|
static pw_password_t *
|
|
|
|
alloc_pw_string(const char *source)
|
|
|
|
{
|
2021-10-21 10:17:47 +00:00
|
|
|
size_t len = strlen(source) + 1;
|
|
|
|
pw_password_t *pw = alloc_pw_size(len);
|
|
|
|
|
2020-06-25 01:45:44 +00:00
|
|
|
if (!pw) {
|
|
|
|
return (NULL);
|
|
|
|
}
|
|
|
|
memcpy(pw->value, source, pw->len);
|
|
|
|
return (pw);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
pw_free(pw_password_t *pw)
|
|
|
|
{
|
2022-02-25 13:26:54 +00:00
|
|
|
memset(pw->value, 0, pw->len);
|
2021-10-21 04:06:55 +00:00
|
|
|
if (try_lock(munlock, pw->value, pw->len) == 0) {
|
2021-10-21 10:17:47 +00:00
|
|
|
(void) munmap(pw->value, pw->len);
|
2021-10-21 04:06:55 +00:00
|
|
|
}
|
2020-06-25 01:45:44 +00:00
|
|
|
free(pw);
|
|
|
|
}
|
|
|
|
|
|
|
|
static pw_password_t *
|
|
|
|
pw_fetch(pam_handle_t *pamh)
|
|
|
|
{
|
|
|
|
const char *token;
|
|
|
|
if (pam_get_authtok(pamh, PAM_AUTHTOK, &token, NULL) != PAM_SUCCESS) {
|
|
|
|
pam_syslog(pamh, LOG_ERR,
|
|
|
|
"couldn't get password from PAM stack");
|
|
|
|
return (NULL);
|
|
|
|
}
|
|
|
|
if (!token) {
|
|
|
|
pam_syslog(pamh, LOG_ERR,
|
|
|
|
"token from PAM stack is null");
|
|
|
|
return (NULL);
|
|
|
|
}
|
|
|
|
return (alloc_pw_string(token));
|
|
|
|
}
|
|
|
|
|
|
|
|
static const pw_password_t *
|
|
|
|
pw_fetch_lazy(pam_handle_t *pamh)
|
|
|
|
{
|
|
|
|
pw_password_t *pw = pw_fetch(pamh);
|
|
|
|
if (pw == NULL) {
|
|
|
|
return (NULL);
|
|
|
|
}
|
|
|
|
int ret = pam_set_data(pamh, PASSWORD_VAR_NAME, pw, destroy_pw);
|
|
|
|
if (ret != PAM_SUCCESS) {
|
|
|
|
pw_free(pw);
|
|
|
|
pam_syslog(pamh, LOG_ERR, "pam_set_data failed");
|
|
|
|
return (NULL);
|
|
|
|
}
|
|
|
|
return (pw);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const pw_password_t *
|
|
|
|
pw_get(pam_handle_t *pamh)
|
|
|
|
{
|
|
|
|
const pw_password_t *authtok = NULL;
|
|
|
|
int ret = pam_get_data(pamh, PASSWORD_VAR_NAME,
|
|
|
|
(const void**)(&authtok));
|
|
|
|
if (ret == PAM_SUCCESS)
|
|
|
|
return (authtok);
|
|
|
|
if (ret == PAM_NO_MODULE_DATA)
|
|
|
|
return (pw_fetch_lazy(pamh));
|
|
|
|
pam_syslog(pamh, LOG_ERR, "password not available");
|
|
|
|
return (NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
pw_clear(pam_handle_t *pamh)
|
|
|
|
{
|
|
|
|
int ret = pam_set_data(pamh, PASSWORD_VAR_NAME, NULL, NULL);
|
|
|
|
if (ret != PAM_SUCCESS) {
|
|
|
|
pam_syslog(pamh, LOG_ERR, "clearing password failed");
|
|
|
|
return (-1);
|
|
|
|
}
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
destroy_pw(pam_handle_t *pamh, void *data, int errcode)
|
|
|
|
{
|
2021-12-11 01:32:43 +00:00
|
|
|
(void) pamh, (void) errcode;
|
|
|
|
|
2020-06-25 01:45:44 +00:00
|
|
|
if (data != NULL) {
|
|
|
|
pw_free((pw_password_t *)data);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
pam_zfs_init(pam_handle_t *pamh)
|
|
|
|
{
|
|
|
|
int error = 0;
|
|
|
|
if ((g_zfs = libzfs_init()) == NULL) {
|
|
|
|
error = errno;
|
|
|
|
pam_syslog(pamh, LOG_ERR, "Zfs initialization error: %s",
|
|
|
|
libzfs_error_init(error));
|
|
|
|
}
|
|
|
|
return (error);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
pam_zfs_free(void)
|
|
|
|
{
|
|
|
|
libzfs_fini(g_zfs);
|
|
|
|
}
|
|
|
|
|
|
|
|
static pw_password_t *
|
|
|
|
prepare_passphrase(pam_handle_t *pamh, zfs_handle_t *ds,
|
|
|
|
const char *passphrase, nvlist_t *nvlist)
|
|
|
|
{
|
|
|
|
pw_password_t *key = alloc_pw_size(WRAPPING_KEY_LEN);
|
|
|
|
if (!key) {
|
|
|
|
return (NULL);
|
|
|
|
}
|
|
|
|
uint64_t salt;
|
|
|
|
uint64_t iters;
|
|
|
|
if (nvlist != NULL) {
|
|
|
|
int fd = open("/dev/urandom", O_RDONLY);
|
|
|
|
if (fd < 0) {
|
|
|
|
pw_free(key);
|
|
|
|
return (NULL);
|
|
|
|
}
|
|
|
|
int bytes_read = 0;
|
|
|
|
char *buf = (char *)&salt;
|
|
|
|
size_t bytes = sizeof (uint64_t);
|
|
|
|
while (bytes_read < bytes) {
|
|
|
|
ssize_t len = read(fd, buf + bytes_read, bytes
|
|
|
|
- bytes_read);
|
|
|
|
if (len < 0) {
|
|
|
|
close(fd);
|
|
|
|
pw_free(key);
|
|
|
|
return (NULL);
|
|
|
|
}
|
|
|
|
bytes_read += len;
|
|
|
|
}
|
|
|
|
close(fd);
|
|
|
|
|
|
|
|
if (nvlist_add_uint64(nvlist,
|
|
|
|
zfs_prop_to_name(ZFS_PROP_PBKDF2_SALT), salt)) {
|
|
|
|
pam_syslog(pamh, LOG_ERR,
|
|
|
|
"failed to add salt to nvlist");
|
|
|
|
pw_free(key);
|
|
|
|
return (NULL);
|
|
|
|
}
|
|
|
|
iters = DEFAULT_PBKDF2_ITERATIONS;
|
|
|
|
if (nvlist_add_uint64(nvlist, zfs_prop_to_name(
|
|
|
|
ZFS_PROP_PBKDF2_ITERS), iters)) {
|
|
|
|
pam_syslog(pamh, LOG_ERR,
|
|
|
|
"failed to add iters to nvlist");
|
|
|
|
pw_free(key);
|
|
|
|
return (NULL);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
salt = zfs_prop_get_int(ds, ZFS_PROP_PBKDF2_SALT);
|
|
|
|
iters = zfs_prop_get_int(ds, ZFS_PROP_PBKDF2_ITERS);
|
|
|
|
}
|
|
|
|
|
|
|
|
salt = LE_64(salt);
|
|
|
|
if (!PKCS5_PBKDF2_HMAC_SHA1((char *)passphrase,
|
|
|
|
strlen(passphrase), (uint8_t *)&salt,
|
|
|
|
sizeof (uint64_t), iters, WRAPPING_KEY_LEN,
|
|
|
|
(uint8_t *)key->value)) {
|
|
|
|
pam_syslog(pamh, LOG_ERR, "pbkdf failed");
|
|
|
|
pw_free(key);
|
|
|
|
return (NULL);
|
|
|
|
}
|
|
|
|
return (key);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
is_key_loaded(pam_handle_t *pamh, const char *ds_name)
|
|
|
|
{
|
|
|
|
zfs_handle_t *ds = zfs_open(g_zfs, ds_name, ZFS_TYPE_FILESYSTEM);
|
|
|
|
if (ds == NULL) {
|
|
|
|
pam_syslog(pamh, LOG_ERR, "dataset %s not found", ds_name);
|
|
|
|
return (-1);
|
|
|
|
}
|
|
|
|
int keystatus = zfs_prop_get_int(ds, ZFS_PROP_KEYSTATUS);
|
|
|
|
zfs_close(ds);
|
|
|
|
return (keystatus != ZFS_KEYSTATUS_UNAVAILABLE);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
change_key(pam_handle_t *pamh, const char *ds_name,
|
|
|
|
const char *passphrase)
|
|
|
|
{
|
|
|
|
zfs_handle_t *ds = zfs_open(g_zfs, ds_name, ZFS_TYPE_FILESYSTEM);
|
|
|
|
if (ds == NULL) {
|
|
|
|
pam_syslog(pamh, LOG_ERR, "dataset %s not found", ds_name);
|
|
|
|
return (-1);
|
|
|
|
}
|
|
|
|
nvlist_t *nvlist = fnvlist_alloc();
|
|
|
|
pw_password_t *key = prepare_passphrase(pamh, ds, passphrase, nvlist);
|
|
|
|
if (key == NULL) {
|
|
|
|
nvlist_free(nvlist);
|
|
|
|
zfs_close(ds);
|
|
|
|
return (-1);
|
|
|
|
}
|
|
|
|
if (nvlist_add_string(nvlist,
|
|
|
|
zfs_prop_to_name(ZFS_PROP_KEYLOCATION),
|
|
|
|
"prompt")) {
|
|
|
|
pam_syslog(pamh, LOG_ERR, "nvlist_add failed for keylocation");
|
|
|
|
pw_free(key);
|
|
|
|
nvlist_free(nvlist);
|
|
|
|
zfs_close(ds);
|
|
|
|
return (-1);
|
|
|
|
}
|
|
|
|
if (nvlist_add_uint64(nvlist,
|
|
|
|
zfs_prop_to_name(ZFS_PROP_KEYFORMAT),
|
|
|
|
ZFS_KEYFORMAT_PASSPHRASE)) {
|
|
|
|
pam_syslog(pamh, LOG_ERR, "nvlist_add failed for keyformat");
|
|
|
|
pw_free(key);
|
|
|
|
nvlist_free(nvlist);
|
|
|
|
zfs_close(ds);
|
|
|
|
return (-1);
|
|
|
|
}
|
|
|
|
int ret = lzc_change_key(ds_name, DCP_CMD_NEW_KEY, nvlist,
|
|
|
|
(uint8_t *)key->value, WRAPPING_KEY_LEN);
|
|
|
|
pw_free(key);
|
|
|
|
if (ret) {
|
|
|
|
pam_syslog(pamh, LOG_ERR, "change_key failed: %d", ret);
|
|
|
|
nvlist_free(nvlist);
|
|
|
|
zfs_close(ds);
|
|
|
|
return (-1);
|
|
|
|
}
|
|
|
|
nvlist_free(nvlist);
|
|
|
|
zfs_close(ds);
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
decrypt_mount(pam_handle_t *pamh, const char *ds_name,
|
|
|
|
const char *passphrase)
|
|
|
|
{
|
|
|
|
zfs_handle_t *ds = zfs_open(g_zfs, ds_name, ZFS_TYPE_FILESYSTEM);
|
|
|
|
if (ds == NULL) {
|
|
|
|
pam_syslog(pamh, LOG_ERR, "dataset %s not found", ds_name);
|
|
|
|
return (-1);
|
|
|
|
}
|
|
|
|
pw_password_t *key = prepare_passphrase(pamh, ds, passphrase, NULL);
|
|
|
|
if (key == NULL) {
|
|
|
|
zfs_close(ds);
|
|
|
|
return (-1);
|
|
|
|
}
|
|
|
|
int ret = lzc_load_key(ds_name, B_FALSE, (uint8_t *)key->value,
|
|
|
|
WRAPPING_KEY_LEN);
|
|
|
|
pw_free(key);
|
|
|
|
if (ret) {
|
|
|
|
pam_syslog(pamh, LOG_ERR, "load_key failed: %d", ret);
|
|
|
|
zfs_close(ds);
|
|
|
|
return (-1);
|
|
|
|
}
|
|
|
|
ret = zfs_mount(ds, NULL, 0);
|
|
|
|
if (ret) {
|
|
|
|
pam_syslog(pamh, LOG_ERR, "mount failed: %d", ret);
|
|
|
|
zfs_close(ds);
|
|
|
|
return (-1);
|
|
|
|
}
|
|
|
|
zfs_close(ds);
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
unmount_unload(pam_handle_t *pamh, const char *ds_name)
|
|
|
|
{
|
|
|
|
zfs_handle_t *ds = zfs_open(g_zfs, ds_name, ZFS_TYPE_FILESYSTEM);
|
|
|
|
if (ds == NULL) {
|
|
|
|
pam_syslog(pamh, LOG_ERR, "dataset %s not found", ds_name);
|
|
|
|
return (-1);
|
|
|
|
}
|
|
|
|
int ret = zfs_unmount(ds, NULL, 0);
|
|
|
|
if (ret) {
|
|
|
|
pam_syslog(pamh, LOG_ERR, "zfs_unmount failed with: %d", ret);
|
|
|
|
zfs_close(ds);
|
|
|
|
return (-1);
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = lzc_unload_key(ds_name);
|
|
|
|
if (ret) {
|
|
|
|
pam_syslog(pamh, LOG_ERR, "unload_key failed with: %d", ret);
|
|
|
|
zfs_close(ds);
|
|
|
|
return (-1);
|
|
|
|
}
|
|
|
|
zfs_close(ds);
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
char *homes_prefix;
|
|
|
|
char *runstatedir;
|
2020-11-22 17:32:34 +00:00
|
|
|
char *homedir;
|
|
|
|
char *dsname;
|
2020-06-25 01:45:44 +00:00
|
|
|
uid_t uid;
|
|
|
|
const char *username;
|
|
|
|
int unmount_and_unload;
|
|
|
|
} zfs_key_config_t;
|
|
|
|
|
|
|
|
static int
|
|
|
|
zfs_key_config_load(pam_handle_t *pamh, zfs_key_config_t *config,
|
|
|
|
int argc, const char **argv)
|
|
|
|
{
|
|
|
|
config->homes_prefix = strdup("rpool/home");
|
|
|
|
if (config->homes_prefix == NULL) {
|
|
|
|
pam_syslog(pamh, LOG_ERR, "strdup failure");
|
|
|
|
return (-1);
|
|
|
|
}
|
|
|
|
config->runstatedir = strdup(RUNSTATEDIR "/pam_zfs_key");
|
|
|
|
if (config->runstatedir == NULL) {
|
|
|
|
pam_syslog(pamh, LOG_ERR, "strdup failure");
|
|
|
|
free(config->homes_prefix);
|
|
|
|
return (-1);
|
|
|
|
}
|
|
|
|
const char *name;
|
|
|
|
if (pam_get_user(pamh, &name, NULL) != PAM_SUCCESS) {
|
|
|
|
pam_syslog(pamh, LOG_ERR,
|
|
|
|
"couldn't get username from PAM stack");
|
|
|
|
free(config->runstatedir);
|
|
|
|
free(config->homes_prefix);
|
|
|
|
return (-1);
|
|
|
|
}
|
|
|
|
struct passwd *entry = getpwnam(name);
|
|
|
|
if (!entry) {
|
|
|
|
free(config->runstatedir);
|
|
|
|
free(config->homes_prefix);
|
|
|
|
return (-1);
|
|
|
|
}
|
|
|
|
config->uid = entry->pw_uid;
|
|
|
|
config->username = name;
|
|
|
|
config->unmount_and_unload = 1;
|
2020-11-22 17:32:34 +00:00
|
|
|
config->dsname = NULL;
|
|
|
|
config->homedir = NULL;
|
2020-06-25 01:45:44 +00:00
|
|
|
for (int c = 0; c < argc; c++) {
|
|
|
|
if (strncmp(argv[c], "homes=", 6) == 0) {
|
|
|
|
free(config->homes_prefix);
|
|
|
|
config->homes_prefix = strdup(argv[c] + 6);
|
|
|
|
} else if (strncmp(argv[c], "runstatedir=", 12) == 0) {
|
|
|
|
free(config->runstatedir);
|
|
|
|
config->runstatedir = strdup(argv[c] + 12);
|
|
|
|
} else if (strcmp(argv[c], "nounmount") == 0) {
|
|
|
|
config->unmount_and_unload = 0;
|
2020-11-22 17:32:34 +00:00
|
|
|
} else if (strcmp(argv[c], "prop_mountpoint") == 0) {
|
2022-09-27 00:18:05 +00:00
|
|
|
if (config->homedir == NULL)
|
|
|
|
config->homedir = strdup(entry->pw_dir);
|
2020-06-25 01:45:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
zfs_key_config_free(zfs_key_config_t *config)
|
|
|
|
{
|
|
|
|
free(config->homes_prefix);
|
2020-11-22 17:32:34 +00:00
|
|
|
free(config->runstatedir);
|
|
|
|
free(config->homedir);
|
|
|
|
free(config->dsname);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
find_dsname_by_prop_value(zfs_handle_t *zhp, void *data)
|
|
|
|
{
|
|
|
|
zfs_type_t type = zfs_get_type(zhp);
|
|
|
|
zfs_key_config_t *target = data;
|
|
|
|
char mountpoint[ZFS_MAXPROPLEN];
|
|
|
|
|
|
|
|
/* Skip any datasets whose type does not match */
|
|
|
|
if ((type & ZFS_TYPE_FILESYSTEM) == 0) {
|
|
|
|
zfs_close(zhp);
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Skip any datasets whose mountpoint does not match */
|
|
|
|
(void) zfs_prop_get(zhp, ZFS_PROP_MOUNTPOINT, mountpoint,
|
|
|
|
sizeof (mountpoint), NULL, NULL, 0, B_FALSE);
|
|
|
|
if (strcmp(target->homedir, mountpoint) != 0) {
|
|
|
|
zfs_close(zhp);
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
target->dsname = strdup(zfs_get_name(zhp));
|
|
|
|
zfs_close(zhp);
|
|
|
|
return (1);
|
2020-06-25 01:45:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static char *
|
|
|
|
zfs_key_config_get_dataset(zfs_key_config_t *config)
|
|
|
|
{
|
2020-11-22 17:32:34 +00:00
|
|
|
if (config->homedir != NULL &&
|
|
|
|
config->homes_prefix != NULL) {
|
|
|
|
zfs_handle_t *zhp = zfs_open(g_zfs, config->homes_prefix,
|
|
|
|
ZFS_TYPE_FILESYSTEM);
|
|
|
|
if (zhp == NULL) {
|
|
|
|
pam_syslog(NULL, LOG_ERR, "dataset %s not found",
|
|
|
|
config->homes_prefix);
|
|
|
|
return (NULL);
|
|
|
|
}
|
|
|
|
|
2022-01-06 19:12:53 +00:00
|
|
|
(void) zfs_iter_filesystems(zhp, find_dsname_by_prop_value,
|
2020-11-22 17:32:34 +00:00
|
|
|
config);
|
|
|
|
zfs_close(zhp);
|
|
|
|
char *dsname = config->dsname;
|
|
|
|
config->dsname = NULL;
|
|
|
|
return (dsname);
|
|
|
|
}
|
|
|
|
|
2022-09-16 21:02:54 +00:00
|
|
|
if (config->homes_prefix == NULL) {
|
|
|
|
return (NULL);
|
|
|
|
}
|
|
|
|
|
2020-06-25 01:45:44 +00:00
|
|
|
size_t len = ZFS_MAX_DATASET_NAME_LEN;
|
|
|
|
size_t total_len = strlen(config->homes_prefix) + 1
|
|
|
|
+ strlen(config->username);
|
|
|
|
if (total_len > len) {
|
|
|
|
return (NULL);
|
|
|
|
}
|
|
|
|
char *ret = malloc(len + 1);
|
|
|
|
if (!ret) {
|
|
|
|
return (NULL);
|
|
|
|
}
|
|
|
|
ret[0] = 0;
|
Fix unsafe string operations
Coverity caught unsafe use of `strcpy()` in `ztest_dmu_objset_own()`,
`nfs_init_tmpfile()` and `dump_snapshot()`. It also caught an unsafe use
of `strlcat()` in `nfs_init_tmpfile()`.
Inspired by this, I did an audit of every single usage of `strcpy()` and
`strcat()` in the code. If I could not prove that the usage was safe, I
changed the code to use either `strlcpy()` or `strlcat()`, depending on
which function was originally used. In some cases, `snprintf()` was used
to replace multiple uses of `strcat` because it was cleaner.
Whenever I changed a function, I preferred to use `sizeof(dst)` when the
compiler is able to provide the string size via that. When it could not
because the string was passed by a caller, I checked the entire call
tree of the function to find out how big the buffer was and hard coded
it. Hardcoding is less than ideal, but it is safe unless someone shrinks
the buffer sizes being passed.
Additionally, Coverity reported three more string related issues:
* It caught a case where we do an overlapping memory copy in a call to
`snprintf()`. We fix that via `kmem_strdup()` and `kmem_strfree()`.
* It caught `sizeof (buf)` being used instead of `buflen` in
`zdb_nicenum()`'s call to `zfs_nicenum()`, which is passed to
`snprintf()`. We change that to pass `buflen`.
* It caught a theoretical unterminated string passed to `strcmp()`.
This one is likely a false positive, but we have the information
needed to do this more safely, so we change this to silence the false
positive not just in coverity, but potentially other static analysis
tools too. We switch to `strncmp()`.
* There was a false positive in tests/zfs-tests/cmd/dir_rd_update.c. We
suppress it by switching to `snprintf()` since other static analysis
tools might complain about it too. Interestingly, there is a possible
real bug there too, since it assumes that the passed directory path
ends with '/'. We add a '/' to fix that potential bug.
Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Signed-off-by: Richard Yao <richard.yao@alumni.stonybrook.edu>
Closes #13913
2022-09-27 23:47:24 +00:00
|
|
|
(void) snprintf(ret, len + 1, "%s/%s", config->homes_prefix,
|
|
|
|
config->username);
|
2020-06-25 01:45:44 +00:00
|
|
|
return (ret);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
zfs_key_config_modify_session_counter(pam_handle_t *pamh,
|
|
|
|
zfs_key_config_t *config, int delta)
|
|
|
|
{
|
|
|
|
const char *runtime_path = config->runstatedir;
|
|
|
|
if (mkdir(runtime_path, S_IRWXU) != 0 && errno != EEXIST) {
|
|
|
|
pam_syslog(pamh, LOG_ERR, "Can't create runtime path: %d",
|
|
|
|
errno);
|
|
|
|
return (-1);
|
|
|
|
}
|
|
|
|
if (chown(runtime_path, 0, 0) != 0) {
|
|
|
|
pam_syslog(pamh, LOG_ERR, "Can't chown runtime path: %d",
|
|
|
|
errno);
|
|
|
|
return (-1);
|
|
|
|
}
|
|
|
|
if (chmod(runtime_path, S_IRWXU) != 0) {
|
|
|
|
pam_syslog(pamh, LOG_ERR, "Can't chmod runtime path: %d",
|
|
|
|
errno);
|
|
|
|
return (-1);
|
|
|
|
}
|
|
|
|
size_t runtime_path_len = strlen(runtime_path);
|
|
|
|
size_t counter_path_len = runtime_path_len + 1 + 10;
|
|
|
|
char *counter_path = malloc(counter_path_len + 1);
|
|
|
|
if (!counter_path) {
|
|
|
|
return (-1);
|
|
|
|
}
|
|
|
|
counter_path[0] = 0;
|
|
|
|
strcat(counter_path, runtime_path);
|
|
|
|
snprintf(counter_path + runtime_path_len, counter_path_len, "/%d",
|
|
|
|
config->uid);
|
|
|
|
const int fd = open(counter_path,
|
|
|
|
O_RDWR | O_CLOEXEC | O_CREAT | O_NOFOLLOW,
|
|
|
|
S_IRUSR | S_IWUSR);
|
|
|
|
free(counter_path);
|
|
|
|
if (fd < 0) {
|
|
|
|
pam_syslog(pamh, LOG_ERR, "Can't open counter file: %d", errno);
|
|
|
|
return (-1);
|
|
|
|
}
|
|
|
|
if (flock(fd, LOCK_EX) != 0) {
|
|
|
|
pam_syslog(pamh, LOG_ERR, "Can't lock counter file: %d", errno);
|
|
|
|
close(fd);
|
|
|
|
return (-1);
|
|
|
|
}
|
|
|
|
char counter[20];
|
|
|
|
char *pos = counter;
|
|
|
|
int remaining = sizeof (counter) - 1;
|
|
|
|
int ret;
|
|
|
|
counter[sizeof (counter) - 1] = 0;
|
|
|
|
while (remaining > 0 && (ret = read(fd, pos, remaining)) > 0) {
|
|
|
|
remaining -= ret;
|
|
|
|
pos += ret;
|
|
|
|
}
|
|
|
|
*pos = 0;
|
|
|
|
long int counter_value = strtol(counter, NULL, 10);
|
|
|
|
counter_value += delta;
|
|
|
|
if (counter_value < 0) {
|
|
|
|
counter_value = 0;
|
|
|
|
}
|
|
|
|
lseek(fd, 0, SEEK_SET);
|
|
|
|
if (ftruncate(fd, 0) != 0) {
|
|
|
|
pam_syslog(pamh, LOG_ERR, "Can't truncate counter file: %d",
|
|
|
|
errno);
|
|
|
|
close(fd);
|
|
|
|
return (-1);
|
|
|
|
}
|
|
|
|
snprintf(counter, sizeof (counter), "%ld", counter_value);
|
|
|
|
remaining = strlen(counter);
|
|
|
|
pos = counter;
|
|
|
|
while (remaining > 0 && (ret = write(fd, pos, remaining)) > 0) {
|
|
|
|
remaining -= ret;
|
|
|
|
pos += ret;
|
|
|
|
}
|
|
|
|
close(fd);
|
|
|
|
return (counter_value);
|
|
|
|
}
|
|
|
|
|
|
|
|
__attribute__((visibility("default")))
|
|
|
|
PAM_EXTERN int
|
|
|
|
pam_sm_authenticate(pam_handle_t *pamh, int flags,
|
|
|
|
int argc, const char **argv)
|
|
|
|
{
|
2021-12-11 01:32:43 +00:00
|
|
|
(void) flags, (void) argc, (void) argv;
|
|
|
|
|
2020-06-25 01:45:44 +00:00
|
|
|
if (pw_fetch_lazy(pamh) == NULL) {
|
|
|
|
return (PAM_AUTH_ERR);
|
|
|
|
}
|
|
|
|
|
|
|
|
return (PAM_SUCCESS);
|
|
|
|
}
|
|
|
|
|
|
|
|
__attribute__((visibility("default")))
|
|
|
|
PAM_EXTERN int
|
|
|
|
pam_sm_setcred(pam_handle_t *pamh, int flags,
|
|
|
|
int argc, const char **argv)
|
|
|
|
{
|
2021-12-11 01:32:43 +00:00
|
|
|
(void) pamh, (void) flags, (void) argc, (void) argv;
|
2020-06-25 01:45:44 +00:00
|
|
|
return (PAM_SUCCESS);
|
|
|
|
}
|
|
|
|
|
|
|
|
__attribute__((visibility("default")))
|
|
|
|
PAM_EXTERN int
|
|
|
|
pam_sm_chauthtok(pam_handle_t *pamh, int flags,
|
|
|
|
int argc, const char **argv)
|
|
|
|
{
|
|
|
|
if (geteuid() != 0) {
|
|
|
|
pam_syslog(pamh, LOG_ERR,
|
|
|
|
"Cannot zfs_mount when not being root.");
|
|
|
|
return (PAM_PERM_DENIED);
|
|
|
|
}
|
|
|
|
zfs_key_config_t config;
|
|
|
|
if (zfs_key_config_load(pamh, &config, argc, argv) == -1) {
|
|
|
|
return (PAM_SERVICE_ERR);
|
|
|
|
}
|
|
|
|
if (config.uid < 1000) {
|
|
|
|
zfs_key_config_free(&config);
|
|
|
|
return (PAM_SUCCESS);
|
|
|
|
}
|
|
|
|
{
|
|
|
|
if (pam_zfs_init(pamh) != 0) {
|
|
|
|
zfs_key_config_free(&config);
|
|
|
|
return (PAM_SERVICE_ERR);
|
|
|
|
}
|
|
|
|
char *dataset = zfs_key_config_get_dataset(&config);
|
|
|
|
if (!dataset) {
|
|
|
|
pam_zfs_free();
|
|
|
|
zfs_key_config_free(&config);
|
|
|
|
return (PAM_SERVICE_ERR);
|
|
|
|
}
|
|
|
|
int key_loaded = is_key_loaded(pamh, dataset);
|
|
|
|
if (key_loaded == -1) {
|
|
|
|
free(dataset);
|
|
|
|
pam_zfs_free();
|
|
|
|
zfs_key_config_free(&config);
|
|
|
|
return (PAM_SERVICE_ERR);
|
|
|
|
}
|
|
|
|
free(dataset);
|
|
|
|
pam_zfs_free();
|
|
|
|
if (! key_loaded) {
|
|
|
|
pam_syslog(pamh, LOG_ERR,
|
|
|
|
"key not loaded, returning try_again");
|
|
|
|
zfs_key_config_free(&config);
|
|
|
|
return (PAM_PERM_DENIED);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((flags & PAM_UPDATE_AUTHTOK) != 0) {
|
|
|
|
const pw_password_t *token = pw_get(pamh);
|
|
|
|
if (token == NULL) {
|
|
|
|
zfs_key_config_free(&config);
|
|
|
|
return (PAM_SERVICE_ERR);
|
|
|
|
}
|
|
|
|
if (pam_zfs_init(pamh) != 0) {
|
|
|
|
zfs_key_config_free(&config);
|
|
|
|
return (PAM_SERVICE_ERR);
|
|
|
|
}
|
|
|
|
char *dataset = zfs_key_config_get_dataset(&config);
|
|
|
|
if (!dataset) {
|
|
|
|
pam_zfs_free();
|
|
|
|
zfs_key_config_free(&config);
|
|
|
|
return (PAM_SERVICE_ERR);
|
|
|
|
}
|
|
|
|
if (change_key(pamh, dataset, token->value) == -1) {
|
|
|
|
free(dataset);
|
|
|
|
pam_zfs_free();
|
|
|
|
zfs_key_config_free(&config);
|
|
|
|
return (PAM_SERVICE_ERR);
|
|
|
|
}
|
|
|
|
free(dataset);
|
|
|
|
pam_zfs_free();
|
|
|
|
zfs_key_config_free(&config);
|
|
|
|
if (pw_clear(pamh) == -1) {
|
|
|
|
return (PAM_SERVICE_ERR);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
zfs_key_config_free(&config);
|
|
|
|
}
|
|
|
|
return (PAM_SUCCESS);
|
|
|
|
}
|
|
|
|
|
|
|
|
PAM_EXTERN int
|
|
|
|
pam_sm_open_session(pam_handle_t *pamh, int flags,
|
|
|
|
int argc, const char **argv)
|
|
|
|
{
|
2021-12-11 01:32:43 +00:00
|
|
|
(void) flags;
|
|
|
|
|
2020-06-25 01:45:44 +00:00
|
|
|
if (geteuid() != 0) {
|
|
|
|
pam_syslog(pamh, LOG_ERR,
|
|
|
|
"Cannot zfs_mount when not being root.");
|
|
|
|
return (PAM_SUCCESS);
|
|
|
|
}
|
|
|
|
zfs_key_config_t config;
|
2022-10-06 00:09:24 +00:00
|
|
|
if (zfs_key_config_load(pamh, &config, argc, argv) != 0) {
|
|
|
|
return (PAM_SESSION_ERR);
|
|
|
|
}
|
|
|
|
|
2020-06-25 01:45:44 +00:00
|
|
|
if (config.uid < 1000) {
|
|
|
|
zfs_key_config_free(&config);
|
|
|
|
return (PAM_SUCCESS);
|
|
|
|
}
|
|
|
|
|
|
|
|
int counter = zfs_key_config_modify_session_counter(pamh, &config, 1);
|
|
|
|
if (counter != 1) {
|
|
|
|
zfs_key_config_free(&config);
|
|
|
|
return (PAM_SUCCESS);
|
|
|
|
}
|
|
|
|
|
|
|
|
const pw_password_t *token = pw_get(pamh);
|
|
|
|
if (token == NULL) {
|
|
|
|
zfs_key_config_free(&config);
|
|
|
|
return (PAM_SESSION_ERR);
|
|
|
|
}
|
|
|
|
if (pam_zfs_init(pamh) != 0) {
|
|
|
|
zfs_key_config_free(&config);
|
|
|
|
return (PAM_SERVICE_ERR);
|
|
|
|
}
|
|
|
|
char *dataset = zfs_key_config_get_dataset(&config);
|
|
|
|
if (!dataset) {
|
|
|
|
pam_zfs_free();
|
|
|
|
zfs_key_config_free(&config);
|
|
|
|
return (PAM_SERVICE_ERR);
|
|
|
|
}
|
|
|
|
if (decrypt_mount(pamh, dataset, token->value) == -1) {
|
|
|
|
free(dataset);
|
|
|
|
pam_zfs_free();
|
|
|
|
zfs_key_config_free(&config);
|
|
|
|
return (PAM_SERVICE_ERR);
|
|
|
|
}
|
|
|
|
free(dataset);
|
|
|
|
pam_zfs_free();
|
|
|
|
zfs_key_config_free(&config);
|
|
|
|
if (pw_clear(pamh) == -1) {
|
|
|
|
return (PAM_SERVICE_ERR);
|
|
|
|
}
|
|
|
|
return (PAM_SUCCESS);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
__attribute__((visibility("default")))
|
|
|
|
PAM_EXTERN int
|
|
|
|
pam_sm_close_session(pam_handle_t *pamh, int flags,
|
|
|
|
int argc, const char **argv)
|
|
|
|
{
|
2021-12-11 01:32:43 +00:00
|
|
|
(void) flags;
|
|
|
|
|
2020-06-25 01:45:44 +00:00
|
|
|
if (geteuid() != 0) {
|
|
|
|
pam_syslog(pamh, LOG_ERR,
|
|
|
|
"Cannot zfs_mount when not being root.");
|
|
|
|
return (PAM_SUCCESS);
|
|
|
|
}
|
|
|
|
zfs_key_config_t config;
|
2022-09-27 23:48:35 +00:00
|
|
|
if (zfs_key_config_load(pamh, &config, argc, argv) != 0) {
|
|
|
|
return (PAM_SESSION_ERR);
|
|
|
|
}
|
2020-06-25 01:45:44 +00:00
|
|
|
if (config.uid < 1000) {
|
|
|
|
zfs_key_config_free(&config);
|
|
|
|
return (PAM_SUCCESS);
|
|
|
|
}
|
|
|
|
|
|
|
|
int counter = zfs_key_config_modify_session_counter(pamh, &config, -1);
|
|
|
|
if (counter != 0) {
|
|
|
|
zfs_key_config_free(&config);
|
|
|
|
return (PAM_SUCCESS);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (config.unmount_and_unload) {
|
|
|
|
if (pam_zfs_init(pamh) != 0) {
|
|
|
|
zfs_key_config_free(&config);
|
|
|
|
return (PAM_SERVICE_ERR);
|
|
|
|
}
|
|
|
|
char *dataset = zfs_key_config_get_dataset(&config);
|
|
|
|
if (!dataset) {
|
|
|
|
pam_zfs_free();
|
|
|
|
zfs_key_config_free(&config);
|
|
|
|
return (PAM_SESSION_ERR);
|
|
|
|
}
|
|
|
|
if (unmount_unload(pamh, dataset) == -1) {
|
|
|
|
free(dataset);
|
|
|
|
pam_zfs_free();
|
|
|
|
zfs_key_config_free(&config);
|
|
|
|
return (PAM_SESSION_ERR);
|
|
|
|
}
|
|
|
|
free(dataset);
|
|
|
|
pam_zfs_free();
|
|
|
|
}
|
|
|
|
|
|
|
|
zfs_key_config_free(&config);
|
|
|
|
return (PAM_SUCCESS);
|
|
|
|
}
|