From: Anthony BOCCI Subject: doas: add optional timeout to persist directive To: "tech@openbsd.org" Date: Sat, 01 Aug 2026 15:26:06 +0000 Hi, Currently, the persist timeout in doas is hard-coded to 5 minutes. In some situations, a shorter timeout is desirable to reduce the window during which a compromised session could be abused without re-authentication. This patch adds an optional argument to the persist keyword in doas.conf, allowing the user to specify the duration in seconds before re-authentication is required. The default remains 300 seconds when no argument is provided, so existing configurations are unaffected. Example: permit persist 30 :wheel --- usr.bin/doas/doas.c +++ usr.bin/doas/doas.c @@ -241,7 +241,7 @@ authuser_checkpass(char *myname, char *login_style) } static void -authuser(char *myname, char *login_style, int persist) +authuser(char *myname, char *login_style, int persist, int timeout) { int i, fd = -1; @@ -258,7 +258,7 @@ authuser(char *myname, char *login_style, int persist) exit(1); good: if (fd != -1) { - int secs = 5 * 60; + int secs = (timeout > 0) ? timeout : 5 * 60; ioctl(fd, TIOCSETVERAUTH, &secs); close(fd); } @@ -427,7 +427,7 @@ main(int argc, char **argv) if (nflag) errx(1, "Authentication required"); - authuser(mypw->pw_name, login_style, rule->options & PERSIST); + authuser(mypw->pw_name, login_style, rule->options & PERSIST, rule->timeout); } if ((p = getenv("PATH")) != NULL) diff --git usr.bin/doas/doas.conf.5 usr.bin/doas/doas.conf.5 index c547366ab0b..c2976d0a48b 100644 --- usr.bin/doas/doas.conf.5 +++ usr.bin/doas/doas.conf.5 @@ -48,9 +48,9 @@ The user is not required to enter a password. .It Ic nolog Do not log successful command execution to .Xr syslogd 8 . -.It Ic persist +.It Ic persist Op Ar N After the user successfully authenticates, do not ask for a password -again for some time. +again for N seconds, the default is 5 minutes. .It Ic keepenv Environment variables other than those listed in .Xr doas 1 diff --git usr.bin/doas/doas.h usr.bin/doas/doas.h index ce6a03618ac..bc9b4fd129c 100644 --- usr.bin/doas/doas.h +++ usr.bin/doas/doas.h @@ -18,6 +18,7 @@ struct rule { int action; int options; + int timeout; const char *ident; const char *target; const char *cmd; diff --git usr.bin/doas/parse.y usr.bin/doas/parse.y index 604becb5445..f8902b923b4 100644 --- usr.bin/doas/parse.y +++ usr.bin/doas/parse.y @@ -25,6 +25,7 @@ #include #include #include +#include #include "doas.h" @@ -33,6 +34,7 @@ typedef struct { struct { int action; int options; + int timeout; const char *cmd; const char **cmdargs; const char **envlist; @@ -72,7 +74,7 @@ arraylen(const char **arr) %token TPERMIT TDENY TAS TCMD TARGS %token TNOPASS TNOLOG TPERSIST TKEEPENV TSETENV -%token TSTRING +%token TSTRING TNUMBER %% @@ -91,6 +93,7 @@ rule: action ident target cmd { r->action = $1.action; r->options = $1.options; r->envlist = $1.envlist; + r->timeout = $1.timeout; r->ident = $2.str; r->target = $3.str; r->cmd = $4.cmd; @@ -111,6 +114,7 @@ action: TPERMIT options { $$.action = PERMIT; $$.options = $2.options; $$.envlist = $2.envlist; + $$.timeout = $2.timeout; } | TDENY { $$.action = DENY; $$.options = 0; @@ -120,9 +124,11 @@ action: TPERMIT options { options: /* none */ { $$.options = 0; $$.envlist = NULL; + $$.timeout = 0; } | options option { $$.options = $1.options | $2.options; $$.envlist = $1.envlist; + $$.timeout = ($2.timeout > 0) ? $2.timeout : $1.timeout; if (($$.options & (NOPASS|PERSIST)) == (NOPASS|PERSIST)) { yyerror("can't combine nopass and persist"); YYERROR; @@ -144,6 +150,11 @@ option: TNOPASS { } | TPERSIST { $$.options = PERSIST; $$.envlist = NULL; + $$.timeout = 0; + } | TPERSIST TNUMBER { + $$.options = PERSIST; + $$.envlist = NULL; + $$.timeout = $2.timeout; } | TKEEPENV { $$.options = KEEPENV; $$.envlist = NULL; @@ -224,10 +235,11 @@ static struct keyword { int yylex(void) { - char buf[1024], *ebuf, *p, *str; + char buf[1024], *ebuf, *p, *str, *endptr; int c, quoted = 0, quotes = 0, qerr = 0, escape = 0, nonkw = 0; unsigned long qpos = 0; size_t i; + long timeout = 0; p = buf; ebuf = buf + sizeof(buf); @@ -342,6 +354,14 @@ eow: return keywords[i].token; } } + if (isdigit(buf[0])) { + errno = 0; + timeout = strtol(buf, &endptr, 10); + if (errno != ERANGE && timeout >= 0 && timeout <= INT_MAX && *endptr == '\0') { + yylval.timeout = (int)timeout; + return TNUMBER; + } + } if ((str = strdup(buf)) == NULL) err(1, "%s", __func__); yylval.str = str; -- Anthony