Re: [BUG] rebase: can write reflog with uninit. `action` string

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi Peff and Kristoffer

On 01/05/2025 14:17, Jeff King wrote:
On Wed, Apr 30, 2025 at 05:17:38PM +0200, Kristoffer Haugsbakk wrote:

Probably the smallest solution is for ctx->reflog_message to copy the
result and always own the memory (and then remember to free it, both at
cleanup and if it is ever overwritten).

But I think the way reflog_message() returns the "buf" member of a
static strbuf is kind of an anti-pattern, exactly because you can get
this kind of subtle re-use. It probably should just return a non-const
pointer, handing over memory ownership to the caller. That would require
adjusting its other callers, too.

Getting rid of the static buffer would certainly protect us from the use-after-free. The bug here is that we're not calling reflog_message() and storing the result in ctx->reflog_message() to create the correct message in do_merge(). Looking at your patch, having to remember to copy the string returned from reflog_message() is a bit of a pain. I wonder if we could change ctx->reflog_message to be an strbuf and update reflog_message() like so

diff --git a/sequencer.c b/sequencer.c
index ad0ab75c8d4..59d80ddf0cc 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -3941,11 +3941,10 @@ static const char *sequencer_reflog_action(struct replay_opts *opts)
 }

 __attribute__((format (printf, 3, 4)))
-static const char *reflog_message(struct replay_opts *opts,
+static void reflog_message(struct replay_opts *opts,
 	const char *sub_action, const char *fmt, ...)
 {
 	va_list ap;
-	static struct strbuf buf = STRBUF_INIT;
+	struct strbuf *buf = &opts->ctx->reflog_message;

 	va_start(ap, fmt);
 	strbuf_reset(&buf);
@@ -3957,8 +3956,6 @@ static const char *reflog_message(struct replay_opts *opts,
 		strbuf_vaddf(&buf, fmt, ap);
 	}
 	va_end(ap);
-
-	return buf.buf;
 }

static struct commit *lookup_label(struct repository *r, const char *label,

All of the other callers should can then use use ctx->reflog_message.buf where they were using the return value of reflog_message() before. That would protect us from the use-after-free.

I'll try and put a proper patch together next week that removes the static buffer and starts calling reflog_message() when we're merging.

Best Wishes

Phillip


So the "smallest" version is perhaps something like this, totally
untested except for confirming that t3430 no longer complains:

diff --git a/sequencer.c b/sequencer.c
index b5c4043757..07aa3b3731 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -228,7 +228,7 @@ struct replay_ctx {
  	 * Stores the reflog message that will be used when creating a
  	 * commit. Points to a static buffer and should not be free()'d.
  	 */
-	const char *reflog_message;
+	char *reflog_message;
  	/*
  	 * The number of completed fixup and squash commands in the
  	 * current chain.
@@ -411,6 +411,7 @@ static void replay_ctx_release(struct replay_ctx *ctx)
  {
  	strbuf_release(&ctx->current_fixups);
  	strbuf_release(&ctx->message);
+	free(ctx->reflog_message);
  }
void replay_opts_release(struct replay_opts *opts)
@@ -3939,7 +3940,7 @@ static const char *reflog_message(struct replay_opts *opts,
  	static struct strbuf buf = STRBUF_INIT;
va_start(ap, fmt);
-	strbuf_reset(&buf);
+	strbuf_release(&buf); /* guarantees realloaction */
  	strbuf_addstr(&buf, sequencer_reflog_action(opts));
  	if (sub_action)
  		strbuf_addf(&buf, " (%s)", sub_action);
@@ -4886,9 +4887,11 @@ static int pick_one_commit(struct repository *r,
  	int res;
  	struct todo_item *item = todo_list->items + todo_list->current;
  	const char *arg = todo_item_get_arg(todo_list, item);
-	if (is_rebase_i(opts))
-		ctx->reflog_message = reflog_message(
-			opts, command_to_string(item->command), NULL);
+	if (is_rebase_i(opts)) {
+		free(ctx->reflog_message);
+		ctx->reflog_message = xstrdup(reflog_message(
+			opts, command_to_string(item->command), NULL));
+	}
res = do_pick_commit(r, item, opts, is_final_fixup(todo_list),
  			     check_todo);
@@ -4947,7 +4950,8 @@ static int pick_commits(struct repository *r,
  	struct replay_ctx *ctx = opts->ctx;
  	int res = 0, reschedule = 0;
- ctx->reflog_message = sequencer_reflog_action(opts);
+	free(ctx->reflog_message);
+	ctx->reflog_message = xstrdup(sequencer_reflog_action(opts));
  	if (opts->allow_ff)
  		ASSERT(!(opts->signoff || opts->no_commit ||
  			 opts->record_origin || should_edit(opts) ||
@@ -5423,7 +5427,8 @@ int sequencer_continue(struct repository *r, struct replay_opts *opts)
  			unlink(rebase_path_dropped());
  		}
- ctx->reflog_message = reflog_message(opts, "continue", NULL);
+		free(ctx->reflog_message);
+		ctx->reflog_message = xstrdup(reflog_message(opts, "continue", NULL));
  		if (commit_staged_changes(r, opts, &todo_list)) {
  			res = -1;
  			goto release_todo_list;
@@ -5475,7 +5480,8 @@ static int single_pick(struct repository *r,
  			TODO_PICK : TODO_REVERT;
  	item.commit = cmit;
- opts->ctx->reflog_message = sequencer_reflog_action(opts);
+	free(opts->ctx->reflog_message);
+	opts->ctx->reflog_message = xstrdup(sequencer_reflog_action(opts));
  	return do_pick_commit(r, &item, opts, 0, &check_todo);
  }
I'm hoping your or Phillip can decide on the best fix from here.

-Peff





[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]

  Powered by Linux