gdb: remove static buffer in command_line_input

[I sent this earlier today, but I don't see it in the archives.
Resending it through a different computer / SMTP.]

The use of the static buffer in command_line_input is becoming
problematic, as explained here [1].  In short, with this patch [2] that
attempt to fix a post-hook bug, when running gdb.base/commands.exp, we
hit a case where we read a "define" command line from a script file
using command_command_line_input.  The command line is stored in
command_line_input's static buffer.  Inside the define command's
execution, we read the lines inside the define using command_line_input,
which overwrites the define command, in command_line_input's static
buffer.  After the execution of the define command, execute_command does
a command look up to see if a post-hook is registered.  For that, it
uses a now stale pointer that used to point to the define command, in
the static buffer, causing a use-after-free.  Note that the pointer in
execute_command points to the dynamically-allocated buffer help by the
static buffer in command_line_input, not to the static object itself,
hence why we see a use-after-free.

Fix that by removing the static buffer.  I initially changed
command_line_input and other related functions to return an std::string,
which is the obvious but naive solution.  The thing is that some callees
don't need to return an allocated string, so this this an unnecessary
pessimization.  I changed it to passing in a reference to an std::string
buffer, which the callee can use if it needs to return
dynamically-allocated content.  It fills the buffer and returns a
pointers to the C string inside.  The callees that don't need to return
dynamically-allocated content simply don't use it.

So, it started with modifying command_line_input as described above, all
the other changes derive directly from that.

One slightly shady thing is in handle_line_of_input, where we now pass a
pointer to an std::string's internal buffer to readline's history_value
function, which takes a `char *`.  I'm pretty sure that this function
does not modify the input string, because I was able to change it (with
enough massaging) to take a `const char *`.

A subtle change is that we now clear a UI's line buffer using a
SCOPE_EXIT in command_line_handler, after executing the command.
This was previously done by this line in handle_line_of_input:

  /* We have a complete command line now.  Prepare for the next
     command, but leave ownership of memory to the buffer .  */
  cmd_line_buffer->used_size = 0;

I think the new way is clearer.

[1] https://inbox.sourceware.org/gdb-patches/becb8438-81ef-8ad8-cc42-fcbfaea8cddd@simark.ca/
[2] https://inbox.sourceware.org/gdb-patches/20221213112241.621889-1-jan.vrany@labware.com/

Change-Id: I8fc89b1c69870c7fc7ad9c1705724bd493596300
Reviewed-By: Tom Tromey <tom@tromey.com>
This commit is contained in:
Simon Marchi 2022-12-15 14:06:25 -05:00
parent ffd894b51d
commit f8631e5e04
13 changed files with 101 additions and 107 deletions

View file

@ -3561,7 +3561,8 @@ get_selections (int *choices, int n_choices, int max_results,
if (prompt == NULL) if (prompt == NULL)
prompt = "> "; prompt = "> ";
args = command_line_input (prompt, annotation_suffix); std::string buffer;
args = command_line_input (buffer, prompt, annotation_suffix);
if (args == NULL) if (args == NULL)
error_no_arg (_("one or more choice numbers")); error_no_arg (_("one or more choice numbers"));

View file

@ -13782,8 +13782,8 @@ strace_command (const char *arg, int from_tty)
static struct uploaded_tp *this_utp; static struct uploaded_tp *this_utp;
static int next_cmd; static int next_cmd;
static char * static const char *
read_uploaded_action (void) read_uploaded_action (std::string &buffer)
{ {
char *rslt = nullptr; char *rslt = nullptr;

View file

@ -44,7 +44,7 @@
static enum command_control_type static enum command_control_type
recurse_read_control_structure recurse_read_control_structure
(gdb::function_view<const char * ()> read_next_line_func, (read_next_line_ftype read_next_line_func,
struct command_line *current_cmd, struct command_line *current_cmd,
gdb::function_view<void (const char *)> validator); gdb::function_view<void (const char *)> validator);
@ -54,7 +54,7 @@ static void do_define_command (const char *comname, int from_tty,
static void do_document_command (const char *comname, int from_tty, static void do_document_command (const char *comname, int from_tty,
const counted_command_line *commands); const counted_command_line *commands);
static const char *read_next_line (); static const char *read_next_line (std::string &buffer);
/* Level of control structure when reading. */ /* Level of control structure when reading. */
static int control_level; static int control_level;
@ -894,7 +894,7 @@ user_args::insert_args (const char *line) const
from stdin. */ from stdin. */
static const char * static const char *
read_next_line () read_next_line (std::string &buffer)
{ {
struct ui *ui = current_ui; struct ui *ui = current_ui;
char *prompt_ptr, control_prompt[256]; char *prompt_ptr, control_prompt[256];
@ -917,7 +917,7 @@ read_next_line ()
else else
prompt_ptr = NULL; prompt_ptr = NULL;
return command_line_input (prompt_ptr, "commands"); return command_line_input (buffer, prompt_ptr, "commands");
} }
/* Given an input line P, skip the command and return a pointer to the /* Given an input line P, skip the command and return a pointer to the
@ -1064,7 +1064,7 @@ process_next_line (const char *p, command_line_up *command,
obtain lines of the command. */ obtain lines of the command. */
static enum command_control_type static enum command_control_type
recurse_read_control_structure (gdb::function_view<const char * ()> read_next_line_func, recurse_read_control_structure (read_next_line_ftype read_next_line_func,
struct command_line *current_cmd, struct command_line *current_cmd,
gdb::function_view<void (const char *)> validator) gdb::function_view<void (const char *)> validator)
{ {
@ -1085,8 +1085,9 @@ recurse_read_control_structure (gdb::function_view<const char * ()> read_next_li
{ {
dont_repeat (); dont_repeat ();
std::string buffer;
next = nullptr; next = nullptr;
val = process_next_line (read_next_line_func (), &next, val = process_next_line (read_next_line_func (buffer), &next,
current_cmd->control_type != python_control current_cmd->control_type != python_control
&& current_cmd->control_type != guile_control && current_cmd->control_type != guile_control
&& current_cmd->control_type != compile_control, && current_cmd->control_type != compile_control,
@ -1215,7 +1216,7 @@ read_command_lines (const char *prompt_arg, int from_tty, int parse_commands,
obtained using READ_NEXT_LINE_FUNC. */ obtained using READ_NEXT_LINE_FUNC. */
counted_command_line counted_command_line
read_command_lines_1 (gdb::function_view<const char * ()> read_next_line_func, read_command_lines_1 (read_next_line_ftype read_next_line_func,
int parse_commands, int parse_commands,
gdb::function_view<void (const char *)> validator) gdb::function_view<void (const char *)> validator)
{ {
@ -1231,7 +1232,9 @@ read_command_lines_1 (gdb::function_view<const char * ()> read_next_line_func,
while (1) while (1)
{ {
dont_repeat (); dont_repeat ();
val = process_next_line (read_next_line_func (), &next, parse_commands,
std::string buffer;
val = process_next_line (read_next_line_func (buffer), &next, parse_commands,
validator); validator);
/* Ignore blank lines or comments. */ /* Ignore blank lines or comments. */

View file

@ -112,11 +112,18 @@ private:
} }
}; };
/* Prototype for a function to call to get one more input line.
If the function needs to return a dynamically allocated string, it can place
in the passed-in buffer, and return a pointer to it. Otherwise, it can
simply ignore it. */
using read_next_line_ftype = gdb::function_view<const char * (std::string &)>;
extern counted_command_line read_command_lines extern counted_command_line read_command_lines
(const char *, int, int, gdb::function_view<void (const char *)>); (const char *, int, int, gdb::function_view<void (const char *)>);
extern counted_command_line read_command_lines_1 extern counted_command_line read_command_lines_1
(gdb::function_view<const char * ()>, int, (read_next_line_ftype, int, gdb::function_view<void (const char *)>);
gdb::function_view<void (const char *)>);
/* Exported to cli/cli-cmds.c */ /* Exported to cli/cli-cmds.c */

View file

@ -316,7 +316,8 @@ typedef void initialize_file_ftype (void);
extern char *gdb_readline_wrapper (const char *); extern char *gdb_readline_wrapper (const char *);
extern const char *command_line_input (const char *, const char *); extern const char *command_line_input (std::string &cmd_line_buffer,
const char *, const char *);
extern void print_prompt (void); extern void print_prompt (void);

View file

@ -483,13 +483,13 @@ struct ui *main_ui;
struct ui *current_ui; struct ui *current_ui;
struct ui *ui_list; struct ui *ui_list;
/* Get a pointer to the current UI's line buffer. This is used to /* Get a reference to the current UI's line buffer. This is used to
construct a whole line of input from partial input. */ construct a whole line of input from partial input. */
static struct buffer * static std::string &
get_command_line_buffer (void) get_command_line_buffer (void)
{ {
return &current_ui->line_buffer; return current_ui->line_buffer;
} }
/* When there is an event ready on the stdin file descriptor, instead /* When there is an event ready on the stdin file descriptor, instead
@ -620,46 +620,41 @@ command_handler (const char *command)
} }
} }
/* Append RL, an input line returned by readline or one of its /* Append RL, an input line returned by readline or one of its emulations, to
emulations, to CMD_LINE_BUFFER. Returns the command line if we CMD_LINE_BUFFER. Return true if we have a whole command line ready to be
have a whole command line ready to be processed by the command processed by the command interpreter or false if the command line isn't
interpreter or NULL if the command line isn't complete yet (input complete yet (input line ends in a backslash). */
line ends in a backslash). */
static char * static bool
command_line_append_input_line (struct buffer *cmd_line_buffer, const char *rl) command_line_append_input_line (std::string &cmd_line_buffer, const char *rl)
{ {
char *cmd; size_t len = strlen (rl);
size_t len;
len = strlen (rl);
if (len > 0 && rl[len - 1] == '\\') if (len > 0 && rl[len - 1] == '\\')
{ {
/* Don't copy the backslash and wait for more. */ /* Don't copy the backslash and wait for more. */
buffer_grow (cmd_line_buffer, rl, len - 1); cmd_line_buffer.append (rl, len - 1);
cmd = NULL; return false;
} }
else else
{ {
/* Copy whole line including terminating null, and we're /* Copy whole line including terminating null, and we're
done. */ done. */
buffer_grow (cmd_line_buffer, rl, len + 1); cmd_line_buffer.append (rl, len + 1);
cmd = cmd_line_buffer->buffer; return true;
} }
return cmd;
} }
/* Handle a line of input coming from readline. /* Handle a line of input coming from readline.
If the read line ends with a continuation character (backslash), If the read line ends with a continuation character (backslash), return
save the partial input in CMD_LINE_BUFFER (except the backslash), nullptr. Otherwise, return a pointer to the command line, indicating a whole
and return NULL. Otherwise, save the partial input and return a command line is ready to be executed.
pointer to CMD_LINE_BUFFER's buffer (null terminated), indicating a
whole command line is ready to be executed.
Returns EOF on end of file. The returned pointer may or may not point to CMD_LINE_BUFFER's internal
buffer.
Return EOF on end of file.
If REPEAT, handle command repetitions: If REPEAT, handle command repetitions:
@ -670,38 +665,32 @@ command_line_append_input_line (struct buffer *cmd_line_buffer, const char *rl)
command instead of the empty input line. command instead of the empty input line.
*/ */
char * const char *
handle_line_of_input (struct buffer *cmd_line_buffer, handle_line_of_input (std::string &cmd_line_buffer,
const char *rl, int repeat, const char *rl, int repeat,
const char *annotation_suffix) const char *annotation_suffix)
{ {
struct ui *ui = current_ui; struct ui *ui = current_ui;
int from_tty = ui->instream == ui->stdin_stream; int from_tty = ui->instream == ui->stdin_stream;
char *p1;
char *cmd;
if (rl == NULL) if (rl == NULL)
return (char *) EOF; return (char *) EOF;
cmd = command_line_append_input_line (cmd_line_buffer, rl); bool complete = command_line_append_input_line (cmd_line_buffer, rl);
if (cmd == NULL) if (!complete)
return NULL; return NULL;
/* We have a complete command line now. Prepare for the next
command, but leave ownership of memory to the buffer . */
cmd_line_buffer->used_size = 0;
if (from_tty && annotation_level > 1) if (from_tty && annotation_level > 1)
printf_unfiltered (("\n\032\032post-%s\n"), annotation_suffix); printf_unfiltered (("\n\032\032post-%s\n"), annotation_suffix);
#define SERVER_COMMAND_PREFIX "server " #define SERVER_COMMAND_PREFIX "server "
server_command = startswith (cmd, SERVER_COMMAND_PREFIX); server_command = startswith (cmd_line_buffer.c_str (), SERVER_COMMAND_PREFIX);
if (server_command) if (server_command)
{ {
/* Note that we don't call `save_command_line'. Between this /* Note that we don't call `save_command_line'. Between this
and the check in dont_repeat, this insures that repeating and the check in dont_repeat, this insures that repeating
will still do the right thing. */ will still do the right thing. */
return cmd + strlen (SERVER_COMMAND_PREFIX); return cmd_line_buffer.c_str () + strlen (SERVER_COMMAND_PREFIX);
} }
/* Do history expansion if that is wished. */ /* Do history expansion if that is wished. */
@ -710,32 +699,29 @@ handle_line_of_input (struct buffer *cmd_line_buffer,
char *cmd_expansion; char *cmd_expansion;
int expanded; int expanded;
expanded = history_expand (cmd, &cmd_expansion); /* Note: here, we pass a pointer to the std::string's internal buffer as
a `char *`. At the time of writing, readline's history_expand does
not modify the passed-in string. Ideally, readline should be modified
to make that parameter `const char *`. */
expanded = history_expand (&cmd_line_buffer[0], &cmd_expansion);
gdb::unique_xmalloc_ptr<char> history_value (cmd_expansion); gdb::unique_xmalloc_ptr<char> history_value (cmd_expansion);
if (expanded) if (expanded)
{ {
size_t len;
/* Print the changes. */ /* Print the changes. */
printf_unfiltered ("%s\n", history_value.get ()); printf_unfiltered ("%s\n", history_value.get ());
/* If there was an error, call this function again. */ /* If there was an error, call this function again. */
if (expanded < 0) if (expanded < 0)
return cmd; return cmd_line_buffer.c_str ();
/* history_expand returns an allocated string. Just replace cmd_line_buffer = history_value.get ();
our buffer with it. */
len = strlen (history_value.get ());
xfree (buffer_finish (cmd_line_buffer));
cmd_line_buffer->buffer = history_value.get ();
cmd_line_buffer->buffer_size = len + 1;
cmd = history_value.release ();
} }
} }
/* If we just got an empty line, and that is supposed to repeat the /* If we just got an empty line, and that is supposed to repeat the
previous command, return the previously saved command. */ previous command, return the previously saved command. */
for (p1 = cmd; *p1 == ' ' || *p1 == '\t'; p1++) const char *p1;
for (p1 = cmd_line_buffer.c_str (); *p1 == ' ' || *p1 == '\t'; p1++)
; ;
if (repeat && *p1 == '\0') if (repeat && *p1 == '\0')
return get_saved_command_line (); return get_saved_command_line ();
@ -747,17 +733,21 @@ handle_line_of_input (struct buffer *cmd_line_buffer,
and then later fetch it from the value history and remove the and then later fetch it from the value history and remove the
'#'. The kill ring is probably better, but some people are in '#'. The kill ring is probably better, but some people are in
the habit of commenting things out. */ the habit of commenting things out. */
if (*cmd != '\0' && from_tty && current_ui->input_interactive_p ()) if (cmd_line_buffer[0] != '\0' && from_tty && current_ui->input_interactive_p ())
gdb_add_history (cmd); gdb_add_history (cmd_line_buffer.c_str ());
/* Save into global buffer if appropriate. */ /* Save into global buffer if appropriate. */
if (repeat) if (repeat)
{ {
save_command_line (cmd); save_command_line (cmd_line_buffer.c_str ());
/* It is important that we return a pointer to the saved command line
here, for the `cmd_start == saved_command_line` check in
execute_command to work. */
return get_saved_command_line (); return get_saved_command_line ();
} }
else
return cmd; return cmd_line_buffer.c_str ();
} }
/* See event-top.h. */ /* See event-top.h. */
@ -790,11 +780,10 @@ gdb_rl_deprep_term_function (void)
void void
command_line_handler (gdb::unique_xmalloc_ptr<char> &&rl) command_line_handler (gdb::unique_xmalloc_ptr<char> &&rl)
{ {
struct buffer *line_buffer = get_command_line_buffer (); std::string &line_buffer = get_command_line_buffer ();
struct ui *ui = current_ui; struct ui *ui = current_ui;
char *cmd;
cmd = handle_line_of_input (line_buffer, rl.get (), 1, "prompt"); const char *cmd = handle_line_of_input (line_buffer, rl.get (), 1, "prompt");
if (cmd == (char *) EOF) if (cmd == (char *) EOF)
{ {
/* stdin closed. The connection with the terminal is gone. /* stdin closed. The connection with the terminal is gone.
@ -857,6 +846,9 @@ command_line_handler (gdb::unique_xmalloc_ptr<char> &&rl)
{ {
ui->prompt_state = PROMPT_NEEDED; ui->prompt_state = PROMPT_NEEDED;
/* Ensure the UI's line buffer is empty for the next command. */
SCOPE_EXIT { line_buffer.clear (); };
command_handler (cmd); command_handler (cmd);
if (ui->prompt_state != PROMPTED) if (ui->prompt_state != PROMPTED)

View file

@ -1501,7 +1501,9 @@ decode_line_2 (struct linespec_state *self,
{ {
prompt = "> "; prompt = "> ";
} }
args = command_line_input (prompt, "overload-choice");
std::string buffer;
args = command_line_input (buffer, prompt, "overload-choice");
if (args == 0 || *args == 0) if (args == 0 || *args == 0)
error_no_arg (_("one or more choice numbers")); error_no_arg (_("one or more choice numbers"));

View file

@ -564,7 +564,7 @@ mi_cmd_break_commands (const char *command, char **argv, int argc)
int count = 1; int count = 1;
auto reader auto reader
= [&] () = [&] (std::string &buffer)
{ {
const char *result = nullptr; const char *result = nullptr;
if (count < argc) if (count < argc)

View file

@ -582,7 +582,7 @@ bppy_set_commands (PyObject *self, PyObject *newvalue, void *closure)
bool first = true; bool first = true;
char *save_ptr = nullptr; char *save_ptr = nullptr;
auto reader auto reader
= [&] () = [&] (std::string &buffer)
{ {
const char *result = strtok_r (first ? commands.get () : nullptr, const char *result = strtok_r (first ? commands.get () : nullptr,
"\n", &save_ptr); "\n", &save_ptr);

View file

@ -38,11 +38,12 @@ gdbpy_readline_wrapper (FILE *sys_stdin, FILE *sys_stdout,
{ {
int n; int n;
const char *p = NULL; const char *p = NULL;
std::string buffer;
char *q; char *q;
try try
{ {
p = command_line_input (prompt, "python"); p = command_line_input (buffer, prompt, "python");
} }
/* Handle errors by raising Python exceptions. */ /* Handle errors by raising Python exceptions. */
catch (const gdb_exception &except) catch (const gdb_exception &except)

View file

@ -644,7 +644,7 @@ execute_gdb_command (PyObject *self, PyObject *args, PyObject *kw)
bool first = true; bool first = true;
char *save_ptr = nullptr; char *save_ptr = nullptr;
auto reader auto reader
= [&] () = [&] (std::string &buffer)
{ {
const char *result = strtok_r (first ? &arg_copy[0] : nullptr, const char *result = strtok_r (first ? &arg_copy[0] : nullptr,
"\n", &save_ptr); "\n", &save_ptr);

View file

@ -307,8 +307,6 @@ ui::ui (FILE *instream_, FILE *outstream_, FILE *errstream_)
m_gdb_stderr (new stderr_file (errstream)), m_gdb_stderr (new stderr_file (errstream)),
m_gdb_stdlog (m_gdb_stderr) m_gdb_stdlog (m_gdb_stderr)
{ {
buffer_init (&line_buffer);
unbuffer_stream (instream_); unbuffer_stream (instream_);
if (ui_list == NULL) if (ui_list == NULL)
@ -343,8 +341,6 @@ ui::~ui ()
delete m_gdb_stdin; delete m_gdb_stdin;
delete m_gdb_stdout; delete m_gdb_stdout;
delete m_gdb_stderr; delete m_gdb_stderr;
buffer_free (&line_buffer);
} }
/* Open file named NAME for read/write, making sure not to make it the /* Open file named NAME for read/write, making sure not to make it the
@ -452,11 +448,11 @@ read_command_file (FILE *stream)
while (ui->instream != NULL && !feof (ui->instream)) while (ui->instream != NULL && !feof (ui->instream))
{ {
const char *command;
/* Get a command-line. This calls the readline package. */ /* Get a command-line. This calls the readline package. */
command = command_line_input (NULL, NULL); std::string command_buffer;
if (command == NULL) const char *command
= command_line_input (command_buffer, nullptr, nullptr);
if (command == nullptr)
break; break;
command_handler (command); command_handler (command);
} }
@ -1333,23 +1329,23 @@ gdb_safe_append_history (void)
} }
} }
/* Read one line from the command input stream `instream' into a local /* Read one line from the command input stream `instream'.
static buffer. The buffer is made bigger as necessary. Returns
the address of the start of the line.
NULL is returned for end of file. CMD_LINE_BUFFER is a buffer that the function may use to store the result, if
it needs to be dynamically-allocated. Otherwise, it is unused.string
Return nullptr for end of file.
This routine either uses fancy command line editing or simple input This routine either uses fancy command line editing or simple input
as the user has requested. */ as the user has requested. */
const char * const char *
command_line_input (const char *prompt_arg, const char *annotation_suffix) command_line_input (std::string &cmd_line_buffer, const char *prompt_arg,
const char *annotation_suffix)
{ {
static struct buffer cmd_line_buffer;
static int cmd_line_buffer_initialized;
struct ui *ui = current_ui; struct ui *ui = current_ui;
const char *prompt = prompt_arg; const char *prompt = prompt_arg;
char *cmd; const char *cmd;
int from_tty = ui->instream == ui->stdin_stream; int from_tty = ui->instream == ui->stdin_stream;
/* The annotation suffix must be non-NULL. */ /* The annotation suffix must be non-NULL. */
@ -1374,15 +1370,6 @@ command_line_input (const char *prompt_arg, const char *annotation_suffix)
prompt = local_prompt; prompt = local_prompt;
} }
if (!cmd_line_buffer_initialized)
{
buffer_init (&cmd_line_buffer);
cmd_line_buffer_initialized = 1;
}
/* Starting a new command line. */
cmd_line_buffer.used_size = 0;
#ifdef SIGTSTP #ifdef SIGTSTP
if (job_control) if (job_control)
signal (SIGTSTP, handle_sigtstp); signal (SIGTSTP, handle_sigtstp);
@ -1422,7 +1409,7 @@ command_line_input (const char *prompt_arg, const char *annotation_suffix)
rl.reset (gdb_readline_no_editing (prompt)); rl.reset (gdb_readline_no_editing (prompt));
} }
cmd = handle_line_of_input (&cmd_line_buffer, rl.get (), cmd = handle_line_of_input (cmd_line_buffer, rl.get (),
0, annotation_suffix); 0, annotation_suffix);
if (cmd == (char *) EOF) if (cmd == (char *) EOF)
{ {

View file

@ -68,7 +68,7 @@ struct ui
/* The UI's command line buffer. This is to used to accumulate /* The UI's command line buffer. This is to used to accumulate
input until we have a whole command line. */ input until we have a whole command line. */
struct buffer line_buffer; std::string line_buffer;
/* The callback used by the event loop whenever an event is detected /* The callback used by the event loop whenever an event is detected
on the UI's input file descriptor. This function incrementally on the UI's input file descriptor. This function incrementally
@ -289,9 +289,9 @@ extern void show_commands (const char *args, int from_tty);
extern void set_verbose (const char *, int, struct cmd_list_element *); extern void set_verbose (const char *, int, struct cmd_list_element *);
extern char *handle_line_of_input (struct buffer *cmd_line_buffer, extern const char *handle_line_of_input (std::string &cmd_line_buffer,
const char *rl, int repeat, const char *rl, int repeat,
const char *annotation_suffix); const char *annotation_suffix);
/* Call at startup to see if the user has requested that gdb start up /* Call at startup to see if the user has requested that gdb start up
quietly. */ quietly. */