diff --git a/openvpn-CVE-2024-5594.patch b/openvpn-CVE-2024-5594.patch new file mode 100644 index 0000000..ad3f5f2 --- /dev/null +++ b/openvpn-CVE-2024-5594.patch @@ -0,0 +1,214 @@ +--- src.orig/openvpn/buffer.c 2025-01-22 09:11:26.945102537 +0100 ++++ src/openvpn/buffer.c 2025-01-22 09:15:18.992145494 +0100 +@@ -1113,6 +1113,21 @@ + return ret; + } + ++bool ++string_check_buf(struct buffer *buf, const unsigned int inclusive, const unsigned int exclusive) ++{ ++ ASSERT(buf); ++ for (int i = 0; i < BLEN(buf); i++) ++ { ++ char c = BSTR(buf)[i]; ++ if (!char_inc_exc(c, inclusive, exclusive)) ++ { ++ return false; ++ } ++ } ++ return true; ++} ++ + const char * + string_mod_const(const char *str, + const unsigned int inclusive, +--- src.orig/openvpn/buffer.h 2025-01-22 09:11:26.945102537 +0100 ++++ src/openvpn/buffer.h 2025-01-22 09:16:50.090383898 +0100 +@@ -944,6 +944,17 @@ + bool string_class(const char *str, const unsigned int inclusive, const unsigned int exclusive); + + bool string_mod(char *str, const unsigned int inclusive, const unsigned int exclusive, const char replace); ++/** ++ * Check a buffer if it only consists of allowed characters. ++ * ++ * @param buf The buffer to be checked. ++ * @param inclusive The character classes that are allowed. ++ * @param exclusive Character classes that are not allowed even if they are also in inclusive. ++ * @return True if the string consists only of allowed characters, false otherwise. ++ */ ++bool ++string_check_buf(struct buffer *buf, const unsigned int inclusive, const unsigned int exclusive); ++ + + const char *string_mod_const(const char *str, + const unsigned int inclusive, +--- src.orig/openvpn/forward.c 2025-01-22 09:11:26.948102576 +0100 ++++ src/openvpn/forward.c 2025-01-22 09:27:02.718712050 +0100 +@@ -230,6 +230,52 @@ + } + } + ++static void ++parse_incoming_control_channel_command(struct context *c, struct buffer *buf) ++{ ++ if (buf_string_match_head_str(buf, "AUTH_FAILED")) ++ { ++ receive_auth_failed(c, buf); ++ } ++ else if (buf_string_match_head_str(buf, "PUSH_")) ++ { ++ incoming_push_message(c, buf); ++ } ++ else if (buf_string_match_head_str(buf, "RESTART")) ++ { ++ server_pushed_signal(c, buf, true, 7); ++ } ++ else if (buf_string_match_head_str(buf, "HALT")) ++ { ++ server_pushed_signal(c, buf, false, 4); ++ } ++ else if (buf_string_match_head_str(buf, "INFO_PRE")) ++ { ++ server_pushed_info(c, buf, 8); ++ } ++ else if (buf_string_match_head_str(buf, "INFO")) ++ { ++ server_pushed_info(c, buf, 4); ++ } ++ else if (buf_string_match_head_str(buf, "CR_RESPONSE")) ++ { ++ receive_cr_response(c, buf); ++ } ++ else if (buf_string_match_head_str(buf, "AUTH_PENDING")) ++ { ++ receive_auth_pending(c, buf); ++ } ++ else if (buf_string_match_head_str(buf, "EXIT")) ++ { ++ receive_exit_message(c); ++ } ++ else ++ { ++ msg(D_PUSH_ERRORS, "WARNING: Received unknown control message: %s", BSTR(buf)); ++ } ++} ++ ++ + /* + * Handle incoming configuration + * messages on the control channel. +@@ -245,51 +291,37 @@ + struct buffer buf = alloc_buf_gc(len, &gc); + if (tls_rec_payload(c->c2.tls_multi, &buf)) + { +- /* force null termination of message */ +- buf_null_terminate(&buf); +- +- /* enforce character class restrictions */ +- string_mod(BSTR(&buf), CC_PRINT, CC_CRLF, 0); +- +- if (buf_string_match_head_str(&buf, "AUTH_FAILED")) +- { +- receive_auth_failed(c, &buf); +- } +- else if (buf_string_match_head_str(&buf, "PUSH_")) +- { +- incoming_push_message(c, &buf); +- } +- else if (buf_string_match_head_str(&buf, "RESTART")) +- { +- server_pushed_signal(c, &buf, true, 7); +- } +- else if (buf_string_match_head_str(&buf, "HALT")) +- { +- server_pushed_signal(c, &buf, false, 4); +- } +- else if (buf_string_match_head_str(&buf, "INFO_PRE")) +- { +- server_pushed_info(c, &buf, 8); +- } +- else if (buf_string_match_head_str(&buf, "INFO")) +- { +- server_pushed_info(c, &buf, 4); +- } +- else if (buf_string_match_head_str(&buf, "CR_RESPONSE")) +- { +- receive_cr_response(c, &buf); +- } +- else if (buf_string_match_head_str(&buf, "AUTH_PENDING")) +- { +- receive_auth_pending(c, &buf); +- } +- else if (buf_string_match_head_str(&buf, "EXIT")) +- { +- receive_exit_message(c); +- } +- else ++ while (BLEN(&buf) > 1) + { +- msg(D_PUSH_ERRORS, "WARNING: Received unknown control message: %s", BSTR(&buf)); ++ /* commands on the control channel are seperated by 0x00 bytes. ++ * cmdlen does not include the 0 byte of the string */ ++ int cmdlen = (int)strnlen(BSTR(&buf), BLEN(&buf)); ++ if (cmdlen < BLEN(&buf)) ++ { ++ /* include the NUL byte and ensure NUL termination */ ++ int cmdlen = (int)strlen(BSTR(&buf)) + 1; ++ /* Construct a buffer that only holds the current command and ++ * its closing NUL byte */ ++ struct buffer cmdbuf = alloc_buf_gc(cmdlen, &gc); ++ buf_write(&cmdbuf, BPTR(&buf), cmdlen); ++ /* check we have only printable characters or null byte in the ++ * command string and no newlines */ ++ if (!string_check_buf(&buf, CC_PRINT | CC_NULL, CC_CRLF)) ++ { ++ msg(D_PUSH_ERRORS, "WARNING: Received control with invalid characters: %s", ++ format_hex(BPTR(&buf), BLEN(&buf), 256, &gc)); ++ } ++ else ++ { ++ parse_incoming_control_channel_command(c, &cmdbuf); ++ } ++ } ++ else ++ { ++ msg(D_PUSH_ERRORS, "WARNING: Ignoring control channel " ++ "message command without NUL termination"); ++ } ++ buf_advance(&buf, cmdlen); + } + } + else +--- tests.orig/unit_tests/openvpn/test_buffer.c 2025-01-22 09:11:56.003473042 +0100 ++++ tests/unit_tests/openvpn/test_buffer.c 2025-01-22 09:30:26.633484093 +0100 +@@ -259,6 +259,22 @@ + gc_free(&gc); + } + ++static void ++test_character_string_mod_buf(void **state) ++{ ++ struct gc_arena gc = gc_new(); ++ struct buffer buf = alloc_buf_gc(1024, &gc); ++ const char test1[] = "There is a nice 1234\x00 year old tree!"; ++ buf_write(&buf, test1, sizeof(test1)); ++ /* allow the null bytes and string but not the ! */ ++ assert_false(string_check_buf(&buf, CC_ALNUM | CC_SPACE | CC_NULL, 0)); ++ /* remove final ! and null byte to pass */ ++ buf_inc_len(&buf, -2); ++ assert_true(string_check_buf(&buf, CC_ALNUM | CC_SPACE | CC_NULL, 0)); ++ /* Check excluding digits works */ ++ assert_false(string_check_buf(&buf, CC_ALNUM | CC_SPACE | CC_NULL, CC_DIGIT)); ++ gc_free(&gc); ++} + + int + main(void) +@@ -289,6 +305,7 @@ + cmocka_unit_test(test_buffer_free_gc_one), + cmocka_unit_test(test_buffer_free_gc_two), + cmocka_unit_test(test_buffer_gc_realloc), ++ cmocka_unit_test(test_character_string_mod_buf) + }; + + return cmocka_run_group_tests_name("buffer", tests, NULL, NULL); diff --git a/openvpn.changes b/openvpn.changes index 711fefd..32adb14 100644 --- a/openvpn.changes +++ b/openvpn.changes @@ -3,6 +3,13 @@ Wed Jan 22 16:35:27 UTC 2025 - Dominique Leuenberger - Drop rcFOO symlinks for CODE16 (PED-266). +------------------------------------------------------------------- +Wed Jan 22 08:55:44 UTC 2025 - Rahul Jain + +- FIX:VUL-0 CVE-2024-5594: openvpn: properly handle null bytes and + invalid characters in control messages(bsc#1235147 CVE-2024-5594) + Patchname:openvpn-CVE-2024-5594.patch + ------------------------------------------------------------------- Fri Dec 20 08:13:18 UTC 2024 - Jan Engelhardt diff --git a/openvpn.spec b/openvpn.spec index 6ce6110..0f7dde1 100644 --- a/openvpn.spec +++ b/openvpn.spec @@ -39,6 +39,7 @@ Source10: %{name}-tmpfile.conf Source11: rc%{name} Patch1: %{name}-2.3-plugin-man.dif Patch2: openvpn-CVE-2024-28882.patch +Patch3: openvpn-CVE-2024-5594.patch BuildRequires: iproute2 BuildRequires: libcap-ng-devel BuildRequires: liblz4-devel