Compare commits

...
Sign in to create a new pull request.

1 commit

Author SHA1 Message Date
Jiri Kucera
4b7b2e04b6 Add new test 2021-09-03 16:32:47 +02:00
3 changed files with 424 additions and 0 deletions

View file

@ -0,0 +1,60 @@
#!/bin/bash
#
# File: ./tests/Regression/bz1841869-pwd-from-tty/runtest.sh
# Author: Jiří Kučera <jkucera AT redhat.com>
# Brief: Test whether the passphrase is read from tty properly
#
# SPDX-License-Identifier: GPL-2.0-or-later
#
# Copyright (C) 2021 Red Hat, Inc.
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; either version 2 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# Include Beaker environment
. /usr/share/beakerlib/beakerlib.sh || exit 1
TEST="${TEST:-/CoreOS/volume_key/tests/Regression/bz1841869-pwd-from-tty}"
TESTVERSION="${TESTVERSION:-1.0}"
PACKAGES="${PACKAGES:-volume_key}"
REQUIRES="${REQUIRES:-gcc cryptsetup nss-tools expect tcllib}"
_PASSPHRASE="t0pS3cr31!"
rlJournalStart
rlPhaseStartSetup
rlAssertRpm --all
rlRun "TmpDir=\$(mktemp -d)" 0 "Creating tmp directory"
rlRun "cp -v vk_wrap.c ${TmpDir}" 0 "Copying vk_wrap.c"
rlRun "cp -v vk_dummy.c ${TmpDir}" 0 "Copying vk_dummy.c"
rlRun "pushd ${TmpDir}"
rlRun "gcc --std=c99 -pedantic -W -Wall -Werror -o vk_wrap vk_wrap.c" 0 \
"Compiling vk_wrap.c"
rlRun "gcc --std=c99 -pedantic -W -Wall -Werror -o vk_dummy vk_dummy.c" 0 \
"Compiling vk_dummy.c"
rlPhaseEnd
rlPhaseStartTest
_PARAMS="VK_WRAP_PROGAM=${TmpDir}/vk_dummy"
_PARAMS="${_PARAMS} VK_WRAP_PASSPHRASE=${_PASSPHRASE}"
rlRun "${_PARAMS} ${TmpDir}/vk_wrap" 0 "Run volume_key via wrapper"
rlPhaseEnd
rlPhaseStartCleanup
rlRun "popd"
rlRun "rm -rfd $TmpDir" 0 "Removing tmp directory"
rlPhaseEnd
rlJournalPrintText
rlJournalEnd

View file

@ -0,0 +1,185 @@
/*
* File: ./tests/Regression/bz1841869-pwd-from-tty/vk_dummy.c
* Author: Jiří Kučera <jkucera AT redhat.com>
* Brief: Dummy volume_key (only request passphrase)
*
* SPDX-License-Identifier: GPL-2.0-or-later
*
* Copyright (C) 2021 Red Hat, Inc.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <sys/types.h>
#include <sys/stat.h>
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>
#include <string.h>
#include <termios.h>
#include <fcntl.h>
#include <unistd.h>
struct ditem {
char *message;
struct ditem *next;
};
struct diagnose {
struct ditem *head, *tail;
};
static int diagnose_append(struct diagnose *diagnose, char *message)
{
struct ditem *p = NULL;
if ((p = malloc(sizeof(struct ditem))) == NULL)
return -1;
p->message = message;
p->next = NULL;
if (diagnose->head == NULL)
diagnose->head = diagnose->tail = p;
else {
diagnose->tail->next = p;
diagnose->tail = p;
}
return 0;
}
static void diagnose_dispose(struct diagnose *diagnose)
{
struct ditem *p = NULL, *t = NULL;
for (p = diagnose->head; (t = p); p = p->next, free(t))
free(t->message);
diagnose->head = diagnose->tail = NULL;
}
static void diagnose_show(struct diagnose *diagnose)
{
struct ditem *p = NULL;
fputs("=== Diagnose ===\n", stderr);
for (p = diagnose->head; p; p = p->next)
fprintf(sdterr, "- %s\n", p->message ? p->message : "(null)");
fputs("================\n", stderr);
}
static void log_read(int nread, struct diagnose *diagnose)
{
char *s = NULL;
if (nread < 0) {
switch (errno) {
case EINTR:
diagnose_append(diagnose, strdup("(EINTR)"));
break;
case EAGAIN:
diagnose_append(diagnose, strdup("(EAGAIN)"));
break;
default:
if (asprintf(&s, "(%s)", strerror(errno)) < 0)
s = NULL;
diagnose_append(diagnose, s);
break;
}
}
else if (nread == 0)
diagnose_append(diagnose, strdup("(EOF)"));
else {
if (asprintf(&s, "Read %d bytes.", nread) < 0)
s = NULL;
diagnose_append(diagnose, s);
}
}
static char *get_password(const char *prompt, struct diagnose *diagnose)
{
int tty = -1, in_file = -1, out_file = -1, echo_disabled = 0;
int nread = 0, tread = 0;
char buf[LINE_MAX + 1], *p = NULL;
struct termios otermios;
if ((tty = open("/dev/tty", O_RDONLY, 0)) < 0) {
in_file = STDIN_FILENO;
out_file = STDERR_FILENO;
}
else
out_file = in_file = tty;
write(out_file, prompt, strlen(prompt));
if (tcgetattr(in_file, &otermios) == 0) {
struct termios ntermios;
ntermios = otermios;
ntermios.c_lflag &= ~ECHO;
echo_disabled = tcsetattr(in_file, TCSAFLUSH, &ntermios) == 0;
}
while (tread < LINE_MAX) {
nread = read(in_file, buf + tread, LINE_MAX - tread);
log_read(nread, diagnose);
if (nread < 0) {
if (errno == EINTR || errno == EAGAIN)
continue;
break;
}
else if (nread == 0)
break;
tread += nread;
}
buf[tread] = '\0';
if (echo_disabled) {
tcsetattr(in_file, TCSAFLUSH, &otermios);
write(out_file, "\n", 1);
}
if (tty >= 0)
close(tty);
if (nread < 0)
return NULL;
p = strchr(buf, '\r');
if (p != NULL)
*p = '\0';
p = strchr(buf, '\n');
if (p != NULL)
*p = '\0';
return strdup(buf);
}
int main(void)
{
struct diagnose diagnose = {NULL, NULL};
char *passphrase = NULL, *s = NULL;
passphrase = get_password("Please, enter the passphrase: ", &diagnose);
if (asprintf(&s, "The passphrase is: %s", passphrase) < 0)
s = NULL;
free(passphrase);
diagnose_append(&diagnose, s);
diagnose_show(&diagnose);
diagnose_dispose(&diagnose);
return EXIT_SUCCESS;
}

View file

@ -0,0 +1,179 @@
/*
* File: ./tests/Regression/bz1841869-pwd-from-tty/vk_wrap.c
* Author: Jiří Kučera <jkucera AT redhat.com>
* Brief: Run volume_key, emulate EINTR and EAGAIN
*
* SPDX-License-Identifier: GPL-2.0-or-later
*
* Copyright (C) 2021 Red Hat, Inc.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdarg.h>
#include <string.h>
#include <signal.h>
#include <fcntl.h>
#include <unistd.h>
enum emultypes {
NO_EMUL,
EINTR_EMUL,
EAGAIN_EMUL
};
static void error(const char *format, ...)
{
va_list args;
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
}
static char *get_env(const char *name, int optional)
{
char *p = getenv(name);
if (!optional && (p == NULL || *p == '\0'))
return (error("Environment variable %s is not set!\n", name), NULL);
return p;
}
static int get_emul_type(void)
{
char *p = get_env("VK_WRAP_EMULATE", 1);
if (p == NULL)
return NO_EMUL;
else if (strcmp(p, "EINTR") == 0)
return EINTR_EMUL;
else if (strcmp(p, "EAGAIN") == 0)
return EAGAIN_EMUL;
return NO_EMUL;
}
static void close_pipe(int pipefd[2])
{
close(pipefd[0]);
close(pipefd[1]);
}
static void freeargs(char **args)
{
char **p = args;
if (!p)
return;
while (*p)
free(*p++);
free(args);
}
static char **mkargs(const *program, int argc, char **argv)
{
char **args = NULL;
size_t args_size = (argc + 1) * sizeof(*args);
int i = 0;
if ((args = malloc(args_size)) == NULL)
return NULL;
memset(args, '\0', args_size);
if ((args[0] = strdup(program)) == NULL)
return (freeargs(args), NULL);
for (i = 1; i < argc; i++)
if ((args[i] = strdup(argv[i])) == NULL)
return (freeargs(args), NULL);
return args;
}
int main(int argc, char *argv[])
{
char *program = NULL, *passphrase = NULL;
int emultype = NO_EMUL, cpid = 0, pipefd[2];
if ((program = get_env("VK_WRAP_PROGRAM", 0)) == NULL)
return EXIT_FAILURE;
if ((passphrase = get_env("VK_WRAP_PASSPHRASE", 0)) == NULL)
return EXIT_FAILURE;
emultype = get_emul_type();
if (pipe(pipefd) < 0)
return (perror("pipe"), EXIT_FAILURE);
/* Set non-blocking reading only if we emulate EAGAIN */
if (emultype == EAGAIN_EMUL && fcntl(pipefd[0], F_SETFL, O_NONBLOCK) < 0)
return (perror("fcntl"), close_pipe(pipefd), EXIT_FAILURE);
cpid = fork();
if (cpid < 0)
return (perror("fork"), close_pipe(pipefd), EXIT_FAILURE);
if (cpid == 0) {
char **args = NULL;
/* We are in the child, close the writing end of the pipe */
close(pipefd[1]);
/* Map the reading end of the pipe to standard input */
if (pipefd[0] != STDIN_FILENO) {
if (dup2(pipefd[0], STDIN_FILENO) < 0)
return (perror("dup2"), close(pipefd[0]), EXIT_FAILURE);
close(pipefd[0]);
}
/* Copy the program arguments */
if ((args = mkargs(program, argc, argv)) == NULL)
return (perror("mkargs"), EXIT_FAILURE);
/* Replace the current process image with the program */
if (execv(args[0], args) < 0)
return (perror("execv"), freeargs(args), EXIT_FAILURE);
}
/* We are in the parent, close the reading end of the pipe */
close(pipefd[0]);
switch (emultype) {
case EINTR_EMUL:
/* Hopefully this emulate EINTR */
sleep(2); /* Wait for the child to enter read */
/* Now child is inside kernel space, send it the signal */
kill(cpid, SIGSTOP);
sleep(1); /* Wait a bit */
/* Resume child */
kill(cpid, SIGCONT);
break;
case EAGAIN_EMUL:
/* Delay writing the passphrase. When the child read from an empty pipe
in the non-blocking mode, read() returns -1 and sets errno to EAGAIN
*/
sleep(3);
break;
default:
break;
}
/* Write the passphrase */
if (write(pipefd[1], passphrase, strlen(passphrase)) < 0)
perror("write");
if (write(pipefd[1], "\n", 1) < 0)
perror("write");
close(pipefd[1]); /* Reader will see EOF */
/* Wait for the child to finish */
waitpid(cpid, NULL, 0);
return EXIT_SUCCESS;
}