From fa9b1e74393f899cd7ac3b05c6141f89531114ba Mon Sep 17 00:00:00 2001 From: woahwhattheheck Date: Sun, 20 Sep 2026 23:25:34 -0400 Subject: [PATCH 1/2] readpass: reject overlong passphrase input --- libcperciva/util/readpass.c | 68 ++++++++++++++++++++++++++++++------- 1 file changed, 56 insertions(+), 12 deletions(-) diff --git a/libcperciva/util/readpass.c b/libcperciva/util/readpass.c index 3683aa1f..ed95b54c 100644 --- a/libcperciva/util/readpass.c +++ b/libcperciva/util/readpass.c @@ -54,6 +54,60 @@ resetsigs(struct sigaction savedsa[NSIGS]) } } +/* + * Read one password line without silently accepting a truncated prefix. + * fgets(3) can fill the buffer before consuming the line terminator; in that + * case inspect the next byte and reject any non-terminator suffix. + */ +static int +readpass_readline(char * buf, size_t buflen, FILE * f) +{ + size_t len; + int ch; + int next; + + if (fgets(buf, buflen, f) == NULL) { + if (feof(f)) + warn0("EOF reading password"); + else + warnp("Cannot read password"); + return (-1); + } + + /* A short read, or a line ending already in the buffer, is complete. */ + len = strcspn(buf, "\r\n"); + if (len < buflen - 1) + return (0); + + /* The buffer is full and contains no line terminator. */ + if ((ch = fgetc(f)) == EOF) { + if (ferror(f)) { + warnp("Cannot read password"); + return (-1); + } + return (0); + } + if (ch == '\n') + return (0); + if (ch == '\r') { + if ((next = fgetc(f)) == EOF) { + if (ferror(f)) { + warnp("Cannot read password"); + return (-1); + } + return (0); + } + if ((next != '\n') && (ungetc(next, f) == EOF)) { + warnp("Cannot unread password input"); + return (-1); + } + return (0); + } + + warn0("Password is too long"); + return (-1); +} + /** * readpass(passwd, prompt, confirmprompt, devtty): * If ${devtty} is 0, read a password from stdin. If ${devtty} is 1, read a @@ -136,25 +190,15 @@ readpass(char ** passwd, const char * prompt, fprintf(stderr, "%s: ", prompt); /* Read the password. */ - if (fgets(passbuf, MAXPASSLEN, readfrom) == NULL) { - if (feof(readfrom)) - warn0("EOF reading password"); - else - warnp("Cannot read password"); + if (readpass_readline(passbuf, MAXPASSLEN, readfrom)) goto err3; - } /* Confirm the password if necessary. */ if (confirmprompt != NULL) { if (usingtty) fprintf(stderr, "%s: ", confirmprompt); - if (fgets(confpassbuf, MAXPASSLEN, readfrom) == NULL) { - if (feof(readfrom)) - warn0("EOF reading password"); - else - warnp("Cannot read password"); + if (readpass_readline(confpassbuf, MAXPASSLEN, readfrom)) goto err3; - } if (strcmp(passbuf, confpassbuf)) { fprintf(stderr, "Passwords mismatch, please try again\n"); From b431b2e4c72a35421b408ad541d1d546efebddc1 Mon Sep 17 00:00:00 2001 From: woahwhattheheck Date: Sun, 20 Sep 2026 23:34:43 -0400 Subject: [PATCH 2/2] readpass: drain CR-terminated input before confirmation --- libcperciva/util/readpass.c | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/libcperciva/util/readpass.c b/libcperciva/util/readpass.c index ed95b54c..7df97e7d 100644 --- a/libcperciva/util/readpass.c +++ b/libcperciva/util/readpass.c @@ -90,15 +90,11 @@ readpass_readline(char * buf, size_t buflen, FILE * f) if (ch == '\n') return (0); if (ch == '\r') { - if ((next = fgetc(f)) == EOF) { - if (ferror(f)) { - warnp("Cannot read password"); - return (-1); - } - return (0); - } - if ((next != '\n') && (ungetc(next, f) == EOF)) { - warnp("Cannot unread password input"); + do { + next = fgetc(f); + } while ((next != '\n') && (next != EOF)); + if ((next == EOF) && ferror(f)) { + warnp("Cannot read password"); return (-1); } return (0);