doas: add optional timeout to persist directive

Anthony BOCCI <[email protected]> Sat, 01 Aug 2026 15:26:06 +0000
Newsgroups gmane.os.openbsd.tech
Message-ID <LF7o3tepEYpCVCC6-qUfCAI3hVXquoKj8ItcKES-YOHOC2ca7TGTLOELzCq1HEiEIw-rvQ-5o1ai8oOEcYmhAn4q7DtR5K1JMOCvyZA8MkE=@abosec.fr>
This is an OpenPGP/MIME signed message (RFC 4880 and 3156)
--------d7653d291080ff3bf85f325963c9b4f0ff79b206cc08b63065646101fcc405fc
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain;charset=utf-8


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 =3D -1;

@@ -258,7 +258,7 @@ authuser(char *myname, char *login_style, int persist)
 	exit(1);
 good:
 	if (fd !=3D -1) {
-		int secs =3D 5 * 60;
+		int secs =3D (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->time=
out);
 	}

 	if ((p =3D getenv("PATH")) !=3D 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 =3D $1.action;
 			r->options =3D $1.options;
 			r->envlist =3D $1.envlist;
+			r->timeout =3D $1.timeout;
 			r->ident =3D $2.str;
 			r->target =3D $3.str;
 			r->cmd =3D $4.cmd;
@@ -111,6 +114,7 @@ action:		TPERMIT options {
 			$$.action =3D PERMIT;
 			$$.options =3D $2.options;
 			$$.envlist =3D $2.envlist;
+			$$.timeout =3D $2.timeout;
 		} | TDENY {
 			$$.action =3D DENY;
 			$$.options =3D 0;
@@ -120,9 +124,11 @@ action:		TPERMIT options {
 options:	/* none */ {
 			$$.options =3D 0;
 			$$.envlist =3D NULL;
+			$$.timeout =3D 0;
 		} | options option {
 			$$.options =3D $1.options | $2.options;
 			$$.envlist =3D $1.envlist;
+			$$.timeout =3D ($2.timeout > 0) ? $2.timeout : $1.timeout;
 			if (($$.options & (NOPASS|PERSIST)) =3D=3D (NOPASS|PERSIST)) {
 				yyerror("can't combine nopass and persist");
 				YYERROR;
@@ -144,6 +150,11 @@ option:		TNOPASS {
 		} | TPERSIST {
 			$$.options =3D PERSIST;
 			$$.envlist =3D NULL;
+			$$.timeout =3D 0;
+		} | TPERSIST TNUMBER {
+			$$.options =3D PERSIST;
+			$$.envlist =3D NULL;
+			$$.timeout =3D $2.timeout;
 		} | TKEEPENV {
 			$$.options =3D KEEPENV;
 			$$.envlist =3D 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 =3D 0, quotes =3D 0, qerr =3D 0, escape =3D 0, nonkw =3D 0;
 	unsigned long qpos =3D 0;
 	size_t i;
+	long timeout =3D 0;

 	p =3D buf;
 	ebuf =3D buf + sizeof(buf);
@@ -342,6 +354,14 @@ eow:
 				return keywords[i].token;
 		}
 	}
+	if (isdigit(buf[0])) {
+		errno =3D 0;
+		timeout =3D strtol(buf, &endptr, 10);
+		if (errno !=3D ERANGE && timeout >=3D 0 && timeout <=3D INT_MAX && *endp=
tr =3D=3D '\0') {
+			yylval.timeout =3D (int)timeout;
+			return TNUMBER;
+		}
+	}
 	if ((str =3D strdup(buf)) =3D=3D NULL)
 		err(1, "%s", __func__);
 	yylval.str =3D str;




--
Anthony=

--------d7653d291080ff3bf85f325963c9b4f0ff79b206cc08b63065646101fcc405fc
Content-Type: application/pgp-signature; name="signature.asc"
Content-Description: OpenPGP digital signature
Content-Disposition: attachment; filename="signature.asc"

-----BEGIN PGP SIGNATURE-----
Version: ProtonMail

wrsEARYKAG0FgmpuD/8JEAEjrKiRlre2RRQAAAAAABwAIHNhbHRAbm90YXRp
b25zLm9wZW5wZ3Bqcy5vcmeL6cBle4BX6LG+xuN5pmgiamdVVG7u15zxDKKG
y991pxYhBDpU3BNSwCzjKhgxRwEjrKiRlre2AADJzQD/SRUy7L3Nz8NSmvRz
XdibzgR2gO3eEdFbtt2A1pNY4pAA/1CQQU1WlUJdmiPbCcLoP7nfhnQ2CICg
2L6gVE8XoX8J
=ieFz
-----END PGP SIGNATURE-----


--------d7653d291080ff3bf85f325963c9b4f0ff79b206cc08b63065646101fcc405fc--