vty: fix use-after-free and memleaks in is_cmd_ambiguous()

vty_test: add test against ambiguous cmd causing use-after-free and memory
leaks. Add this test along with the fix, because the new test triggers the
memory use-after-free and leaks, causing build failures.

Add cmd_deopt_with_ctx() to allow passing a specific talloc ctx.

is_cmd_ambiguous(): keep all cmd_deopt() allocations until the function exits.
Add a comment explaining why. Before this, if a command matched an optional
"[arg]" with square brackets, we would keep it in local var 'matched', but we
would free the string it points to at the end of that loop iteration; upon
encountering another match, we would attempt to strcmp against the freed
'matched'. Instead of adding hard-to-read and -verify free/alloc dances to keep
the 'matched' accurately freed/non-freed/..., just keep all cmd_deopt() string
allocated until done.

Needless to say that this should have been implemented on a lower level upon
inventing optional args, but at least this is fixing a program crash.

Related: OS#33903390
Change-Id: Ia71ba742108b5ff020997bfb612ad5eb30d04fcd
diff --git a/tests/vty/vty_test.c b/tests/vty/vty_test.c
index a3478e1..30efb9a 100644
--- a/tests/vty/vty_test.c
+++ b/tests/vty/vty_test.c
@@ -385,6 +385,42 @@
 	return CMD_SUCCESS;
 }
 
+DEFUN(cfg_ambiguous_nr_1, cfg_ambiguous_nr_1_cmd,
+	"ambiguous_nr [<0-23>]",
+	"testing is_cmd_ambiguous()\n"
+	"optional number arg\n")
+{
+	printf("Called: 'ambiguous_nr [<0-23>]' (argc=%d)\n", argc);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_ambiguous_nr_2, cfg_ambiguous_nr_2_cmd,
+	"ambiguous_nr <0-23> keyword",
+	"testing is_cmd_ambiguous()\n"
+	"optional number arg\n")
+{
+	printf("Called: 'ambiguous_nr <0-23> keyword'\n");
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_ambiguous_str_1, cfg_ambiguous_str_1_cmd,
+	"ambiguous_str [ARG]",
+	"testing is_cmd_ambiguous()\n"
+	"optional string arg\n")
+{
+	printf("Called: 'ambiguous_str [ARG]' (argc=%d)\n", argc);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_ambiguous_str_2, cfg_ambiguous_str_2_cmd,
+	"ambiguous_str ARG keyword",
+	"testing is_cmd_ambiguous()\n"
+	"optional string arg\n")
+{
+	printf("Called: 'ambiguous_str ARG keyword'\n");
+	return CMD_SUCCESS;
+}
+
 void test_vty_add_cmds()
 {
 	install_element(CONFIG_NODE, &cfg_level1_cmd);
@@ -398,6 +434,30 @@
 
 	install_node(&level3_node, NULL);
 	install_element(LEVEL3_NODE, &cfg_level3_child_cmd);
+
+	install_element_ve(&cfg_ambiguous_nr_1_cmd);
+	install_element_ve(&cfg_ambiguous_nr_2_cmd);
+	install_element_ve(&cfg_ambiguous_str_1_cmd);
+	install_element_ve(&cfg_ambiguous_str_2_cmd);
+}
+
+void test_is_cmd_ambiguous()
+{
+	struct vty *vty;
+	struct vty_test test;
+
+	printf("Going to test is_cmd_ambiguous()\n");
+	vty = create_test_vty(&test);
+
+	OSMO_ASSERT(do_vty_command(vty, "ambiguous_nr") == CMD_SUCCESS);
+	OSMO_ASSERT(do_vty_command(vty, "ambiguous_nr 23") == CMD_SUCCESS);
+	OSMO_ASSERT(do_vty_command(vty, "ambiguous_nr 23 keyword") == CMD_SUCCESS);
+
+	OSMO_ASSERT(do_vty_command(vty, "ambiguous_str") == CMD_SUCCESS);
+	OSMO_ASSERT(do_vty_command(vty, "ambiguous_str arg") == CMD_SUCCESS);
+	OSMO_ASSERT(do_vty_command(vty, "ambiguous_str arg keyword") == CMD_SUCCESS);
+
+	destroy_test_vty(&test, vty);
 }
 
 static int go_parent_cb(struct vty *vty)
@@ -465,6 +525,8 @@
 	test_exit_by_indent("ok_indented_root.cfg", 0);
 	test_exit_by_indent("ok_empty_parent.cfg", 0);
 
+	test_is_cmd_ambiguous();
+
 	/* Leak check */
 	OSMO_ASSERT(talloc_total_blocks(stats_ctx) == 1);