Index | Thread | Search

From:
Anthony BOCCI <anthony@abosec.fr>
Subject:
doas: add optional timeout to persist directive
To:
"tech@openbsd.org" <tech@openbsd.org>
Date:
Sat, 01 Aug 2026 15:26:06 +0000

Download raw body.

Thread
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 <stdio.h>
 #include <string.h>
 #include <err.h>
+#include <errno.h>

 #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