diff --git a/configure.ac b/configure.ac index 212a3bb7..5a61d5e3 100644 --- a/configure.ac +++ b/configure.ac @@ -122,6 +122,11 @@ AC_CHECK_HEADERS( \ [linux/magic.h] \ ) +# +# Checks for functions +# +AC_CHECK_FUNC([pipe2], [], AC_MSG_FAILURE([Required function pipe2 missing])) + # # Checks for packages # diff --git a/src/imp/Makefile.am b/src/imp/Makefile.am index a67a92ce..a9b5e1e2 100644 --- a/src/imp/Makefile.am +++ b/src/imp/Makefile.am @@ -62,7 +62,9 @@ IMP_SOURCES = \ run.c \ exec/user.h \ exec/user.c \ - exec/exec.c + exec/exec.c \ + exec/safe_popen.h \ + exec/safe_popen.c if HAVE_PAM IMP_SOURCES += \ @@ -135,7 +137,8 @@ TESTS = \ test_privsep.t \ test_impcmd.t \ test_passwd.t \ - test_pidinfo.t + test_pidinfo.t \ + test_safe_popen.t check_PROGRAMS = \ $(TESTS) @@ -187,3 +190,11 @@ test_pidinfo_t_SOURCES = \ imp_log.c \ imp_log.h test_pidinfo_t_LDADD = $(test_ldadd) + +test_safe_popen_t_SOURCES = \ + test/safe_popen.c \ + exec/safe_popen.c \ + exec/safe_popen.h \ + imp_log.c \ + imp_log.h +test_safe_popen_t_LDADD= $(test_ldadd) diff --git a/src/imp/exec/exec.c b/src/imp/exec/exec.c index 70dc25e9..80aa57d8 100644 --- a/src/imp/exec/exec.c +++ b/src/imp/exec/exec.c @@ -17,6 +17,8 @@ * Signed J as key "J" in JSON object on stdin, path to requested * job shell and single argument on cmdline. * + * If FLUX_IMP_EXEC_HELPER is set, then execute the value of this + * variable and read input from there. */ #if HAVE_CONFIG_H @@ -43,6 +45,7 @@ #include "privsep.h" #include "passwd.h" #include "user.h" +#include "safe_popen.h" #if HAVE_PAM #include "pam.h" @@ -371,8 +374,31 @@ static void imp_exec_put_kv (struct imp_exec *exec, imp_die (1, "exec: Failed to set job shell arguments"); } +/* Read IMP input using a helper process + */ +static void imp_exec_init_helper (struct imp_exec *exec, + char *helper) +{ + int status; + struct safe_popen *sp; + + if (!(sp = safe_popen (helper))) + imp_die (1, "exec: failed to invoke helper: %s", helper); + + imp_exec_init_stream (exec, safe_popen_fp (sp)); + + if (safe_popen_wait (sp, &status) < 0 + || status != 0) + imp_die (1, "exec: helper %s failed with status=0x%04x", + helper, + status); + + safe_popen_destroy (sp); +} + int imp_exec_unprivileged (struct imp_state *imp, struct kv *kv) { + char *helper; struct imp_exec *exec = imp_exec_create (imp); if (!exec) imp_die (1, "exec: initialization failure"); @@ -381,8 +407,16 @@ int imp_exec_unprivileged (struct imp_state *imp, struct kv *kv) imp_die (1, "exec: user %s not in allowed-users list", exec->imp_pwd->pw_name); - /* Read input from stdin, cmdline: */ - imp_exec_init_stream (exec, stdin); + if ((helper = getenv ("FLUX_IMP_EXEC_HELPER"))) { + if (strlen (helper) == 0) + imp_die (1, "exec: FLUX_IMP_EXEC_HELPER is empty"); + /* Read input from helper command */ + imp_exec_init_helper (exec, helper); + } + else { + /* Read input from stdin, cmdline: */ + imp_exec_init_stream (exec, stdin); + } /* XXX; Parse jobspec if necessary, disabled for now: */ //if (!(jobspec = json_loads (spec, 0, &err))) diff --git a/src/imp/exec/safe_popen.c b/src/imp/exec/safe_popen.c new file mode 100644 index 00000000..b39a3de6 --- /dev/null +++ b/src/imp/exec/safe_popen.c @@ -0,0 +1,121 @@ +/************************************************************\ + * Copyright 2023 Lawrence Livermore National Security, LLC + * (c.f. AUTHORS, NOTICE.LLNS, COPYING) + * + * This file is part of the Flux resource manager framework. + * For details, see https://github.com/flux-framework. + * + * SPDX-License-Identifier: LGPL-3.0 +\************************************************************/ + +#ifndef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "src/libutil/argsplit.h" +#include "safe_popen.h" +#include "imp_log.h" + +struct safe_popen { + pid_t pid; + FILE *fp; +}; + +void safe_popen_destroy (struct safe_popen *sp) +{ + if (sp) { + int saved_errno = errno; + fclose (sp->fp); + free (sp); + errno = saved_errno; + } +} + +struct safe_popen * safe_popen (const char *cmd) +{ + struct safe_popen *sp; + int pfds[2] = {-1, -1}; + + if (cmd == NULL || strlen (cmd) == 0) { + errno = EINVAL; + return NULL; + } + + if (!(sp = calloc (1, sizeof (*sp))) + || pipe (pfds) < 0 + || !(sp->fp = fdopen (pfds[0], "r")) + || (sp->pid = fork ()) < 0) { + imp_warn ("Failed to setup child for popen: %s", strerror (errno)); + goto error; + } + + if (sp->pid == 0) { + /* Child process: Use pfds[1] as stdout + */ + char **argv = argsplit (cmd); + if (!argv) { + fprintf (stderr, "imp: popen: failed to tokenize '%s'\n", cmd); + _exit (126); + } + if (dup2 (pfds[1], STDOUT_FILENO) < 0) { + fprintf (stderr, "imp: popen: dup2: %s\n", strerror (errno)); + _exit (126); + } + (void) close (pfds[0]); + (void) close (pfds[1]); + setsid (); + execvp (argv[0], argv); + fprintf (stderr, "imp: popen: %s: %s\n", argv[0], strerror (errno)); + if (errno == ENOENT) + _exit (127); + _exit (126); + } + + /* Parent + */ + (void) close (pfds[1]); + + return sp; +error: + safe_popen_destroy (sp); + if (pfds[0] > 0) { + (void) close (pfds[0]); + (void) close (pfds[1]); + } + return NULL; +} + +FILE *safe_popen_fp (struct safe_popen *sp) +{ + if (!sp || !sp->fp) { + errno = EINVAL; + return NULL; + } + return sp->fp; +} + +int safe_popen_wait (struct safe_popen *sp, int *statusp) +{ + pid_t pid; + + if (!sp || sp->pid <= (pid_t) 0) { + errno = EINVAL; + return -1; + } + do { + pid = waitpid (sp->pid, statusp, 0); + } while (pid == -1 && errno == EINTR); + + return pid == -1 ? -1 : 0; +} + +/* vi: ts=4 sw=4 expandtab + */ diff --git a/src/imp/exec/safe_popen.h b/src/imp/exec/safe_popen.h new file mode 100644 index 00000000..2fa218a7 --- /dev/null +++ b/src/imp/exec/safe_popen.h @@ -0,0 +1,47 @@ +/************************************************************\ + * Copyright 2023 Lawrence Livermore National Security, LLC + * (c.f. AUTHORS, NOTICE.LLNS, COPYING) + * + * This file is part of the Flux resource manager framework. + * For details, see https://github.com/flux-framework. + * + * SPDX-License-Identifier: LGPL-3.0 +\************************************************************/ + +#ifndef HAVE_IMP_EXEC_SAFE_POPEN_H +#define HAVE_IMP_EXEC_SAFE_POPEN_H 1 + +#include + +struct safe_popen; + +/* Safer and simpler version of popen(3): + * - does not invoke shell + * - splits `cmd` on whitespace only + * + * Returns a new safe_popen object on success, NULL with errno set + * on failure. + * EINVAL - cmd is NULL or an empty string. + * ENOMEM - out of memory + */ +struct safe_popen *safe_popen (const char *cmd); + +/* Return FILE * associated with safe_popen object */ +FILE *safe_popen_fp (struct safe_popen *sp); + +/* Call waitpid(2) on process spawned by safe_popen(), placing process + * status in `status` if non-NULL. + * Returns 0 on success or -1 with errno set on error. + * Special exit codes: + * - 127: exec failed to find command + * - 126: exec failed for other reason + * - 125: internal error in child process before exec + */ +int safe_popen_wait (struct safe_popen *sp, int *status); + +/* Destroy safe_popen object. Close open file descriptor and free + * associated memory. + */ +void safe_popen_destroy (struct safe_popen *sp); + +#endif /* !HAVE_IMP_EXEC_SAFE_POPEN_H */ diff --git a/src/imp/imp_log.c b/src/imp/imp_log.c index f62900c4..a9eded47 100644 --- a/src/imp/imp_log.c +++ b/src/imp/imp_log.c @@ -201,31 +201,40 @@ static void vlog_msg (int level, const char *format, va_list ap, void imp_say (const char *fmt, ...) { va_list ap; + int saved_errno; if (imp_logger.level < IMP_LOG_INFO) return; + saved_errno = errno; va_start (ap, fmt); vlog_msg (IMP_LOG_INFO, fmt, ap, imp_logger.outputs); va_end (ap); + errno = saved_errno; } void imp_warn (const char *fmt, ...) { va_list ap; + int saved_errno; if (imp_logger.level < IMP_LOG_WARNING) return; + saved_errno = errno; va_start (ap, fmt); vlog_msg (IMP_LOG_WARNING, fmt, ap, imp_logger.outputs); va_end (ap); + errno = saved_errno; } void imp_debug (const char *fmt, ...) { va_list ap; + int saved_errno; if (imp_logger.level < IMP_LOG_DEBUG) return; + saved_errno = errno; va_start (ap, fmt); vlog_msg (IMP_LOG_DEBUG, fmt, ap, imp_logger.outputs); va_end (ap); + errno = saved_errno; } void imp_die (int code, const char *fmt, ...) diff --git a/src/imp/privsep.c b/src/imp/privsep.c index 25f26a25..3cda1790 100644 --- a/src/imp/privsep.c +++ b/src/imp/privsep.c @@ -21,6 +21,7 @@ #include #include #include +#include /* Max size of KV array allowed to be sent over privsep pipe */ #define PRIVSEP_MAX_KVLEN 1024*1024*4 @@ -147,7 +148,8 @@ privsep_t * privsep_init (privsep_child_f fn, void *arg) ps->wfd = -1; ps->rfd = -1; - if (pipe (ps->upfds) < 0 || pipe (ps->ppfds) < 0) { + if (pipe2 (ps->upfds, O_CLOEXEC) < 0 + || pipe2 (ps->ppfds, O_CLOEXEC) < 0) { imp_warn ("privsep_init: pipe: %s\n", strerror (errno)); privsep_destroy (ps); return (NULL); diff --git a/src/imp/test/safe_popen.c b/src/imp/test/safe_popen.c new file mode 100644 index 00000000..4fed09c0 --- /dev/null +++ b/src/imp/test/safe_popen.c @@ -0,0 +1,121 @@ +/************************************************************\ + * Copyright 2023 Lawrence Livermore National Security, LLC + * (c.f. AUTHORS, NOTICE.LLNS, COPYING) + * + * This file is part of the Flux resource manager framework. + * For details, see https://github.com/flux-framework. + * + * SPDX-License-Identifier: LGPL-3.0 +\************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include "imp_log.h" +#include "exec/safe_popen.h" +#include "src/libtap/tap.h" + +static void test_safe_popen_invalid (void) +{ + ok (safe_popen (NULL) == NULL && errno == EINVAL, + "safe_popen (NULL) fails with EINVAL"); + ok (safe_popen ("") == NULL && errno == EINVAL, + "safe_popen (\"\") fails with EINVAL"); + ok (safe_popen_wait (NULL, NULL) < 0 && errno == EINVAL, + "safe_popen_wait (NULL, NULL) returns EINVAL"); + ok (safe_popen_fp (NULL) == NULL && errno == EINVAL, + "safe_popen_fp (NULL) fails with EINVAL"); + lives_ok ({safe_popen_destroy (NULL);}, + "safe_popen_destroy (NULL) doesn't crash program"); +} + +static void test_safe_popen_basic (void) +{ + size_t n; + FILE *fp; + char buf[64]; + int status; + struct safe_popen *sp; + + sp = safe_popen ("printf %s hello"); + if (sp == NULL) + BAIL_OUT ("safe_popen failed"); + pass ("safe_popen success"); + + if (!(fp = safe_popen_fp (sp))) + BAIL_OUT ("safe_popen_get_fp failed"); + pass ("safe_popen_get_fp success"); + + memset (buf, 0, sizeof (buf)); + n = fread (buf, 1, sizeof (buf), fp); + ok (n == 5, + "fread() from fp got %zu bytes (expected 5)", n); + is (buf, "hello", + "buffer == \"hello\""); + ok (safe_popen_wait (sp, &status) == 0, + "safe_popen_wait() returned 0"); + ok (status == 0, + "status == 0"); + ok (safe_popen_wait (sp, &status) < 0, + "safe_popen_wait() fails when called again"); + ok (WIFEXITED (status) && WEXITSTATUS (status) == 0, + "safe_popen_wait() reports exit code of 0"); + safe_popen_destroy (sp); +} + +static void test_safe_popen_failure (void) +{ + int status; + + /* Call printf(1) with no arguments, which should fail */ + struct safe_popen *sp = safe_popen ("printf"); + if (sp == NULL) + BAIL_OUT ("safe_popen(printf) failed"); + pass ("safe_popen (\"printf\") success"); + ok (safe_popen_wait (sp, &status) == 0, + "safe_popen_wait() == 0"); + ok (WIFEXITED (status) && WEXITSTATUS (status) > 0, + "safe_popen_wait() reports exit code of %d", WEXITSTATUS (status)); + safe_popen_destroy (sp); + + if (!(sp = safe_popen ("nosuchcommand"))) + BAIL_OUT ("safe_popen(nosuchcommand) failed"); + pass ("safe_popen (\"nosuchcommand\") success"); + ok (safe_popen_wait (sp, &status) == 0, + "safe_popen_wait() == 0"); + ok (WIFEXITED (status) && WEXITSTATUS (status) == 127, + "safe_popen_wait() reports exit code of %d", WEXITSTATUS (status)); + safe_popen_destroy (sp); +} + +static int log_diag (int level, const char *str, + void *arg __attribute__ ((unused))) +{ + diag ("safe_popen: %s: %s\n", imp_log_strlevel (level), str); + return (0); +} + +int main (void) +{ + /* safe_popen uses imp log for errors so initialize here + */ + imp_openlog (); + imp_log_add ("diag", IMP_LOG_DEBUG, log_diag, NULL); + + plan (NO_PLAN); + + test_safe_popen_invalid (); + test_safe_popen_basic (); + test_safe_popen_failure (); + + imp_closelog (); + done_testing (); +} + +/* + * vi: ts=4 sw=4 expandtab + */ diff --git a/src/libutil/Makefile.am b/src/libutil/Makefile.am index 48ee723e..7ca89aed 100644 --- a/src/libutil/Makefile.am +++ b/src/libutil/Makefile.am @@ -34,7 +34,9 @@ libutil_la_SOURCES = \ strlcpy.c \ strlcpy.h \ path.c \ - path.h + path.h \ + argsplit.c \ + argsplit.h TESTS = \ test_hash.t \ @@ -43,7 +45,8 @@ TESTS = \ test_kv.t \ test_sha256.t \ test_aux.t \ - test_path.t + test_path.t \ + test_argsplit.t test_ldadd = \ $(top_builddir)/src/libutil/libutil.la \ @@ -88,3 +91,7 @@ test_aux_t_CPPFLAGS = $(test_cppflags) test_path_t_SOURCES = test/path.c test_path_t_LDADD = $(test_ldadd) test_path_t_CPPFLAGS = $(test_cppflags) + +test_argsplit_t_SOURCES = test/argsplit.c +test_argsplit_t_LDADD = $(test_ldadd) +test_argsplit_t_CPPFLAGS = $(test_cppflags) diff --git a/src/libutil/argsplit.c b/src/libutil/argsplit.c new file mode 100644 index 00000000..470cf490 --- /dev/null +++ b/src/libutil/argsplit.c @@ -0,0 +1,83 @@ +/************************************************************\ + * Copyright 2023 Lawrence Livermore National Security, LLC + * (c.f. AUTHORS, NOTICE.LLNS, COPYING) + * + * This file is part of the Flux resource manager framework. + * For details, see https://github.com/flux-framework. + * + * SPDX-License-Identifier: LGPL-3.0 +\************************************************************/ + +#if HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +#include +#include +#include + +#include "argsplit.h" + +void args_free (char **args) +{ + if (args) { + int i = 0; + while (args[i]) + free (args[i++]); + free (args); + } +} + +char **argsplit (const char *args) +{ + int count = 0; + char *s; + char *sp = NULL; + char *str; + char *copy; + char **argv; + int i = 0; + + if (!args || strlen (args) == 0) { + errno = 0; + return NULL; + } + if (!(copy = strdup (args))) + return NULL; + + /* Non-destructively count number of tokens + */ + s = copy; + while ((s = strpbrk (s, " \t"))) { + count++; + while (*s == ' ' || *s == '\t') + s++; + } + + /* Allocate return vector + */ + if (!(argv = calloc (count+2, sizeof (char *)))) + goto error; + + /* Split into takens + */ + i = 0; + str = copy; + while ((s = strtok_r (str, " \t", &sp))) { + if (!(argv[i] = strdup (s))) + goto error; + i++; + str = NULL; + } + free (copy); + return argv; +error: + free (copy); + args_free (argv); + return NULL; +} + + +/* + * vi:tabstop=4 shiftwidth=4 expandtab + */ diff --git a/src/libutil/argsplit.h b/src/libutil/argsplit.h new file mode 100644 index 00000000..7f980d3b --- /dev/null +++ b/src/libutil/argsplit.h @@ -0,0 +1,23 @@ +/************************************************************\ + * Copyright 2023 Lawrence Livermore National Security, LLC + * (c.f. AUTHORS, NOTICE.LLNS, COPYING) + * + * This file is part of the Flux resource manager framework. + * For details, see https://github.com/flux-framework. + * + * SPDX-License-Identifier: LGPL-3.0 +\************************************************************/ + +#ifndef HAVE_ARGSPLIT_H +#define HAVE_ARGSPLIT_H + +/* Split a string on whitespace, returning an array on success. + * Caller must free returned arg array, e.g. via args_free() below. + */ +char **argsplit (const char *str); + +/* Convenience function to destroy an allocated array of char * + */ +void args_free (char **args); + +#endif /* !HAVE_ARGSPLIT_H */ diff --git a/src/libutil/test/argsplit.c b/src/libutil/test/argsplit.c new file mode 100644 index 00000000..07c4a57f --- /dev/null +++ b/src/libutil/test/argsplit.c @@ -0,0 +1,78 @@ +/************************************************************\ + * Copyright 2023 Lawrence Livermore National Security, LLC + * (c.f. AUTHORS, NOTICE.LLNS, COPYING) + * + * This file is part of the Flux resource manager framework. + * For details, see https://github.com/flux-framework. + * + * SPDX-License-Identifier: LGPL-3.0 +\************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include "argsplit.h" +#include "src/libtap/tap.h" + +int main (void) +{ + char **argv; + + plan (NO_PLAN); + errno = 0; + ok (argsplit (NULL) == NULL && errno == 0, + "argsplit (NULL) returns NULL with errno not set"); + ok (argsplit ("") == NULL && errno == 0, + "argsplit (\"\") returns NULL with errno not set"); + argv = argsplit ("one"); + ok (argv != NULL, + "argsplit (\"one\") works"); + is (argv[0], "one", + "first argument is correct"); + ok (argv[1] == NULL, + "returned array is NULL terminated"); + args_free (argv); + + argv = argsplit ("one two"); + ok (argv != NULL, + "argsplit (\"one two\") works"); + is (argv[0], "one", + "first argument is correct"); + is (argv[1], "two", + "second argument is correct"); + ok (argv[2] == NULL, + "returned array is NULL terminated"); + args_free (argv); + + argv = argsplit ("one two three "); + ok (argv != NULL, + "argsplit (\"one two three \") works"); + is (argv[0], "one", + "first argument is correct"); + is (argv[1], "two", + "second argument is correct"); + is (argv[2], "three", + "third argument is correct"); + ok (argv[3] == NULL, + "returned array is NULL terminated"); + args_free (argv); + + argv = argsplit (" one\t two\t"); + ok (argv != NULL, + "argsplit (\" one\t two\t\") works"); + is (argv[0], "one", + "first argument is correct"); + is (argv[1], "two", + "second argument is correct"); + ok (argv[2] == NULL, + "returned array is NULL terminated"); + args_free (argv); + + done_testing (); +} + + diff --git a/t/t2000-imp-exec.t b/t/t2000-imp-exec.t index a53b383d..3441cd9a 100755 --- a/t/t2000-imp-exec.t +++ b/t/t2000-imp-exec.t @@ -19,6 +19,21 @@ fake_imp_input() { printf '{"J":"%s"}' $(echo $1 | $sign) } +cat <helper.sh +#!/bin/sh +printf '{"J":"%s"}' \$(echo \$1 | $sign) +test -z "\$IMP_HELPER_FAIL" +EOF +chmod +x helper.sh + +test_expect_success 'create config allowing current user to exec imp' ' + cat <<-EOF >imp-test.toml + allow-sudo = true + [exec] + allowed-users = [ "$(whoami)" ] + EOF +' +export FLUX_IMP_CONFIG_PATTERN=imp-test.toml test_expect_success 'flux-imp exec returns error when run with no args' ' test_must_fail $flux_imp exec ' @@ -37,6 +52,24 @@ test_expect_success 'flux-imp exec returns error with bad JSON input ' ' test_expect_success 'flux-imp exec returns error with invalid JSON input ' ' echo "{" | test_must_fail $flux_imp exec shell arg ' + +# Now run the expected failure tests above with a helper of /usr/bin/cat: +export FLUX_IMP_EXEC_HELPER=cat +test_expect_success 'flux-imp exec returns error with no input' ' + test_must_fail $flux_imp exec shell arg < /dev/null +' +test_expect_success 'flux-imp exec returns error with bad input ' ' + echo foo | test_must_fail $flux_imp exec shell arg +' +test_expect_success 'flux-imp exec returns error with bad JSON input ' ' + echo "{}" | test_must_fail $flux_imp exec shell arg +' +test_expect_success 'flux-imp exec returns error with invalid JSON input ' ' + echo "{" | test_must_fail $flux_imp exec shell arg +' +unset FLUX_IMP_CONFIG_PATTERN +unset FLUX_IMP_EXEC_HELPER + test_expect_success 'create configs for flux-imp exec and signer' ' cat <<-EOF >no-unpriv-exec.toml && allow-sudo = true @@ -91,6 +124,43 @@ test_expect_success 'flux-imp exec works in unprivileged mode if configured' ' EOF test_cmp works.expected works.out ' +test_expect_success 'flux-imp exec works in unprivileged mode with helper' ' + ( export FLUX_IMP_CONFIG_PATTERN=sign-none.toml && + export FLUX_IMP_EXEC_HELPER=$(pwd)/helper.sh && + $flux_imp exec echo good-again >works-helper.out + ) && + cat >works-helper.expected <<-EOF && + good-again + EOF + test_cmp works-helper.expected works-helper.out +' +test_expect_success 'flux-imp exec returns error with invalid helper' ' + ( export FLUX_IMP_CONFIG_PATTERN=sign-none.toml && + export FLUX_IMP_EXEC_HELPER="foo bar" && + fake_imp_input foo | \ + test_must_fail $flux_imp exec echo good >badhelper.out 2>&1 + ) && + test_debug "cat badhelper.out" && + grep "imp: popen:" badhelper.out +' +test_expect_success 'flux-imp returns error with empty helper' ' + ( export FLUX_IMP_CONFIG_PATTERN=sign-none.toml && + export FLUX_IMP_EXEC_HELPER="" && + fake_imp_input foo | \ + test_must_fail $flux_imp exec echo OK >emptyhelper.out 2>&1 + ) && + test_debug "cat emptyhelper.out" && + grep "FLUX_IMP_EXEC_HELPER is empty" emptyhelper.out +' +test_expect_success 'flux-imp exec fails when helper exits with nonzero status' ' + ( export FLUX_IMP_CONFIG_PATTERN=sign-none.toml && + export FLUX_IMP_EXEC_HELPER=$(pwd)/helper.sh && + export IMP_HELPER_FAIL=1 && + test_must_fail $flux_imp exec echo foo >badhelper.log 2>&1 + ) && + test_debug "cat badhelper.log" && + grep "helper.*failed" badhelper.log +' test_expect_success 'flux-imp exec fails when signature type not allowed' ' ( export FLUX_IMP_CONFIG_PATTERN=./sign-none-allowed-munge.toml && fake_imp_input foo | \ @@ -166,7 +236,7 @@ test_expect_success SUDO,NO_CHAIN_LINT 'flux-imp exec: setuid IMP lingers' ' count=0 && while ! test -f sleeper.pid; do sleep 0.1 - let count++ + count=$((count+1)) test $count -gt 20 && break test_debug "echo retrying count=${count}" done &&