1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-01-10 01:48:00 +00:00

Use strbuf to store results in prompts_t.

UBsan pointed out another memcpy from NULL (again with length 0) in
the prompts_t system. When I looked at it, I realised that firstly
prompt_ensure_result_size was an early not-so-good implementation of
sgrowarray_nm that would benefit from being replaced with a call to
the real one, and secondly, the whole system for storing prompt
results should really have been replaced with strbufs with the no-move
option, because that's doing all the same jobs better.

So, now each prompt_t holds a strbuf in place of its previous manually
managed string. prompt_ensure_result_size is gone (the console
prompt-reading functions use strbuf_append, and everything else just
adds to the strbuf in the usual marshal.c way). New functions exist to
retrieve a prompt_t's result, either by reference or copied.
This commit is contained in:
Simon Tatham 2020-01-21 20:19:47 +00:00
parent 5891142aee
commit cd6bc14f04
10 changed files with 80 additions and 107 deletions

View File

@ -53,10 +53,10 @@ int console_get_userpass_input(prompts_t *p)
int ret = 1; int ret = 1;
for (i = 0; i < p->n_prompts; i++) { for (i = 0; i < p->n_prompts; i++) {
if (promptsgot < nprompts) { if (promptsgot < nprompts) {
p->prompts[i]->result = dupstr(prompts[promptsgot++]); prompt_set_result(p->prompts[i], prompts[promptsgot++]);
if (cgtest_verbose) if (cgtest_verbose)
printf(" prompt \"%s\": response \"%s\"\n", printf(" prompt \"%s\": response \"%s\"\n",
p->prompts[i]->prompt, p->prompts[i]->result); p->prompts[i]->prompt, p->prompts[i]->result->s);
} else { } else {
promptsgot++; /* track number of requests anyway */ promptsgot++; /* track number of requests anyway */
ret = 0; ret = 0;
@ -777,7 +777,7 @@ int main(int argc, char **argv)
perror("puttygen: unable to read passphrase"); perror("puttygen: unable to read passphrase");
RETURN(1); RETURN(1);
} else { } else {
old_passphrase = dupstr(p->prompts[0]->result); old_passphrase = prompt_get_result(p->prompts[0]);
free_prompts(p); free_prompts(p);
} }
} }
@ -921,12 +921,13 @@ int main(int argc, char **argv)
perror("puttygen: unable to read new passphrase"); perror("puttygen: unable to read new passphrase");
RETURN(1); RETURN(1);
} else { } else {
if (strcmp(p->prompts[0]->result, p->prompts[1]->result)) { if (strcmp(prompt_get_result_ref(p->prompts[0]),
prompt_get_result_ref(p->prompts[1]))) {
free_prompts(p); free_prompts(p);
fprintf(stderr, "puttygen: passphrases do not match\n"); fprintf(stderr, "puttygen: passphrases do not match\n");
RETURN(1); RETURN(1);
} }
new_passphrase = dupstr(p->prompts[0]->result); new_passphrase = prompt_get_result(p->prompts[0]);
free_prompts(p); free_prompts(p);
} }
} }

38
misc.c
View File

@ -51,43 +51,29 @@ void add_prompt(prompts_t *p, char *promptstr, bool echo)
prompt_t *pr = snew(prompt_t); prompt_t *pr = snew(prompt_t);
pr->prompt = promptstr; pr->prompt = promptstr;
pr->echo = echo; pr->echo = echo;
pr->result = NULL; pr->result = strbuf_new_nm();
pr->resultsize = 0;
sgrowarray(p->prompts, p->prompts_size, p->n_prompts); sgrowarray(p->prompts, p->prompts_size, p->n_prompts);
p->prompts[p->n_prompts++] = pr; p->prompts[p->n_prompts++] = pr;
} }
void prompt_ensure_result_size(prompt_t *pr, int newlen)
{
if ((int)pr->resultsize < newlen) {
char *newbuf;
newlen = newlen * 5 / 4 + 512; /* avoid too many small allocs */
/*
* We don't use sresize / realloc here, because we will be
* storing sensitive stuff like passwords in here, and we want
* to make sure that the data doesn't get copied around in
* memory without the old copy being destroyed.
*/
newbuf = snewn(newlen, char);
memcpy(newbuf, pr->result, pr->resultsize);
smemclr(pr->result, pr->resultsize);
sfree(pr->result);
pr->result = newbuf;
pr->resultsize = newlen;
}
}
void prompt_set_result(prompt_t *pr, const char *newstr) void prompt_set_result(prompt_t *pr, const char *newstr)
{ {
prompt_ensure_result_size(pr, strlen(newstr) + 1); strbuf_clear(pr->result);
strcpy(pr->result, newstr); put_datapl(pr->result, ptrlen_from_asciz(newstr));
}
const char *prompt_get_result_ref(prompt_t *pr)
{
return pr->result->s;
}
char *prompt_get_result(prompt_t *pr)
{
return dupstr(pr->result->s);
} }
void free_prompts(prompts_t *p) void free_prompts(prompts_t *p)
{ {
size_t i; size_t i;
for (i=0; i < p->n_prompts; i++) { for (i=0; i < p->n_prompts; i++) {
prompt_t *pr = p->prompts[i]; prompt_t *pr = p->prompts[i];
smemclr(pr->result, pr->resultsize); /* burn the evidence */ strbuf_free(pr->result);
sfree(pr->result);
sfree(pr->prompt); sfree(pr->prompt);
sfree(pr); sfree(pr);
} }

18
putty.h
View File

@ -636,19 +636,7 @@ GLOBAL char *cmdline_session_name;
typedef struct { typedef struct {
char *prompt; char *prompt;
bool echo; bool echo;
/* strbuf *result;
* 'result' must be a dynamically allocated array of exactly
* 'resultsize' chars. The code for actually reading input may
* realloc it bigger (and adjust resultsize accordingly) if it has
* to. The caller should free it again when finished with it.
*
* If resultsize==0, then result may be NULL. When setting up a
* prompt_t, it's therefore easiest to initialise them this way,
* which means all actual allocation is done by the callee. This
* is what add_prompt does.
*/
char *result;
size_t resultsize;
} prompt_t; } prompt_t;
typedef struct { typedef struct {
/* /*
@ -682,8 +670,8 @@ typedef struct {
prompts_t *new_prompts(); prompts_t *new_prompts();
void add_prompt(prompts_t *p, char *promptstr, bool echo); void add_prompt(prompts_t *p, char *promptstr, bool echo);
void prompt_set_result(prompt_t *pr, const char *newstr); void prompt_set_result(prompt_t *pr, const char *newstr);
void prompt_ensure_result_size(prompt_t *pr, int len); char *prompt_get_result(prompt_t *pr);
/* Burn the evidence. (Assumes _all_ strings want free()ing.) */ const char *prompt_get_result_ref(prompt_t *pr);
void free_prompts(prompts_t *p); void free_prompts(prompts_t *p);
/* /*

View File

@ -237,7 +237,8 @@ static const char *rlogin_init(Seat *seat, Backend **backend_handle,
if (ret >= 0) { if (ret >= 0) {
/* Next terminal output will come from server */ /* Next terminal output will come from server */
seat_set_trust_status(rlogin->seat, false); seat_set_trust_status(rlogin->seat, false);
rlogin_startup(rlogin, rlogin->prompt->prompts[0]->result); rlogin_startup(rlogin, prompt_get_result_ref(
rlogin->prompt->prompts[0]));
} }
} }
@ -286,7 +287,8 @@ static size_t rlogin_send(Backend *be, const char *buf, size_t len)
if (ret >= 0) { if (ret >= 0) {
/* Next terminal output will come from server */ /* Next terminal output will come from server */
seat_set_trust_status(rlogin->seat, false); seat_set_trust_status(rlogin->seat, false);
rlogin_startup(rlogin, rlogin->prompt->prompts[0]->result); rlogin_startup(rlogin, prompt_get_result_ref(
rlogin->prompt->prompts[0]));
/* that nulls out rlogin->prompt, so then we'll start sending /* that nulls out rlogin->prompt, so then we'll start sending
* data down the wire in the obvious way */ * data down the wire in the obvious way */
} }

View File

@ -416,7 +416,7 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl)
ssh_user_close(s->ppl.ssh, "No username provided"); ssh_user_close(s->ppl.ssh, "No username provided");
return; return;
} }
s->username = dupstr(s->cur_prompt->prompts[0]->result); s->username = prompt_get_result(s->cur_prompt->prompts[0]);
free_prompts(s->cur_prompt); free_prompts(s->cur_prompt);
s->cur_prompt = NULL; s->cur_prompt = NULL;
} }
@ -676,7 +676,7 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl)
"User aborted at passphrase prompt"); "User aborted at passphrase prompt");
return; return;
} }
passphrase = dupstr(s->cur_prompt->prompts[0]->result); passphrase = prompt_get_result(s->cur_prompt->prompts[0]);
free_prompts(s->cur_prompt); free_prompts(s->cur_prompt);
s->cur_prompt = NULL; s->cur_prompt = NULL;
} }
@ -987,8 +987,10 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl)
* we can use the primary defence. * we can use the primary defence.
*/ */
int bottom, top, pwlen, i; int bottom, top, pwlen, i;
const char *pw = prompt_get_result_ref(
s->cur_prompt->prompts[0]);
pwlen = strlen(s->cur_prompt->prompts[0]->result); pwlen = strlen(pw);
if (pwlen < 16) { if (pwlen < 16) {
bottom = 0; /* zero length passwords are OK! :-) */ bottom = 0; /* zero length passwords are OK! :-) */
top = 15; top = 15;
@ -1002,7 +1004,7 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl)
for (i = bottom; i <= top; i++) { for (i = bottom; i <= top; i++) {
if (i == pwlen) { if (i == pwlen) {
pkt = ssh_bpp_new_pktout(s->ppl.bpp, s->pwpkt_type); pkt = ssh_bpp_new_pktout(s->ppl.bpp, s->pwpkt_type);
put_stringz(pkt, s->cur_prompt->prompts[0]->result); put_stringz(pkt, pw);
pq_push(s->ppl.out_pq, pkt); pq_push(s->ppl.out_pq, pkt);
} else { } else {
strbuf *random_data = strbuf_new_nm(); strbuf *random_data = strbuf_new_nm();
@ -1025,7 +1027,8 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl)
ppl_logevent("Sending length-padded password"); ppl_logevent("Sending length-padded password");
pkt = ssh_bpp_new_pktout(s->ppl.bpp, s->pwpkt_type); pkt = ssh_bpp_new_pktout(s->ppl.bpp, s->pwpkt_type);
put_asciz(padded_pw, s->cur_prompt->prompts[0]->result); put_asciz(padded_pw, prompt_get_result_ref(
s->cur_prompt->prompts[0]));
size_t pad = 63 & -padded_pw->len; size_t pad = 63 & -padded_pw->len;
random_read(strbuf_append(padded_pw, pad), pad); random_read(strbuf_append(padded_pw, pad), pad);
put_stringsb(pkt, padded_pw); put_stringsb(pkt, padded_pw);
@ -1037,12 +1040,13 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl)
*/ */
ppl_logevent("Sending unpadded password"); ppl_logevent("Sending unpadded password");
pkt = ssh_bpp_new_pktout(s->ppl.bpp, s->pwpkt_type); pkt = ssh_bpp_new_pktout(s->ppl.bpp, s->pwpkt_type);
put_stringz(pkt, s->cur_prompt->prompts[0]->result); put_stringz(pkt, prompt_get_result_ref(
s->cur_prompt->prompts[0]));
pq_push(s->ppl.out_pq, pkt); pq_push(s->ppl.out_pq, pkt);
} }
} else { } else {
pkt = ssh_bpp_new_pktout(s->ppl.bpp, s->pwpkt_type); pkt = ssh_bpp_new_pktout(s->ppl.bpp, s->pwpkt_type);
put_stringz(pkt, s->cur_prompt->prompts[0]->result); put_stringz(pkt, prompt_get_result_ref(s->cur_prompt->prompts[0]));
pq_push(s->ppl.out_pq, pkt); pq_push(s->ppl.out_pq, pkt);
} }
ppl_logevent("Sent password"); ppl_logevent("Sent password");

View File

@ -430,7 +430,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
} }
sfree(s->locally_allocated_username); /* for change_username */ sfree(s->locally_allocated_username); /* for change_username */
s->username = s->locally_allocated_username = s->username = s->locally_allocated_username =
dupstr(s->cur_prompt->prompts[0]->result); prompt_get_result(s->cur_prompt->prompts[0]);
free_prompts(s->cur_prompt); free_prompts(s->cur_prompt);
} else { } else {
if ((flags & FLAG_VERBOSE) || (flags & FLAG_INTERACTIVE)) if ((flags & FLAG_VERBOSE) || (flags & FLAG_INTERACTIVE))
@ -882,7 +882,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
return; return;
} }
passphrase = passphrase =
dupstr(s->cur_prompt->prompts[0]->result); prompt_get_result(s->cur_prompt->prompts[0]);
free_prompts(s->cur_prompt); free_prompts(s->cur_prompt);
} else { } else {
passphrase = NULL; /* no passphrase needed */ passphrase = NULL; /* no passphrase needed */
@ -1372,8 +1372,8 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
s->ppl.bpp, SSH2_MSG_USERAUTH_INFO_RESPONSE); s->ppl.bpp, SSH2_MSG_USERAUTH_INFO_RESPONSE);
put_uint32(s->pktout, s->num_prompts); put_uint32(s->pktout, s->num_prompts);
for (uint32_t i = 0; i < s->num_prompts; i++) { for (uint32_t i = 0; i < s->num_prompts; i++) {
put_stringz(s->pktout, put_stringz(s->pktout, prompt_get_result_ref(
s->cur_prompt->prompts[i]->result); s->cur_prompt->prompts[i]));
} }
s->pktout->minlen = 256; s->pktout->minlen = 256;
pq_push(s->ppl.out_pq, s->pktout); pq_push(s->ppl.out_pq, s->pktout);
@ -1455,7 +1455,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
* Squirrel away the password. (We may need it later if * Squirrel away the password. (We may need it later if
* asked to change it.) * asked to change it.)
*/ */
s->password = dupstr(s->cur_prompt->prompts[0]->result); s->password = prompt_get_result(s->cur_prompt->prompts[0]);
free_prompts(s->cur_prompt); free_prompts(s->cur_prompt);
/* /*
@ -1581,20 +1581,20 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
* (A side effect is that the user doesn't have to * (A side effect is that the user doesn't have to
* re-enter it if they louse up the new password.) * re-enter it if they louse up the new password.)
*/ */
if (s->cur_prompt->prompts[0]->result[0]) { if (s->cur_prompt->prompts[0]->result->s[0]) {
smemclr(s->password, strlen(s->password)); smemclr(s->password, strlen(s->password));
/* burn the evidence */ /* burn the evidence */
sfree(s->password); sfree(s->password);
s->password = s->password = prompt_get_result(
dupstr(s->cur_prompt->prompts[0]->result); s->cur_prompt->prompts[0]);
} }
/* /*
* Check the two new passwords match. * Check the two new passwords match.
*/ */
got_new = (strcmp(s->cur_prompt->prompts[1]->result, got_new = !strcmp(
s->cur_prompt->prompts[2]->result) prompt_get_result_ref(s->cur_prompt->prompts[1]),
== 0); prompt_get_result_ref(s->cur_prompt->prompts[2]));
if (!got_new) if (!got_new)
/* They don't. Silly user. */ /* They don't. Silly user. */
ppl_printf("Passwords do not match\r\n"); ppl_printf("Passwords do not match\r\n");
@ -1612,8 +1612,8 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
put_stringz(s->pktout, "password"); put_stringz(s->pktout, "password");
put_bool(s->pktout, true); put_bool(s->pktout, true);
put_stringz(s->pktout, s->password); put_stringz(s->pktout, s->password);
put_stringz(s->pktout, put_stringz(s->pktout, prompt_get_result_ref(
s->cur_prompt->prompts[1]->result); s->cur_prompt->prompts[1]));
free_prompts(s->cur_prompt); free_prompts(s->cur_prompt);
s->pktout->minlen = 256; s->pktout->minlen = 256;
pq_push(s->ppl.out_pq, s->pktout); pq_push(s->ppl.out_pq, s->pktout);

View File

@ -7156,7 +7156,6 @@ char *term_get_ttymode(Terminal *term, const char *mode)
struct term_userpass_state { struct term_userpass_state {
size_t curr_prompt; size_t curr_prompt;
bool done_prompt; /* printed out prompt yet? */ bool done_prompt; /* printed out prompt yet? */
size_t pos; /* cursor position */
}; };
/* Tiny wrapper to make it easier to write lots of little strings */ /* Tiny wrapper to make it easier to write lots of little strings */
@ -7211,7 +7210,6 @@ int term_get_userpass_input(Terminal *term, prompts_t *p, bufchain *input)
if (!s->done_prompt) { if (!s->done_prompt) {
term_write(term, ptrlen_from_asciz(pr->prompt)); term_write(term, ptrlen_from_asciz(pr->prompt));
s->done_prompt = true; s->done_prompt = true;
s->pos = 0;
} }
/* Breaking out here ensures that the prompt is printed even /* Breaking out here ensures that the prompt is printed even
@ -7226,8 +7224,6 @@ int term_get_userpass_input(Terminal *term, prompts_t *p, bufchain *input)
case 10: case 10:
case 13: case 13:
term_write(term, PTRLEN_LITERAL("\r\n")); term_write(term, PTRLEN_LITERAL("\r\n"));
prompt_ensure_result_size(pr, s->pos + 1);
pr->result[s->pos] = '\0';
/* go to next prompt, if any */ /* go to next prompt, if any */
s->curr_prompt++; s->curr_prompt++;
s->done_prompt = false; s->done_prompt = false;
@ -7235,18 +7231,18 @@ int term_get_userpass_input(Terminal *term, prompts_t *p, bufchain *input)
break; break;
case 8: case 8:
case 127: case 127:
if (s->pos > 0) { if (pr->result->len > 0) {
if (pr->echo) if (pr->echo)
term_write(term, PTRLEN_LITERAL("\b \b")); term_write(term, PTRLEN_LITERAL("\b \b"));
s->pos--; strbuf_shrink_by(pr->result, 1);
} }
break; break;
case 21: case 21:
case 27: case 27:
while (s->pos > 0) { while (pr->result->len > 0) {
if (pr->echo) if (pr->echo)
term_write(term, PTRLEN_LITERAL("\b \b")); term_write(term, PTRLEN_LITERAL("\b \b"));
s->pos--; strbuf_shrink_by(pr->result, 1);
} }
break; break;
case 3: case 3:
@ -7264,8 +7260,7 @@ int term_get_userpass_input(Terminal *term, prompts_t *p, bufchain *input)
*/ */
if (!pr->echo || (c >= ' ' && c <= '~') || if (!pr->echo || (c >= ' ' && c <= '~') ||
((unsigned char) c >= 160)) { ((unsigned char) c >= 160)) {
prompt_ensure_result_size(pr, s->pos + 1); put_byte(pr->result, c);
pr->result[s->pos++] = c;
if (pr->echo) if (pr->echo)
term_write(term, make_ptrlen(&c, 1)); term_write(term, make_ptrlen(&c, 1));
} }

View File

@ -566,7 +566,6 @@ int console_get_userpass_input(prompts_t *p)
for (curr_prompt = 0; curr_prompt < p->n_prompts; curr_prompt++) { for (curr_prompt = 0; curr_prompt < p->n_prompts; curr_prompt++) {
struct termios oldmode, newmode; struct termios oldmode, newmode;
int len;
prompt_t *pr = p->prompts[curr_prompt]; prompt_t *pr = p->prompts[curr_prompt];
tcgetattr(infd, &oldmode); tcgetattr(infd, &oldmode);
@ -580,19 +579,21 @@ int console_get_userpass_input(prompts_t *p)
console_write(outfp, ptrlen_from_asciz(pr->prompt)); console_write(outfp, ptrlen_from_asciz(pr->prompt));
len = 0; bool failed = false;
while (1) { while (1) {
int ret; size_t toread = 65536;
size_t prev_result_len = pr->result->len;
void *ptr = strbuf_append(pr->result, toread);
int ret = read(infd, ptr, toread);
prompt_ensure_result_size(pr, len * 5 / 4 + 512);
ret = read(infd, pr->result + len, pr->resultsize - len - 1);
if (ret <= 0) { if (ret <= 0) {
len = -1; failed = true;
break; break;
} }
len += ret;
if (pr->result[len - 1] == '\n') { strbuf_shrink_to(pr->result, prev_result_len + ret);
len--; if (pr->result->s[pr->result->len - 1] == '\n') {
strbuf_shrink_by(pr->result, 1);
break; break;
} }
} }
@ -602,12 +603,10 @@ int console_get_userpass_input(prompts_t *p)
if (!pr->echo) if (!pr->echo)
console_write(outfp, PTRLEN_LITERAL("\n")); console_write(outfp, PTRLEN_LITERAL("\n"));
if (len < 0) { if (failed) {
console_close(outfp, infd); console_close(outfp, infd);
return 0; /* failure due to read error */ return 0; /* failure due to read error */
} }
pr->result[len] = '\0';
} }
console_close(outfp, infd); console_close(outfp, infd);

View File

@ -338,7 +338,7 @@ static char *askpass_tty(const char *prompt)
free_prompts(p); free_prompts(p);
return NULL; return NULL;
} else { } else {
char *passphrase = dupstr(p->prompts[0]->result); char *passphrase = prompt_get_result(p->prompts[0]);
free_prompts(p); free_prompts(p);
return passphrase; return passphrase;
} }

View File

@ -490,7 +490,6 @@ int console_get_userpass_input(prompts_t *p)
for (curr_prompt = 0; curr_prompt < p->n_prompts; curr_prompt++) { for (curr_prompt = 0; curr_prompt < p->n_prompts; curr_prompt++) {
DWORD savemode, newmode; DWORD savemode, newmode;
size_t len;
prompt_t *pr = p->prompts[curr_prompt]; prompt_t *pr = p->prompts[curr_prompt];
GetConsoleMode(hin, &savemode); GetConsoleMode(hin, &savemode);
@ -503,22 +502,23 @@ int console_get_userpass_input(prompts_t *p)
console_write(hout, ptrlen_from_asciz(pr->prompt)); console_write(hout, ptrlen_from_asciz(pr->prompt));
len = 0; bool failed = false;
while (1) { while (1) {
DWORD toread = 65536;
size_t prev_result_len = pr->result->len;
void *ptr = strbuf_append(pr->result, toread);
DWORD ret = 0; DWORD ret = 0;
if (!ReadFile(hin, ptr, toread, &ret, NULL) || ret == 0) {
prompt_ensure_result_size(pr, len * 5 / 4 + 512); failed = true;
if (!ReadFile(hin, pr->result + len, pr->resultsize - len - 1,
&ret, NULL) || ret == 0) {
len = (size_t)-1;
break; break;
} }
len += ret;
if (pr->result[len - 1] == '\n') { strbuf_shrink_to(pr->result, prev_result_len + ret);
len--; if (pr->result->s[pr->result->len - 1] == '\n') {
if (pr->result[len - 1] == '\r') strbuf_shrink_by(pr->result, 1);
len--; if (pr->result->s[pr->result->len - 1] == '\r')
strbuf_shrink_by(pr->result, 1);
break; break;
} }
} }
@ -528,11 +528,9 @@ int console_get_userpass_input(prompts_t *p)
if (!pr->echo) if (!pr->echo)
console_write(hout, PTRLEN_LITERAL("\r\n")); console_write(hout, PTRLEN_LITERAL("\r\n"));
if (len == (size_t)-1) { if (failed) {
return 0; /* failure due to read error */ return 0; /* failure due to read error */
} }
pr->result[len] = '\0';
} }
return 1; /* success */ return 1; /* success */