Skip to content

Commit b6c3f8e

Browse files
pks-tgitster
authored andcommitted
builtin/maintenance: fix leak in get_schedule_cmd()
The `get_schedule_cmd()` function allows us to override the schedule command with a specific test command such that we can verify the underlying logic in a platform-independent way. Its memory management is somewhat wild though, because it basically gives up and assigns an allocated string to the string constant output pointer. While this part is marked with `UNLEAK()` to mask this, we also leak the local string lists. Rework the function such that it has a separate out parameter. If set, we will assign it the final allocated command. Plug the other memory leaks and create a common exit path. Signed-off-by: Patrick Steinhardt <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 84e9fc3 commit b6c3f8e

File tree

2 files changed

+81
-47
lines changed

2 files changed

+81
-47
lines changed

builtin/gc.c

Lines changed: 80 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1780,32 +1780,33 @@ static const char *get_frequency(enum schedule_priority schedule)
17801780
* * If $GIT_TEST_MAINT_SCHEDULER is set, return true.
17811781
* In this case, the *cmd value is read as input.
17821782
*
1783-
* * if the input value *cmd is the key of one of the comma-separated list
1784-
* item, then *is_available is set to true and *cmd is modified and becomes
1783+
* * if the input value cmd is the key of one of the comma-separated list
1784+
* item, then *is_available is set to true and *out is set to
17851785
* the mock command.
17861786
*
17871787
* * if the input value *cmd isn’t the key of any of the comma-separated list
1788-
* item, then *is_available is set to false.
1788+
* item, then *is_available is set to false and *out is set to the original
1789+
* command.
17891790
*
17901791
* Ex.:
17911792
* GIT_TEST_MAINT_SCHEDULER not set
17921793
* +-------+-------------------------------------------------+
17931794
* | Input | Output |
1794-
* | *cmd | return code | *cmd | *is_available |
1795+
* | *cmd | return code | *out | *is_available |
17951796
* +-------+-------------+-------------------+---------------+
1796-
* | "foo" | false | "foo" (unchanged) | (unchanged) |
1797+
* | "foo" | false | NULL | (unchanged) |
17971798
* +-------+-------------+-------------------+---------------+
17981799
*
17991800
* GIT_TEST_MAINT_SCHEDULER set to “foo:./mock_foo.sh,bar:./mock_bar.sh”
18001801
* +-------+-------------------------------------------------+
18011802
* | Input | Output |
1802-
* | *cmd | return code | *cmd | *is_available |
1803+
* | *cmd | return code | *out | *is_available |
18031804
* +-------+-------------+-------------------+---------------+
18041805
* | "foo" | true | "./mock.foo.sh" | true |
1805-
* | "qux" | true | "qux" (unchanged) | false |
1806+
* | "qux" | true | "qux" (allocated) | false |
18061807
* +-------+-------------+-------------------+---------------+
18071808
*/
1808-
static int get_schedule_cmd(const char **cmd, int *is_available)
1809+
static int get_schedule_cmd(const char *cmd, int *is_available, char **out)
18091810
{
18101811
char *testing = xstrdup_or_null(getenv("GIT_TEST_MAINT_SCHEDULER"));
18111812
struct string_list_item *item;
@@ -1824,16 +1825,22 @@ static int get_schedule_cmd(const char **cmd, int *is_available)
18241825
if (string_list_split_in_place(&pair, item->string, ":", 2) != 2)
18251826
continue;
18261827

1827-
if (!strcmp(*cmd, pair.items[0].string)) {
1828-
*cmd = pair.items[1].string;
1828+
if (!strcmp(cmd, pair.items[0].string)) {
1829+
if (out)
1830+
*out = xstrdup(pair.items[1].string);
18291831
if (is_available)
18301832
*is_available = 1;
1831-
string_list_clear(&list, 0);
1832-
UNLEAK(testing);
1833-
return 1;
1833+
string_list_clear(&pair, 0);
1834+
goto out;
18341835
}
1836+
1837+
string_list_clear(&pair, 0);
18351838
}
18361839

1840+
if (out)
1841+
*out = xstrdup(cmd);
1842+
1843+
out:
18371844
string_list_clear(&list, 0);
18381845
free(testing);
18391846
return 1;
@@ -1850,9 +1857,8 @@ static int get_random_minute(void)
18501857

18511858
static int is_launchctl_available(void)
18521859
{
1853-
const char *cmd = "launchctl";
18541860
int is_available;
1855-
if (get_schedule_cmd(&cmd, &is_available))
1861+
if (get_schedule_cmd("launchctl", &is_available, NULL))
18561862
return is_available;
18571863

18581864
#ifdef __APPLE__
@@ -1890,12 +1896,12 @@ static char *launchctl_get_uid(void)
18901896

18911897
static int launchctl_boot_plist(int enable, const char *filename)
18921898
{
1893-
const char *cmd = "launchctl";
1899+
char *cmd;
18941900
int result;
18951901
struct child_process child = CHILD_PROCESS_INIT;
18961902
char *uid = launchctl_get_uid();
18971903

1898-
get_schedule_cmd(&cmd, NULL);
1904+
get_schedule_cmd("launchctl", NULL, &cmd);
18991905
strvec_split(&child.args, cmd);
19001906
strvec_pushl(&child.args, enable ? "bootstrap" : "bootout", uid,
19011907
filename, NULL);
@@ -1908,6 +1914,7 @@ static int launchctl_boot_plist(int enable, const char *filename)
19081914

19091915
result = finish_command(&child);
19101916

1917+
free(cmd);
19111918
free(uid);
19121919
return result;
19131920
}
@@ -1959,10 +1966,10 @@ static int launchctl_schedule_plist(const char *exec_path, enum schedule_priorit
19591966
static unsigned long lock_file_timeout_ms = ULONG_MAX;
19601967
struct strbuf plist = STRBUF_INIT, plist2 = STRBUF_INIT;
19611968
struct stat st;
1962-
const char *cmd = "launchctl";
1969+
char *cmd;
19631970
int minute = get_random_minute();
19641971

1965-
get_schedule_cmd(&cmd, NULL);
1972+
get_schedule_cmd("launchctl", NULL, &cmd);
19661973
preamble = "<?xml version=\"1.0\"?>\n"
19671974
"<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
19681975
"<plist version=\"1.0\">"
@@ -2052,6 +2059,7 @@ static int launchctl_schedule_plist(const char *exec_path, enum schedule_priorit
20522059

20532060
free(filename);
20542061
free(name);
2062+
free(cmd);
20552063
strbuf_release(&plist);
20562064
strbuf_release(&plist2);
20572065
return 0;
@@ -2076,9 +2084,8 @@ static int launchctl_update_schedule(int run_maintenance, int fd UNUSED)
20762084

20772085
static int is_schtasks_available(void)
20782086
{
2079-
const char *cmd = "schtasks";
20802087
int is_available;
2081-
if (get_schedule_cmd(&cmd, &is_available))
2088+
if (get_schedule_cmd("schtasks", &is_available, NULL))
20822089
return is_available;
20832090

20842091
#ifdef GIT_WINDOWS_NATIVE
@@ -2097,15 +2104,16 @@ static char *schtasks_task_name(const char *frequency)
20972104

20982105
static int schtasks_remove_task(enum schedule_priority schedule)
20992106
{
2100-
const char *cmd = "schtasks";
2107+
char *cmd;
21012108
struct child_process child = CHILD_PROCESS_INIT;
21022109
const char *frequency = get_frequency(schedule);
21032110
char *name = schtasks_task_name(frequency);
21042111

2105-
get_schedule_cmd(&cmd, NULL);
2112+
get_schedule_cmd("schtasks", NULL, &cmd);
21062113
strvec_split(&child.args, cmd);
21072114
strvec_pushl(&child.args, "/delete", "/tn", name, "/f", NULL);
21082115
free(name);
2116+
free(cmd);
21092117

21102118
return run_command(&child);
21112119
}
@@ -2119,7 +2127,7 @@ static int schtasks_remove_tasks(void)
21192127

21202128
static int schtasks_schedule_task(const char *exec_path, enum schedule_priority schedule)
21212129
{
2122-
const char *cmd = "schtasks";
2130+
char *cmd;
21232131
int result;
21242132
struct child_process child = CHILD_PROCESS_INIT;
21252133
const char *xml;
@@ -2129,7 +2137,7 @@ static int schtasks_schedule_task(const char *exec_path, enum schedule_priority
21292137
struct strbuf tfilename = STRBUF_INIT;
21302138
int minute = get_random_minute();
21312139

2132-
get_schedule_cmd(&cmd, NULL);
2140+
get_schedule_cmd("schtasks", NULL, &cmd);
21332141

21342142
strbuf_addf(&tfilename, "%s/schedule_%s_XXXXXX",
21352143
get_git_common_dir(), frequency);
@@ -2235,6 +2243,7 @@ static int schtasks_schedule_task(const char *exec_path, enum schedule_priority
22352243

22362244
delete_tempfile(&tfile);
22372245
free(name);
2246+
free(cmd);
22382247
return result;
22392248
}
22402249

@@ -2276,29 +2285,36 @@ static int check_crontab_process(const char *cmd)
22762285

22772286
static int is_crontab_available(void)
22782287
{
2279-
const char *cmd = "crontab";
2288+
char *cmd;
22802289
int is_available;
2290+
int ret;
22812291

2282-
if (get_schedule_cmd(&cmd, &is_available))
2283-
return is_available;
2292+
if (get_schedule_cmd("crontab", &is_available, &cmd)) {
2293+
ret = is_available;
2294+
goto out;
2295+
}
22842296

22852297
#ifdef __APPLE__
22862298
/*
22872299
* macOS has cron, but it requires special permissions and will
22882300
* create a UI alert when attempting to run this command.
22892301
*/
2290-
return 0;
2302+
ret = 0;
22912303
#else
2292-
return check_crontab_process(cmd);
2304+
ret = check_crontab_process(cmd);
22932305
#endif
2306+
2307+
out:
2308+
free(cmd);
2309+
return ret;
22942310
}
22952311

22962312
#define BEGIN_LINE "# BEGIN GIT MAINTENANCE SCHEDULE"
22972313
#define END_LINE "# END GIT MAINTENANCE SCHEDULE"
22982314

22992315
static int crontab_update_schedule(int run_maintenance, int fd)
23002316
{
2301-
const char *cmd = "crontab";
2317+
char *cmd;
23022318
int result = 0;
23032319
int in_old_region = 0;
23042320
struct child_process crontab_list = CHILD_PROCESS_INIT;
@@ -2308,15 +2324,17 @@ static int crontab_update_schedule(int run_maintenance, int fd)
23082324
struct tempfile *tmpedit = NULL;
23092325
int minute = get_random_minute();
23102326

2311-
get_schedule_cmd(&cmd, NULL);
2327+
get_schedule_cmd("crontab", NULL, &cmd);
23122328
strvec_split(&crontab_list.args, cmd);
23132329
strvec_push(&crontab_list.args, "-l");
23142330
crontab_list.in = -1;
23152331
crontab_list.out = dup(fd);
23162332
crontab_list.git_cmd = 0;
23172333

2318-
if (start_command(&crontab_list))
2319-
return error(_("failed to run 'crontab -l'; your system might not support 'cron'"));
2334+
if (start_command(&crontab_list)) {
2335+
result = error(_("failed to run 'crontab -l'; your system might not support 'cron'"));
2336+
goto out;
2337+
}
23202338

23212339
/* Ignore exit code, as an empty crontab will return error. */
23222340
finish_command(&crontab_list);
@@ -2386,8 +2404,10 @@ static int crontab_update_schedule(int run_maintenance, int fd)
23862404
result = error(_("'crontab' died"));
23872405
else
23882406
fclose(cron_list);
2407+
23892408
out:
23902409
delete_tempfile(&tmpedit);
2410+
free(cmd);
23912411
return result;
23922412
}
23932413

@@ -2410,10 +2430,9 @@ static int real_is_systemd_timer_available(void)
24102430

24112431
static int is_systemd_timer_available(void)
24122432
{
2413-
const char *cmd = "systemctl";
24142433
int is_available;
24152434

2416-
if (get_schedule_cmd(&cmd, &is_available))
2435+
if (get_schedule_cmd("systemctl", &is_available, NULL))
24172436
return is_available;
24182437

24192438
return real_is_systemd_timer_available();
@@ -2594,9 +2613,10 @@ static int systemd_timer_enable_unit(int enable,
25942613
enum schedule_priority schedule,
25952614
int minute)
25962615
{
2597-
const char *cmd = "systemctl";
2616+
char *cmd = NULL;
25982617
struct child_process child = CHILD_PROCESS_INIT;
25992618
const char *frequency = get_frequency(schedule);
2619+
int ret;
26002620

26012621
/*
26022622
* Disabling the systemd unit while it is already disabled makes
@@ -2607,30 +2627,43 @@ static int systemd_timer_enable_unit(int enable,
26072627
* On the other hand, enabling a systemd unit which is already enabled
26082628
* produces no error.
26092629
*/
2610-
if (!enable)
2630+
if (!enable) {
26112631
child.no_stderr = 1;
2612-
else if (systemd_timer_write_timer_file(schedule, minute))
2613-
return -1;
2632+
} else if (systemd_timer_write_timer_file(schedule, minute)) {
2633+
ret = -1;
2634+
goto out;
2635+
}
26142636

2615-
get_schedule_cmd(&cmd, NULL);
2637+
get_schedule_cmd("systemctl", NULL, &cmd);
26162638
strvec_split(&child.args, cmd);
26172639
strvec_pushl(&child.args, "--user", enable ? "enable" : "disable",
26182640
"--now", NULL);
26192641
strvec_pushf(&child.args, SYSTEMD_UNIT_FORMAT, frequency, "timer");
26202642

2621-
if (start_command(&child))
2622-
return error(_("failed to start systemctl"));
2623-
if (finish_command(&child))
2643+
if (start_command(&child)) {
2644+
ret = error(_("failed to start systemctl"));
2645+
goto out;
2646+
}
2647+
2648+
if (finish_command(&child)) {
26242649
/*
26252650
* Disabling an already disabled systemd unit makes
26262651
* systemctl fail.
26272652
* Let's ignore this failure.
26282653
*
26292654
* Enabling an enabled systemd unit doesn't fail.
26302655
*/
2631-
if (enable)
2632-
return error(_("failed to run systemctl"));
2633-
return 0;
2656+
if (enable) {
2657+
ret = error(_("failed to run systemctl"));
2658+
goto out;
2659+
}
2660+
}
2661+
2662+
ret = 0;
2663+
2664+
out:
2665+
free(cmd);
2666+
return ret;
26342667
}
26352668

26362669
/*

t/t7900-maintenance.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
test_description='git maintenance builtin'
44

5+
TEST_PASSES_SANITIZE_LEAK=true
56
. ./test-lib.sh
67

78
GIT_TEST_COMMIT_GRAPH=0

0 commit comments

Comments
 (0)