1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-01-09 17:38: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;
for (i = 0; i < p->n_prompts; i++) {
if (promptsgot < nprompts) {
p->prompts[i]->result = dupstr(prompts[promptsgot++]);
prompt_set_result(p->prompts[i], prompts[promptsgot++]);
if (cgtest_verbose)
printf(" prompt \"%s\": response \"%s\"\n",
p->prompts[i]->prompt, p->prompts[i]->result);
p->prompts[i]->prompt, p->prompts[i]->result->s);
} else {
promptsgot++; /* track number of requests anyway */
ret = 0;
@ -777,7 +777,7 @@ int main(int argc, char **argv)
perror("puttygen: unable to read passphrase");
RETURN(1);
} else {
old_passphrase = dupstr(p->prompts[0]->result);
old_passphrase = prompt_get_result(p->prompts[0]);
free_prompts(p);
}
}
@ -921,12 +921,13 @@ int main(int argc, char **argv)
perror("puttygen: unable to read new passphrase");
RETURN(1);
} 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);
fprintf(stderr, "puttygen: passphrases do not match\n");
RETURN(1);
}
new_passphrase = dupstr(p->prompts[0]->result);
new_passphrase = prompt_get_result(p->prompts[0]);
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);
pr->prompt = promptstr;
pr->echo = echo;
pr->result = NULL;
pr->resultsize = 0;
pr->result = strbuf_new_nm();
sgrowarray(p->prompts, p->prompts_size, p->n_prompts);
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)
{
prompt_ensure_result_size(pr, strlen(newstr) + 1);
strcpy(pr->result, newstr);
strbuf_clear(pr->result);
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)
{
size_t i;
for (i=0; i < p->n_prompts; i++) {
prompt_t *pr = p->prompts[i];
smemclr(pr->result, pr->resultsize); /* burn the evidence */
sfree(pr->result);
strbuf_free(pr->result);
sfree(pr->prompt);
sfree(pr);
}

18
putty.h
View File

@ -636,19 +636,7 @@ GLOBAL char *cmdline_session_name;
typedef struct {
char *prompt;
bool echo;
/*
* '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;
strbuf *result;
} prompt_t;
typedef struct {
/*
@ -682,8 +670,8 @@ typedef struct {
prompts_t *new_prompts();
void add_prompt(prompts_t *p, char *promptstr, bool echo);
void prompt_set_result(prompt_t *pr, const char *newstr);
void prompt_ensure_result_size(prompt_t *pr, int len);
/* Burn the evidence. (Assumes _all_ strings want free()ing.) */
char *prompt_get_result(prompt_t *pr);
const char *prompt_get_result_ref(prompt_t *pr);
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) {
/* Next terminal output will come from server */
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) {
/* Next terminal output will come from server */
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
* 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");
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);
s->cur_prompt = NULL;
}
@ -676,7 +676,7 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl)
"User aborted at passphrase prompt");
return;
}
passphrase = dupstr(s->cur_prompt->prompts[0]->result);
passphrase = prompt_get_result(s->cur_prompt->prompts[0]);
free_prompts(s->cur_prompt);
s->cur_prompt = NULL;
}
@ -987,8 +987,10 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl)
* we can use the primary defence.
*/
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) {
bottom = 0; /* zero length passwords are OK! :-) */
top = 15;
@ -1002,7 +1004,7 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl)
for (i = bottom; i <= top; i++) {
if (i == pwlen) {
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);
} else {
strbuf *random_data = strbuf_new_nm();
@ -1025,7 +1027,8 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl)
ppl_logevent("Sending length-padded password");
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;
random_read(strbuf_append(padded_pw, pad), pad);
put_stringsb(pkt, padded_pw);
@ -1037,12 +1040,13 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl)
*/
ppl_logevent("Sending unpadded password");
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);
}
} else {
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);
}
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 */
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);
} else {
if ((flags & FLAG_VERBOSE) || (flags & FLAG_INTERACTIVE))
@ -882,7 +882,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
return;
}
passphrase =
dupstr(s->cur_prompt->prompts[0]->result);
prompt_get_result(s->cur_prompt->prompts[0]);
free_prompts(s->cur_prompt);
} else {
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);
put_uint32(s->pktout, s->num_prompts);
for (uint32_t i = 0; i < s->num_prompts; i++) {
put_stringz(s->pktout,
s->cur_prompt->prompts[i]->result);
put_stringz(s->pktout, prompt_get_result_ref(
s->cur_prompt->prompts[i]));
}
s->pktout->minlen = 256;
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
* 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);
/*
@ -1581,20 +1581,20 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
* (A side effect is that the user doesn't have to
* 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));
/* burn the evidence */
sfree(s->password);
s->password =
dupstr(s->cur_prompt->prompts[0]->result);
s->password = prompt_get_result(
s->cur_prompt->prompts[0]);
}
/*
* Check the two new passwords match.
*/
got_new = (strcmp(s->cur_prompt->prompts[1]->result,
s->cur_prompt->prompts[2]->result)
== 0);
got_new = !strcmp(
prompt_get_result_ref(s->cur_prompt->prompts[1]),
prompt_get_result_ref(s->cur_prompt->prompts[2]));
if (!got_new)
/* They don't. Silly user. */
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_bool(s->pktout, true);
put_stringz(s->pktout, s->password);
put_stringz(s->pktout,
s->cur_prompt->prompts[1]->result);
put_stringz(s->pktout, prompt_get_result_ref(
s->cur_prompt->prompts[1]));
free_prompts(s->cur_prompt);
s->pktout->minlen = 256;
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 {
size_t curr_prompt;
bool done_prompt; /* printed out prompt yet? */
size_t pos; /* cursor position */
};
/* 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) {
term_write(term, ptrlen_from_asciz(pr->prompt));
s->done_prompt = true;
s->pos = 0;
}
/* 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 13:
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 */
s->curr_prompt++;
s->done_prompt = false;
@ -7235,18 +7231,18 @@ int term_get_userpass_input(Terminal *term, prompts_t *p, bufchain *input)
break;
case 8:
case 127:
if (s->pos > 0) {
if (pr->result->len > 0) {
if (pr->echo)
term_write(term, PTRLEN_LITERAL("\b \b"));
s->pos--;
strbuf_shrink_by(pr->result, 1);
}
break;
case 21:
case 27:
while (s->pos > 0) {
while (pr->result->len > 0) {
if (pr->echo)
term_write(term, PTRLEN_LITERAL("\b \b"));
s->pos--;
strbuf_shrink_by(pr->result, 1);
}
break;
case 3:
@ -7264,8 +7260,7 @@ int term_get_userpass_input(Terminal *term, prompts_t *p, bufchain *input)
*/
if (!pr->echo || (c >= ' ' && c <= '~') ||
((unsigned char) c >= 160)) {
prompt_ensure_result_size(pr, s->pos + 1);
pr->result[s->pos++] = c;
put_byte(pr->result, c);
if (pr->echo)
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++) {
struct termios oldmode, newmode;
int len;
prompt_t *pr = p->prompts[curr_prompt];
tcgetattr(infd, &oldmode);
@ -580,19 +579,21 @@ int console_get_userpass_input(prompts_t *p)
console_write(outfp, ptrlen_from_asciz(pr->prompt));
len = 0;
bool failed = false;
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) {
len = -1;
failed = true;
break;
}
len += ret;
if (pr->result[len - 1] == '\n') {
len--;
strbuf_shrink_to(pr->result, prev_result_len + ret);
if (pr->result->s[pr->result->len - 1] == '\n') {
strbuf_shrink_by(pr->result, 1);
break;
}
}
@ -602,12 +603,10 @@ int console_get_userpass_input(prompts_t *p)
if (!pr->echo)
console_write(outfp, PTRLEN_LITERAL("\n"));
if (len < 0) {
if (failed) {
console_close(outfp, infd);
return 0; /* failure due to read error */
}
pr->result[len] = '\0';
}
console_close(outfp, infd);

View File

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