2 * Redistribution and use in source and binary forms, with or without
3 * modification, are permitted provided that the following conditions are met:
4 * * Redistributions of source code must retain the above copyright
5 * notice, this list of conditions and the following disclaimer.
6 * * Redistributions in binary form must reproduce the above copyright
7 * notice, this list of conditions and the following disclaimer in the
8 * documentation and/or other materials provided with the distribution.
9 * * Neither the name of the <organization> nor the
10 * names of its contributors may be used to endorse or promote products
11 * derived from this software without specific prior written permission.
13 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16 * ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
17 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 * Copyright (c) 2020, Felix Dörre
25 * All rights reserved.
28 #include <sys/dsl_crypt.h>
29 #include <sys/byteorder.h>
34 #include <sys/zio_crypt.h>
35 #include <openssl/evp.h>
38 #define PAM_SM_PASSWORD
39 #define PAM_SM_SESSION
40 #include <security/pam_modules.h>
42 #if defined(__linux__)
43 #include <security/pam_ext.h>
44 #elif defined(__FreeBSD__)
45 #include <security/pam_appl.h>
47 pam_syslog(pam_handle_t
*pamh
, int loglevel
, const char *fmt
, ...)
51 vsyslog(loglevel
, fmt
, args
);
66 static const char PASSWORD_VAR_NAME
[] = "pam_zfs_key_authtok";
68 static libzfs_handle_t
*g_zfs
;
70 static void destroy_pw(pam_handle_t
*pamh
, void *data
, int errcode
);
77 static pw_password_t
*
78 alloc_pw_size(size_t len
)
80 pw_password_t
*pw
= malloc(sizeof (pw_password_t
));
86 * The use of malloc() triggers a spurious gcc 11 -Wmaybe-uninitialized
87 * warning in the mlock() function call below, so use calloc().
89 pw
->value
= calloc(len
, 1);
94 mlock(pw
->value
, pw
->len
);
98 static pw_password_t
*
99 alloc_pw_string(const char *source
)
101 pw_password_t
*pw
= malloc(sizeof (pw_password_t
));
105 pw
->len
= strlen(source
) + 1;
107 * The use of malloc() triggers a spurious gcc 11 -Wmaybe-uninitialized
108 * warning in the mlock() function call below, so use calloc().
110 pw
->value
= calloc(pw
->len
, 1);
115 mlock(pw
->value
, pw
->len
);
116 memcpy(pw
->value
, source
, pw
->len
);
121 pw_free(pw_password_t
*pw
)
123 bzero(pw
->value
, pw
->len
);
124 munlock(pw
->value
, pw
->len
);
129 static pw_password_t
*
130 pw_fetch(pam_handle_t
*pamh
)
133 if (pam_get_authtok(pamh
, PAM_AUTHTOK
, &token
, NULL
) != PAM_SUCCESS
) {
134 pam_syslog(pamh
, LOG_ERR
,
135 "couldn't get password from PAM stack");
139 pam_syslog(pamh
, LOG_ERR
,
140 "token from PAM stack is null");
143 return (alloc_pw_string(token
));
146 static const pw_password_t
*
147 pw_fetch_lazy(pam_handle_t
*pamh
)
149 pw_password_t
*pw
= pw_fetch(pamh
);
153 int ret
= pam_set_data(pamh
, PASSWORD_VAR_NAME
, pw
, destroy_pw
);
154 if (ret
!= PAM_SUCCESS
) {
156 pam_syslog(pamh
, LOG_ERR
, "pam_set_data failed");
162 static const pw_password_t
*
163 pw_get(pam_handle_t
*pamh
)
165 const pw_password_t
*authtok
= NULL
;
166 int ret
= pam_get_data(pamh
, PASSWORD_VAR_NAME
,
167 (const void**)(&authtok
));
168 if (ret
== PAM_SUCCESS
)
170 if (ret
== PAM_NO_MODULE_DATA
)
171 return (pw_fetch_lazy(pamh
));
172 pam_syslog(pamh
, LOG_ERR
, "password not available");
177 pw_clear(pam_handle_t
*pamh
)
179 int ret
= pam_set_data(pamh
, PASSWORD_VAR_NAME
, NULL
, NULL
);
180 if (ret
!= PAM_SUCCESS
) {
181 pam_syslog(pamh
, LOG_ERR
, "clearing password failed");
188 destroy_pw(pam_handle_t
*pamh
, void *data
, int errcode
)
191 pw_free((pw_password_t
*)data
);
196 pam_zfs_init(pam_handle_t
*pamh
)
199 if ((g_zfs
= libzfs_init()) == NULL
) {
201 pam_syslog(pamh
, LOG_ERR
, "Zfs initialization error: %s",
202 libzfs_error_init(error
));
213 static pw_password_t
*
214 prepare_passphrase(pam_handle_t
*pamh
, zfs_handle_t
*ds
,
215 const char *passphrase
, nvlist_t
*nvlist
)
217 pw_password_t
*key
= alloc_pw_size(WRAPPING_KEY_LEN
);
223 if (nvlist
!= NULL
) {
224 int fd
= open("/dev/urandom", O_RDONLY
);
230 char *buf
= (char *)&salt
;
231 size_t bytes
= sizeof (uint64_t);
232 while (bytes_read
< bytes
) {
233 ssize_t len
= read(fd
, buf
+ bytes_read
, bytes
244 if (nvlist_add_uint64(nvlist
,
245 zfs_prop_to_name(ZFS_PROP_PBKDF2_SALT
), salt
)) {
246 pam_syslog(pamh
, LOG_ERR
,
247 "failed to add salt to nvlist");
251 iters
= DEFAULT_PBKDF2_ITERATIONS
;
252 if (nvlist_add_uint64(nvlist
, zfs_prop_to_name(
253 ZFS_PROP_PBKDF2_ITERS
), iters
)) {
254 pam_syslog(pamh
, LOG_ERR
,
255 "failed to add iters to nvlist");
260 salt
= zfs_prop_get_int(ds
, ZFS_PROP_PBKDF2_SALT
);
261 iters
= zfs_prop_get_int(ds
, ZFS_PROP_PBKDF2_ITERS
);
265 if (!PKCS5_PBKDF2_HMAC_SHA1((char *)passphrase
,
266 strlen(passphrase
), (uint8_t *)&salt
,
267 sizeof (uint64_t), iters
, WRAPPING_KEY_LEN
,
268 (uint8_t *)key
->value
)) {
269 pam_syslog(pamh
, LOG_ERR
, "pbkdf failed");
277 is_key_loaded(pam_handle_t
*pamh
, const char *ds_name
)
279 zfs_handle_t
*ds
= zfs_open(g_zfs
, ds_name
, ZFS_TYPE_FILESYSTEM
);
281 pam_syslog(pamh
, LOG_ERR
, "dataset %s not found", ds_name
);
284 int keystatus
= zfs_prop_get_int(ds
, ZFS_PROP_KEYSTATUS
);
286 return (keystatus
!= ZFS_KEYSTATUS_UNAVAILABLE
);
290 change_key(pam_handle_t
*pamh
, const char *ds_name
,
291 const char *passphrase
)
293 zfs_handle_t
*ds
= zfs_open(g_zfs
, ds_name
, ZFS_TYPE_FILESYSTEM
);
295 pam_syslog(pamh
, LOG_ERR
, "dataset %s not found", ds_name
);
298 nvlist_t
*nvlist
= fnvlist_alloc();
299 pw_password_t
*key
= prepare_passphrase(pamh
, ds
, passphrase
, nvlist
);
305 if (nvlist_add_string(nvlist
,
306 zfs_prop_to_name(ZFS_PROP_KEYLOCATION
),
308 pam_syslog(pamh
, LOG_ERR
, "nvlist_add failed for keylocation");
314 if (nvlist_add_uint64(nvlist
,
315 zfs_prop_to_name(ZFS_PROP_KEYFORMAT
),
316 ZFS_KEYFORMAT_PASSPHRASE
)) {
317 pam_syslog(pamh
, LOG_ERR
, "nvlist_add failed for keyformat");
323 int ret
= lzc_change_key(ds_name
, DCP_CMD_NEW_KEY
, nvlist
,
324 (uint8_t *)key
->value
, WRAPPING_KEY_LEN
);
327 pam_syslog(pamh
, LOG_ERR
, "change_key failed: %d", ret
);
338 decrypt_mount(pam_handle_t
*pamh
, const char *ds_name
,
339 const char *passphrase
)
341 zfs_handle_t
*ds
= zfs_open(g_zfs
, ds_name
, ZFS_TYPE_FILESYSTEM
);
343 pam_syslog(pamh
, LOG_ERR
, "dataset %s not found", ds_name
);
346 pw_password_t
*key
= prepare_passphrase(pamh
, ds
, passphrase
, NULL
);
351 int ret
= lzc_load_key(ds_name
, B_FALSE
, (uint8_t *)key
->value
,
355 pam_syslog(pamh
, LOG_ERR
, "load_key failed: %d", ret
);
359 ret
= zfs_mount(ds
, NULL
, 0);
361 pam_syslog(pamh
, LOG_ERR
, "mount failed: %d", ret
);
370 unmount_unload(pam_handle_t
*pamh
, const char *ds_name
)
372 zfs_handle_t
*ds
= zfs_open(g_zfs
, ds_name
, ZFS_TYPE_FILESYSTEM
);
374 pam_syslog(pamh
, LOG_ERR
, "dataset %s not found", ds_name
);
377 int ret
= zfs_unmount(ds
, NULL
, 0);
379 pam_syslog(pamh
, LOG_ERR
, "zfs_unmount failed with: %d", ret
);
384 ret
= lzc_unload_key(ds_name
);
386 pam_syslog(pamh
, LOG_ERR
, "unload_key failed with: %d", ret
);
400 const char *username
;
401 int unmount_and_unload
;
405 zfs_key_config_load(pam_handle_t
*pamh
, zfs_key_config_t
*config
,
406 int argc
, const char **argv
)
408 config
->homes_prefix
= strdup("rpool/home");
409 if (config
->homes_prefix
== NULL
) {
410 pam_syslog(pamh
, LOG_ERR
, "strdup failure");
413 config
->runstatedir
= strdup(RUNSTATEDIR
"/pam_zfs_key");
414 if (config
->runstatedir
== NULL
) {
415 pam_syslog(pamh
, LOG_ERR
, "strdup failure");
416 free(config
->homes_prefix
);
420 if (pam_get_user(pamh
, &name
, NULL
) != PAM_SUCCESS
) {
421 pam_syslog(pamh
, LOG_ERR
,
422 "couldn't get username from PAM stack");
423 free(config
->runstatedir
);
424 free(config
->homes_prefix
);
427 struct passwd
*entry
= getpwnam(name
);
429 free(config
->runstatedir
);
430 free(config
->homes_prefix
);
433 config
->uid
= entry
->pw_uid
;
434 config
->username
= name
;
435 config
->unmount_and_unload
= 1;
436 config
->dsname
= NULL
;
437 config
->homedir
= NULL
;
438 for (int c
= 0; c
< argc
; c
++) {
439 if (strncmp(argv
[c
], "homes=", 6) == 0) {
440 free(config
->homes_prefix
);
441 config
->homes_prefix
= strdup(argv
[c
] + 6);
442 } else if (strncmp(argv
[c
], "runstatedir=", 12) == 0) {
443 free(config
->runstatedir
);
444 config
->runstatedir
= strdup(argv
[c
] + 12);
445 } else if (strcmp(argv
[c
], "nounmount") == 0) {
446 config
->unmount_and_unload
= 0;
447 } else if (strcmp(argv
[c
], "prop_mountpoint") == 0) {
448 config
->homedir
= strdup(entry
->pw_dir
);
455 zfs_key_config_free(zfs_key_config_t
*config
)
457 free(config
->homes_prefix
);
458 free(config
->runstatedir
);
459 free(config
->homedir
);
460 free(config
->dsname
);
464 find_dsname_by_prop_value(zfs_handle_t
*zhp
, void *data
)
466 zfs_type_t type
= zfs_get_type(zhp
);
467 zfs_key_config_t
*target
= data
;
468 char mountpoint
[ZFS_MAXPROPLEN
];
470 /* Skip any datasets whose type does not match */
471 if ((type
& ZFS_TYPE_FILESYSTEM
) == 0) {
476 /* Skip any datasets whose mountpoint does not match */
477 (void) zfs_prop_get(zhp
, ZFS_PROP_MOUNTPOINT
, mountpoint
,
478 sizeof (mountpoint
), NULL
, NULL
, 0, B_FALSE
);
479 if (strcmp(target
->homedir
, mountpoint
) != 0) {
484 target
->dsname
= strdup(zfs_get_name(zhp
));
490 zfs_key_config_get_dataset(zfs_key_config_t
*config
)
492 if (config
->homedir
!= NULL
&&
493 config
->homes_prefix
!= NULL
) {
494 zfs_handle_t
*zhp
= zfs_open(g_zfs
, config
->homes_prefix
,
495 ZFS_TYPE_FILESYSTEM
);
497 pam_syslog(NULL
, LOG_ERR
, "dataset %s not found",
498 config
->homes_prefix
);
503 (void) zfs_iter_filesystems(zhp
, find_dsname_by_prop_value
,
506 char *dsname
= config
->dsname
;
507 config
->dsname
= NULL
;
511 size_t len
= ZFS_MAX_DATASET_NAME_LEN
;
512 size_t total_len
= strlen(config
->homes_prefix
) + 1
513 + strlen(config
->username
);
514 if (total_len
> len
) {
517 char *ret
= malloc(len
+ 1);
522 strcat(ret
, config
->homes_prefix
);
524 strcat(ret
, config
->username
);
529 zfs_key_config_modify_session_counter(pam_handle_t
*pamh
,
530 zfs_key_config_t
*config
, int delta
)
532 const char *runtime_path
= config
->runstatedir
;
533 if (mkdir(runtime_path
, S_IRWXU
) != 0 && errno
!= EEXIST
) {
534 pam_syslog(pamh
, LOG_ERR
, "Can't create runtime path: %d",
538 if (chown(runtime_path
, 0, 0) != 0) {
539 pam_syslog(pamh
, LOG_ERR
, "Can't chown runtime path: %d",
543 if (chmod(runtime_path
, S_IRWXU
) != 0) {
544 pam_syslog(pamh
, LOG_ERR
, "Can't chmod runtime path: %d",
548 size_t runtime_path_len
= strlen(runtime_path
);
549 size_t counter_path_len
= runtime_path_len
+ 1 + 10;
550 char *counter_path
= malloc(counter_path_len
+ 1);
555 strcat(counter_path
, runtime_path
);
556 snprintf(counter_path
+ runtime_path_len
, counter_path_len
, "/%d",
558 const int fd
= open(counter_path
,
559 O_RDWR
| O_CLOEXEC
| O_CREAT
| O_NOFOLLOW
,
563 pam_syslog(pamh
, LOG_ERR
, "Can't open counter file: %d", errno
);
566 if (flock(fd
, LOCK_EX
) != 0) {
567 pam_syslog(pamh
, LOG_ERR
, "Can't lock counter file: %d", errno
);
573 int remaining
= sizeof (counter
) - 1;
575 counter
[sizeof (counter
) - 1] = 0;
576 while (remaining
> 0 && (ret
= read(fd
, pos
, remaining
)) > 0) {
581 long int counter_value
= strtol(counter
, NULL
, 10);
582 counter_value
+= delta
;
583 if (counter_value
< 0) {
586 lseek(fd
, 0, SEEK_SET
);
587 if (ftruncate(fd
, 0) != 0) {
588 pam_syslog(pamh
, LOG_ERR
, "Can't truncate counter file: %d",
593 snprintf(counter
, sizeof (counter
), "%ld", counter_value
);
594 remaining
= strlen(counter
);
596 while (remaining
> 0 && (ret
= write(fd
, pos
, remaining
)) > 0) {
601 return (counter_value
);
604 __attribute__((visibility("default")))
606 pam_sm_authenticate(pam_handle_t
*pamh
, int flags
,
607 int argc
, const char **argv
)
609 if (pw_fetch_lazy(pamh
) == NULL
) {
610 return (PAM_AUTH_ERR
);
613 return (PAM_SUCCESS
);
616 __attribute__((visibility("default")))
618 pam_sm_setcred(pam_handle_t
*pamh
, int flags
,
619 int argc
, const char **argv
)
621 return (PAM_SUCCESS
);
624 __attribute__((visibility("default")))
626 pam_sm_chauthtok(pam_handle_t
*pamh
, int flags
,
627 int argc
, const char **argv
)
629 if (geteuid() != 0) {
630 pam_syslog(pamh
, LOG_ERR
,
631 "Cannot zfs_mount when not being root.");
632 return (PAM_PERM_DENIED
);
634 zfs_key_config_t config
;
635 if (zfs_key_config_load(pamh
, &config
, argc
, argv
) == -1) {
636 return (PAM_SERVICE_ERR
);
638 if (config
.uid
< 1000) {
639 zfs_key_config_free(&config
);
640 return (PAM_SUCCESS
);
643 if (pam_zfs_init(pamh
) != 0) {
644 zfs_key_config_free(&config
);
645 return (PAM_SERVICE_ERR
);
647 char *dataset
= zfs_key_config_get_dataset(&config
);
650 zfs_key_config_free(&config
);
651 return (PAM_SERVICE_ERR
);
653 int key_loaded
= is_key_loaded(pamh
, dataset
);
654 if (key_loaded
== -1) {
657 zfs_key_config_free(&config
);
658 return (PAM_SERVICE_ERR
);
663 pam_syslog(pamh
, LOG_ERR
,
664 "key not loaded, returning try_again");
665 zfs_key_config_free(&config
);
666 return (PAM_PERM_DENIED
);
670 if ((flags
& PAM_UPDATE_AUTHTOK
) != 0) {
671 const pw_password_t
*token
= pw_get(pamh
);
673 zfs_key_config_free(&config
);
674 return (PAM_SERVICE_ERR
);
676 if (pam_zfs_init(pamh
) != 0) {
677 zfs_key_config_free(&config
);
678 return (PAM_SERVICE_ERR
);
680 char *dataset
= zfs_key_config_get_dataset(&config
);
683 zfs_key_config_free(&config
);
684 return (PAM_SERVICE_ERR
);
686 if (change_key(pamh
, dataset
, token
->value
) == -1) {
689 zfs_key_config_free(&config
);
690 return (PAM_SERVICE_ERR
);
694 zfs_key_config_free(&config
);
695 if (pw_clear(pamh
) == -1) {
696 return (PAM_SERVICE_ERR
);
699 zfs_key_config_free(&config
);
701 return (PAM_SUCCESS
);
705 pam_sm_open_session(pam_handle_t
*pamh
, int flags
,
706 int argc
, const char **argv
)
708 if (geteuid() != 0) {
709 pam_syslog(pamh
, LOG_ERR
,
710 "Cannot zfs_mount when not being root.");
711 return (PAM_SUCCESS
);
713 zfs_key_config_t config
;
714 zfs_key_config_load(pamh
, &config
, argc
, argv
);
715 if (config
.uid
< 1000) {
716 zfs_key_config_free(&config
);
717 return (PAM_SUCCESS
);
720 int counter
= zfs_key_config_modify_session_counter(pamh
, &config
, 1);
722 zfs_key_config_free(&config
);
723 return (PAM_SUCCESS
);
726 const pw_password_t
*token
= pw_get(pamh
);
728 zfs_key_config_free(&config
);
729 return (PAM_SESSION_ERR
);
731 if (pam_zfs_init(pamh
) != 0) {
732 zfs_key_config_free(&config
);
733 return (PAM_SERVICE_ERR
);
735 char *dataset
= zfs_key_config_get_dataset(&config
);
738 zfs_key_config_free(&config
);
739 return (PAM_SERVICE_ERR
);
741 if (decrypt_mount(pamh
, dataset
, token
->value
) == -1) {
744 zfs_key_config_free(&config
);
745 return (PAM_SERVICE_ERR
);
749 zfs_key_config_free(&config
);
750 if (pw_clear(pamh
) == -1) {
751 return (PAM_SERVICE_ERR
);
753 return (PAM_SUCCESS
);
757 __attribute__((visibility("default")))
759 pam_sm_close_session(pam_handle_t
*pamh
, int flags
,
760 int argc
, const char **argv
)
762 if (geteuid() != 0) {
763 pam_syslog(pamh
, LOG_ERR
,
764 "Cannot zfs_mount when not being root.");
765 return (PAM_SUCCESS
);
767 zfs_key_config_t config
;
768 zfs_key_config_load(pamh
, &config
, argc
, argv
);
769 if (config
.uid
< 1000) {
770 zfs_key_config_free(&config
);
771 return (PAM_SUCCESS
);
774 int counter
= zfs_key_config_modify_session_counter(pamh
, &config
, -1);
776 zfs_key_config_free(&config
);
777 return (PAM_SUCCESS
);
780 if (config
.unmount_and_unload
) {
781 if (pam_zfs_init(pamh
) != 0) {
782 zfs_key_config_free(&config
);
783 return (PAM_SERVICE_ERR
);
785 char *dataset
= zfs_key_config_get_dataset(&config
);
788 zfs_key_config_free(&config
);
789 return (PAM_SESSION_ERR
);
791 if (unmount_unload(pamh
, dataset
) == -1) {
794 zfs_key_config_free(&config
);
795 return (PAM_SESSION_ERR
);
801 zfs_key_config_free(&config
);
802 return (PAM_SUCCESS
);