Skip to content

Commit 309c233

Browse files
Moritz Faincron2
authored andcommitted
PUSH_UPDATE: fix option reset logic in continuation messages
Previously, the logic for resetting push options (like 'route') was based on `update_options_found` which was local to `apply_push_options`. This meant that if a PUSH_UPDATE was split across multiple continuation messages, the state was lost, causing routes to be reset multiple times (once per message chunk) rather than once per update sequence. This patch moves the state tracking to `struct options` as `push_update_options_found`, allowing it to persist across the entire PUSH_UPDATE sequence. This fixes an issue where large route lists sent via PUSH_UPDATE would result in only the last chunk's routes being applied, or previous routes being continuously deleted and re-added. Added unit test `test_incoming_push_continuation_route_accumulation` to verify the fix. Github: #925 Signed-off-by: Moritz Fain <[email protected]> Acked-by: Marco Baffo <[email protected]> Message-Id: <CAM8w-qEE6vHj=yUOpTFbM7DqPKzUV0NupvEG4rUefY=kNB2DxQ@mail.gmail.com> URL: https://www.mail-archive.com/[email protected]/msg34814.html Signed-off-by: Gert Doering <[email protected]>
1 parent 8bf8bea commit 309c233

File tree

6 files changed

+131
-26
lines changed

6 files changed

+131
-26
lines changed

src/openvpn/options.c

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3234,6 +3234,7 @@ pre_connect_restore(struct options *o, struct gc_arena *gc)
32343234

32353235
o->push_continuation = 0;
32363236
o->push_option_types_found = 0;
3237+
o->push_update_options_found = 0;
32373238
o->imported_protocol_flags = 0;
32383239
}
32393240

@@ -5400,14 +5401,14 @@ void
54005401
update_option(struct context *c, struct options *options, char *p[], bool is_inline,
54015402
const char *file, int line, const int level, const msglvl_t msglevel,
54025403
const unsigned int permission_mask, unsigned int *option_types_found,
5403-
struct env_set *es, unsigned int *update_options_found)
5404+
struct env_set *es)
54045405
{
54055406
const bool pull_mode = BOOL_CAST(permission_mask & OPT_P_PULL_MODE);
54065407
ASSERT(MAX_PARMS >= 7);
54075408

54085409
if (streq(p[0], "route") && p[1] && !p[5])
54095410
{
5410-
if (!(*update_options_found & OPT_P_U_ROUTE))
5411+
if (!(options->push_update_options_found & OPT_P_U_ROUTE))
54115412
{
54125413
VERIFY_PERMISSION(OPT_P_ROUTE);
54135414
if (!check_route_option(options, p, msglevel, pull_mode))
@@ -5420,12 +5421,12 @@ update_option(struct context *c, struct options *options, char *p[], bool is_inl
54205421
es, &c->net_ctx);
54215422
RESET_OPTION_ROUTES(options->routes, routes);
54225423
}
5423-
*update_options_found |= OPT_P_U_ROUTE;
5424+
options->push_update_options_found |= OPT_P_U_ROUTE;
54245425
}
54255426
}
54265427
else if (streq(p[0], "route-ipv6") && p[1] && !p[4])
54275428
{
5428-
if (!(*update_options_found & OPT_P_U_ROUTE6))
5429+
if (!(options->push_update_options_found & OPT_P_U_ROUTE6))
54295430
{
54305431
VERIFY_PERMISSION(OPT_P_ROUTE);
54315432
if (!check_route6_option(options, p, msglevel, pull_mode))
@@ -5438,12 +5439,12 @@ update_option(struct context *c, struct options *options, char *p[], bool is_inl
54385439
ROUTE_OPTION_FLAGS(&c->options), es, &c->net_ctx);
54395440
RESET_OPTION_ROUTES(options->routes_ipv6, routes_ipv6);
54405441
}
5441-
*update_options_found |= OPT_P_U_ROUTE6;
5442+
options->push_update_options_found |= OPT_P_U_ROUTE6;
54425443
}
54435444
}
54445445
else if (streq(p[0], "redirect-gateway") || streq(p[0], "redirect-private"))
54455446
{
5446-
if (!(*update_options_found & OPT_P_U_REDIR_GATEWAY))
5447+
if (!(options->push_update_options_found & OPT_P_U_REDIR_GATEWAY))
54475448
{
54485449
VERIFY_PERMISSION(OPT_P_ROUTE);
54495450
if (options->routes)
@@ -5456,12 +5457,12 @@ update_option(struct context *c, struct options *options, char *p[], bool is_inl
54565457
}
54575458
env_set_del(es, "route_redirect_gateway_ipv4");
54585459
env_set_del(es, "route_redirect_gateway_ipv6");
5459-
*update_options_found |= OPT_P_U_REDIR_GATEWAY;
5460+
options->push_update_options_found |= OPT_P_U_REDIR_GATEWAY;
54605461
}
54615462
}
54625463
else if (streq(p[0], "dns") && p[1])
54635464
{
5464-
if (!(*update_options_found & OPT_P_U_DNS))
5465+
if (!(options->push_update_options_found & OPT_P_U_DNS))
54655466
{
54665467
VERIFY_PERMISSION(OPT_P_DHCPDNS);
54675468
if (!check_dns_option(options, p, msglevel, pull_mode))
@@ -5470,13 +5471,13 @@ update_option(struct context *c, struct options *options, char *p[], bool is_inl
54705471
}
54715472
gc_free(&options->dns_options.gc);
54725473
CLEAR(options->dns_options);
5473-
*update_options_found |= OPT_P_U_DNS;
5474+
options->push_update_options_found |= OPT_P_U_DNS;
54745475
}
54755476
}
54765477
#if defined(_WIN32) || defined(TARGET_ANDROID)
54775478
else if (streq(p[0], "dhcp-option") && p[1] && !p[3])
54785479
{
5479-
if (!(*update_options_found & OPT_P_U_DHCP))
5480+
if (!(options->push_update_options_found & OPT_P_U_DHCP))
54805481
{
54815482
struct tuntap_options *o = &options->tuntap_options;
54825483
VERIFY_PERMISSION(OPT_P_DHCPDNS);
@@ -5506,17 +5507,17 @@ update_option(struct context *c, struct options *options, char *p[], bool is_inl
55065507
o->http_proxy_port = 0;
55075508
o->http_proxy = NULL;
55085509
#endif
5509-
*update_options_found |= OPT_P_U_DHCP;
5510+
options->push_update_options_found |= OPT_P_U_DHCP;
55105511
}
55115512
}
55125513
#else /* if defined(_WIN32) || defined(TARGET_ANDROID) */
55135514
else if (streq(p[0], "dhcp-option") && p[1] && !p[3])
55145515
{
5515-
if (!(*update_options_found & OPT_P_U_DHCP))
5516+
if (!(options->push_update_options_found & OPT_P_U_DHCP))
55165517
{
55175518
VERIFY_PERMISSION(OPT_P_DHCPDNS);
55185519
delete_all_dhcp_fo(options, &es->list);
5519-
*update_options_found |= OPT_P_U_DHCP;
5520+
options->push_update_options_found |= OPT_P_U_DHCP;
55205521
}
55215522
}
55225523
#endif /* if defined(_WIN32) || defined(TARGET_ANDROID) */

src/openvpn/options.h

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,7 @@ struct options
558558
bool pull; /* client pull of config options from server */
559559
int push_continuation;
560560
unsigned int push_option_types_found;
561+
unsigned int push_update_options_found; /* tracks which option types have been reset in current PUSH_UPDATE sequence */
561562
const char *auth_user_pass_file;
562563
bool auth_user_pass_file_inline;
563564
struct options_pre_connect *pre_connect;
@@ -863,14 +864,11 @@ void remove_option(struct context *c, struct options *options, char *p[], bool i
863864
* @param option_types_found A pointer to the variable where the flags corresponding to the options
864865
* found are stored.
865866
* @param es The environment set structure.
866-
* @param update_options_found A pointer to the variable where the flags corresponding to the update
867-
* options found are stored, used to check if an option of the same type has already been processed
868-
* by update_option() within the same push-update message.
869867
*/
870868
void update_option(struct context *c, struct options *options, char *p[], bool is_inline,
871869
const char *file, int line, const int level, const msglvl_t msglevel,
872870
const unsigned int permission_mask, unsigned int *option_types_found,
873-
struct env_set *es, unsigned int *update_options_found);
871+
struct env_set *es);
874872

875873
void parse_argv(struct options *options, const int argc, char *argv[], const msglvl_t msglevel,
876874
const unsigned int permission_mask, unsigned int *option_types_found,

src/openvpn/options_parse.c

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -517,7 +517,6 @@ apply_push_options(struct context *c, struct options *options, struct buffer *bu
517517
int line_num = 0;
518518
const char *file = "[PUSH-OPTIONS]";
519519
const msglvl_t msglevel = D_PUSH_ERRORS | M_OPTERR;
520-
unsigned int update_options_found = 0;
521520

522521
while (buf_parse(buf, ',', line, sizeof(line)))
523522
{
@@ -564,8 +563,13 @@ apply_push_options(struct context *c, struct options *options, struct buffer *bu
564563
}
565564
else
566565
{
566+
/*
567+
* Use persistent push_update_options_found from options struct to track
568+
* which option types have been reset across continuation messages.
569+
* This prevents routes from being reset on each continuation message.
570+
*/
567571
update_option(c, options, p, false, file, line_num, 0, msglevel, permission_mask,
568-
option_types_found, es, &update_options_found);
572+
option_types_found, es);
569573
}
570574
}
571575
}

src/openvpn/push.c

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -540,11 +540,15 @@ incoming_push_message(struct context *c, const struct buffer *buffer)
540540
}
541541
else
542542
{
543-
if (!option_types_found)
543+
/* Clear push_update_options_found for next PUSH_UPDATE sequence */
544+
c->options.push_update_options_found = 0;
545+
546+
/* Use accumulated option_types_found for the entire PUSH_UPDATE sequence */
547+
if (!c->options.push_option_types_found)
544548
{
545549
msg(M_WARN, "No updatable options found in incoming PUSH_UPDATE message");
546550
}
547-
else if (!do_update(c, option_types_found))
551+
else if (!do_update(c, c->options.push_option_types_found))
548552
{
549553
msg(D_PUSH_ERRORS, "Failed to update options");
550554
goto error;

tests/unit_tests/openvpn/test_options_parse.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ void
6060
update_option(struct context *c, struct options *options, char *p[], bool is_inline,
6161
const char *file, int line, const int level, const msglvl_t msglevel,
6262
const unsigned int permission_mask, unsigned int *option_types_found,
63-
struct env_set *es, unsigned int *update_options_found)
63+
struct env_set *es)
6464
{
6565
}
6666

tests/unit_tests/openvpn/test_push_update_msg.c

Lines changed: 102 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,33 @@ options_postprocess_pull(struct options *options, struct env_set *es)
5454
return true;
5555
}
5656

57+
/*
58+
* Counters to track route accumulation across continuation messages.
59+
* Used to verify the bug where update_options_found resets per message.
60+
*/
61+
static int route_reset_count = 0;
62+
static int route_add_count = 0;
63+
64+
static void
65+
reset_route_counters(void)
66+
{
67+
route_reset_count = 0;
68+
route_add_count = 0;
69+
}
70+
5771
bool
5872
apply_push_options(struct context *c, struct options *options, struct buffer *buf,
5973
unsigned int permission_mask, unsigned int *option_types_found,
6074
struct env_set *es, bool is_update)
6175
{
6276
char line[OPTION_PARM_SIZE];
6377

78+
/*
79+
* Use persistent push_update_options_found from options struct to track
80+
* which option types have been reset across continuation messages.
81+
* This is the FIXED behavior - routes are only reset once per PUSH_UPDATE sequence.
82+
*/
83+
6484
while (buf_parse(buf, ',', line, sizeof(line)))
6585
{
6686
unsigned int push_update_option_flags = 0;
@@ -84,10 +104,27 @@ apply_push_options(struct context *c, struct options *options, struct buffer *bu
84104
return false; /* Cause push/pull error and stop push processing */
85105
}
86106
}
87-
/*
88-
* No need to test also the application part here
89-
* (add_option/remove_option/update_option)
90-
*/
107+
108+
/* Simulate route handling from update_option() in options.c */
109+
if (strncmp(&line[i], "route ", 6) == 0)
110+
{
111+
if (!(options->push_update_options_found & OPT_P_U_ROUTE))
112+
{
113+
/* First route in entire PUSH_UPDATE sequence - reset routes once */
114+
route_reset_count++;
115+
options->push_update_options_found |= OPT_P_U_ROUTE;
116+
}
117+
route_add_count++;
118+
}
119+
/* Simulate add_option() push-continuation logic */
120+
else if (!strcmp(&line[i], "push-continuation 2"))
121+
{
122+
options->push_continuation = 2;
123+
}
124+
else if (!strcmp(&line[i], "push-continuation 1"))
125+
{
126+
options->push_continuation = 1;
127+
}
91128
}
92129
return true;
93130
}
@@ -292,6 +329,65 @@ test_incoming_push_message_mix2(void **state)
292329
free_buf(&buf);
293330
}
294331

332+
/**
333+
* Test that routes accumulate correctly across multiple continuation messages.
334+
* This test exposes a bug where update_options_found is reset to 0 for each
335+
* continuation message, causing routes to be reset on each message instead
336+
* of accumulating.
337+
*
338+
* Expected behavior: routes should only be reset ONCE (when the first route is received),
339+
* then all subsequent routes should accumulate.
340+
*
341+
* Current bug: routes are reset on the first route of EACH continuation message.
342+
*/
343+
static void
344+
test_incoming_push_continuation_route_accumulation(void **state)
345+
{
346+
struct context *c = *state;
347+
unsigned int option_types_found = 0;
348+
349+
reset_route_counters();
350+
351+
/* Message 1: first batch of routes, continuation 2 (more coming) */
352+
struct buffer buf1 = alloc_buf(512);
353+
const char *msg1 = "PUSH_UPDATE, route 10.1.0.0 255.255.0.0, route 10.2.0.0 255.255.0.0, route 10.3.0.0 255.255.0.0,push-continuation 2";
354+
buf_write(&buf1, msg1, strlen(msg1));
355+
356+
assert_int_equal(process_incoming_push_msg(c, &buf1, c->options.pull, pull_permission_mask(c),
357+
&option_types_found),
358+
PUSH_MSG_CONTINUATION);
359+
free_buf(&buf1);
360+
361+
/* Message 2: more routes, continuation 2 (more coming) */
362+
struct buffer buf2 = alloc_buf(512);
363+
const char *msg2 = "PUSH_UPDATE, route 10.4.0.0 255.255.0.0, route 10.5.0.0 255.255.0.0, route 10.6.0.0 255.255.0.0,push-continuation 2";
364+
buf_write(&buf2, msg2, strlen(msg2));
365+
366+
assert_int_equal(process_incoming_push_msg(c, &buf2, c->options.pull, pull_permission_mask(c),
367+
&option_types_found),
368+
PUSH_MSG_CONTINUATION);
369+
free_buf(&buf2);
370+
371+
/* Message 3: final batch of routes, continuation 1 (last message) */
372+
struct buffer buf3 = alloc_buf(512);
373+
const char *msg3 = "PUSH_UPDATE, route 10.7.0.0 255.255.0.0, route 10.8.0.0 255.255.0.0, route 10.9.0.0 255.255.0.0,push-continuation 1";
374+
buf_write(&buf3, msg3, strlen(msg3));
375+
376+
assert_int_equal(process_incoming_push_msg(c, &buf3, c->options.pull, pull_permission_mask(c),
377+
&option_types_found),
378+
PUSH_MSG_UPDATE);
379+
free_buf(&buf3);
380+
381+
/* Verify: all 9 routes should have been added */
382+
assert_int_equal(route_add_count, 9);
383+
384+
/*
385+
* Verify: route option is reset only one time in the first message
386+
* if a push-continuation is present.
387+
*/
388+
assert_int_equal(route_reset_count, 1);
389+
}
390+
295391
#ifdef ENABLE_MANAGEMENT
296392
char *r0[] = {
297393
"PUSH_UPDATE,redirect-gateway local,route 192.168.1.0 255.255.255.0",
@@ -603,6 +699,8 @@ main(void)
603699
cmocka_unit_test_setup_teardown(test_incoming_push_message_bad_format, setup, teardown),
604700
cmocka_unit_test_setup_teardown(test_incoming_push_message_mix, setup, teardown),
605701
cmocka_unit_test_setup_teardown(test_incoming_push_message_mix2, setup, teardown),
702+
cmocka_unit_test_setup_teardown(test_incoming_push_continuation_route_accumulation, setup,
703+
teardown),
606704
#ifdef ENABLE_MANAGEMENT
607705

608706
cmocka_unit_test_setup_teardown(test_send_push_msg0, setup2, teardown2),

0 commit comments

Comments
 (0)