Just use pipe to read passphrase from helper instead of memfd weirdness

This commit is contained in:
наб 2022-12-04 02:25:00 +01:00
parent 720a0103a7
commit d2dcf95b0f
No known key found for this signature in database
GPG Key ID: BCFD0B018D2658F1

View File

@ -5,14 +5,12 @@
#include "main.hpp"
#include <utility>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <termios.h>
#include <unistd.h>
#include <utility>
/// Matches libzfs
@ -69,74 +67,64 @@ int write_exact(const char * path, const void * data, size_t len, mode_t mode) {
/// TRY_MAIN rules, plus -1 for ENOENT
static int get_key_material_helper(const char * helper, const char * whom, bool again, bool newkey, uint8_t *& buf, size_t & len_out) {
#if __linux__ || __FreeBSD__
auto outfd = TRY_HELPER("create helper output", memfd_create(whom, MFD_CLOEXEC));
#else
int outfd;
char fname[8 + 10 + 1 + 20 + 1]; // 4294967296, 18446744073709551616
auto pid = getpid();
for(uint64_t i = 0; i < UINT64_MAX; ++i) {
snprintf(fname, sizeof(fname), "/tzpfms:%" PRIu32 ":%" PRIu64 "", static_cast<uint32_t>(pid), i);
if((outfd = shm_open(fname, O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC, 0000)) != -1 || errno != EEXIST)
break;
int pipes[2];
TRY("create IPC pipe", pipe2(pipes, O_CLOEXEC));
quickscope_wrapper pipes_r_deleter{[=] { close(pipes[0]); }};
if(auto pid = TRY_HELPER("create child", fork()); pid == 0) { // child
dup2(pipes[1], 1);
char * msg;
if(asprintf(&msg, "%sassphrase for %s%s", newkey ? "New p" : "P", whom, again ? " (again)" : "") == -1)
msg = const_cast<char *>(whom);
execl("/bin/sh", "sh", "-c", helper, helper, msg, whom, newkey ? "new" : "", again ? "again" : "", nullptr);
int exec_err = errno;
fprintf(stderr, "exec(/bin/sh): %s\n", strerror(errno));
_exit(exec_err == ENOENT ? 127 : 126);
}
TRY_HELPER("create helper output", outfd);
shm_unlink(fname);
#endif
quickscope_wrapper outfd_deleter{[=] { close(outfd); }};
close(pipes[1]);
switch(auto pid = TRY_HELPER("create child", fork())) {
case 0: // child
dup2(outfd, 1);
char * msg;
if(asprintf(&msg, "%sassphrase for %s%s", newkey ? "New p" : "P", whom, again ? " (again)" : "") == -1)
msg = const_cast<char *>(whom);
execl("/bin/sh", "sh", "-c", helper, helper, msg, whom, newkey ? "new" : "", again ? "again" : "", nullptr);
fprintf(stderr, "exec(/bin/sh): %s\n", strerror(errno));
_exit(127);
buf = nullptr;
len_out = 0;
for(size_t len_cap = 0;;) { // 99% of cases this is 1 allocation and 1 read
if(len_out == len_cap) {
len_cap += 120;
buf = TRY_PTR("allocate passphrase", static_cast<uint8_t *>(realloc(buf, len_cap)));
}
ssize_t rd;
while((rd = read(pipes[0], buf + len_out, len_cap - len_out)) == -1 && errno == EINTR)
;
TRY("read passphrase from helper", rd);
if(rd == 0)
break;
default: // parent
int err, ret;
while((ret = waitpid(pid, &err, 0)) == -1 && errno == EINTR)
;
TRY("wait for helper", ret);
len_out += rd;
}
if(len_out && buf[len_out - 1] == '\n')
--len_out;
if(WIFEXITED(err)) {
switch(WEXITSTATUS(err)) {
case 0:
struct stat sb;
fstat(outfd, &sb);
if(!sb.st_size) // unmmappable
return buf = nullptr, len_out = 0, 0;
else if(auto out = static_cast<uint8_t *>(mmap(nullptr, sb.st_size, PROT_READ, MAP_PRIVATE | MAP_POPULATE, outfd, 0)); out != MAP_FAILED) {
quickscope_wrapper out_deleter{[=] { munmap(out, sb.st_size); }};
len_out = sb.st_size;
if(out[len_out - 1] == '\n') // Trim ending newline, if any
--len_out;
if(!len_out)
buf = nullptr;
else {
if(!(buf = static_cast<uint8_t *>(malloc(len_out))))
len_out = 0, (void)TRY("allocate passphrase", -1);
memcpy(buf, out, len_out);
}
return 0;
} else
TRY("read back passphrase", -1);
case 127: // ENOENT, error already written by shell or child
return -1;
int err, ret;
while((ret = wait(&err)) == -1 && errno == EINTR)
;
TRY("wait for helper", ret);
default:
fprintf(stderr, "Helper '%s' failed with %d.\n", helper, WEXITSTATUS(err));
return __LINE__;
}
} else {
fprintf(stderr, "Helper '%s' died to signal %d: %s.\n", helper, WTERMSIG(err), strsignal(WTERMSIG(err)));
if(WIFEXITED(err))
switch(WEXITSTATUS(err)) {
case 0:
return 0;
case 127: // ENOENT, error already written by shell or child
return -1;
default:
fprintf(stderr, "Helper '%s' failed with %d.\n", helper, WEXITSTATUS(err));
return __LINE__;
}
}
else {
fprintf(stderr, "Helper '%s' died to signal %d: %s.\n", helper, WTERMSIG(err), strsignal(WTERMSIG(err)));
return __LINE__;
}
}