Version in base suite: 1.20.20-2 Base version: network-manager-l2tp_1.20.20-2 Target version: network-manager-l2tp_1.20.20-2+deb13u1 Base file: /srv/ftp-master.debian.org/ftp/pool/main/n/network-manager-l2tp/network-manager-l2tp_1.20.20-2.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/n/network-manager-l2tp/network-manager-l2tp_1.20.20-2+deb13u1.dsc changelog | 12 + patches/CVE-2026-19624.patch | 89 ++++++++++++ patches/CVE-2026-75131.patch | 44 ++++++ patches/CVE-2026-75883.patch | 303 +++++++++++++++++++++++++++++++++++++++++++ patches/series | 3 5 files changed, 451 insertions(+) dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpjsmkor_u/network-manager-l2tp_1.20.20-2.dsc: no acceptable signature found dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpjsmkor_u/network-manager-l2tp_1.20.20-2+deb13u1.dsc: no acceptable signature found diff -Nru network-manager-l2tp-1.20.20/debian/changelog network-manager-l2tp-1.20.20/debian/changelog --- network-manager-l2tp-1.20.20/debian/changelog 2025-02-17 12:03:00.000000000 +0000 +++ network-manager-l2tp-1.20.20/debian/changelog 2026-09-06 06:20:10.000000000 +0000 @@ -1,3 +1,15 @@ +network-manager-l2tp (1.20.20-2+deb13u1) trixie-security; urgency=high + + * Security fixes for local privilege escalation to root involving: + - Injection of '\n' or '\r' control characters written into + /run/nm-l2tp-/ipsec.conf (CVE-2026-19624) + - Quote break-out via the 'user' string property written into + /run/nm-l2tp-/ppp-options (CVE-2026-75131) + - Partial strtol() on mtu/mru/mrru properties allow trailing string + injection written into /run/nm-l2tp-/ppp-options (CVE-2026-75883) + + -- Douglas Kosovic Sun, 06 Sep 2026 16:20:10 +1000 + network-manager-l2tp (1.20.20-2) unstable; urgency=medium * Bump Standards-Version to 4.7.2 diff -Nru network-manager-l2tp-1.20.20/debian/patches/CVE-2026-19624.patch network-manager-l2tp-1.20.20/debian/patches/CVE-2026-19624.patch --- network-manager-l2tp-1.20.20/debian/patches/CVE-2026-19624.patch 1970-01-01 00:00:00.000000000 +0000 +++ network-manager-l2tp-1.20.20/debian/patches/CVE-2026-19624.patch 2026-09-06 06:20:10.000000000 +0000 @@ -0,0 +1,89 @@ +From 47b293b1b7b64c75e3b966a430f1e6945690e01f Mon Sep 17 00:00:00 2001 +From: Douglas Kosovic +Date: Sun, 21 Jun 2026 22:24:26 +1000 +Subject: [PATCH] service: parser-hardening of control characters + +Disallow control charaters in username, VPN data-items and secrets. +--- + src/nm-l2tp-service.c | 40 +++++++++++++++++++++++++++++++++++++++- + 1 file changed, 39 insertions(+), 1 deletion(-) + +diff --git a/src/nm-l2tp-service.c b/src/nm-l2tp-service.c +index b5b419b..fa90927 100644 +--- a/src/nm-l2tp-service.c ++++ b/src/nm-l2tp-service.c +@@ -273,6 +273,20 @@ typedef struct ValidateInfo { + gboolean have_items; + } ValidateInfo; + ++static gboolean ++string_contains_control_char(const char *value) ++{ ++ if (!value) ++ return FALSE; ++ ++ for (const char *p = value; *p; p++) { ++ if (g_ascii_iscntrl(*p)) ++ return TRUE; ++ } ++ ++ return FALSE; ++} ++ + static void + validate_one_property(const char *key, const char *value, gpointer user_data) + { +@@ -296,6 +310,15 @@ validate_one_property(const char *key, const char *value, gpointer user_data) + continue; + switch (prop.type) { + case G_TYPE_STRING: ++ if (string_contains_control_char(value)) { ++ g_set_error(info->error, ++ NM_VPN_PLUGIN_ERROR, ++ NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS, ++ _("property '%s' contains a control character"), ++ prop.name); ++ return; ++ } ++ + if (!strcmp(prop.name, NM_L2TP_KEY_GATEWAY)) { + if (validate_gateway(value)) + return; /* valid */ +@@ -306,7 +329,6 @@ validate_one_property(const char *key, const char *value, gpointer user_data) + _("invalid gateway '%s'"), + value); + } +- + return; + case G_TYPE_UINT: + errno = 0; +@@ -1331,6 +1353,14 @@ nm_l2tp_config_write(NML2tpPlugin *plugin, NMSettingVpn *s_vpn, GError **error) + if (!value || !*value) + value = nm_setting_vpn_get_user_name(s_vpn); + if (value && *value) { ++ if (string_contains_control_char(value)) { ++ g_set_error(error, ++ NM_VPN_PLUGIN_ERROR, ++ NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS, ++ _("VPN username contains a control character")); ++ close(fd); ++ return FALSE; ++ } + write_config_option(fd, "user \"%s\"\n", value); + } + for (int i = 0; ppp_auth_options[i].name; i++) { +@@ -1780,6 +1810,14 @@ handle_need_secrets(NMDBusL2tpPpp *object, GDBusMethodInvocation *invocation, gp + return FALSE; + } + ++ if (string_contains_control_char(user)) { ++ g_dbus_method_invocation_return_error_literal(invocation, ++ NM_VPN_PLUGIN_ERROR, ++ NM_VPN_PLUGIN_ERROR_INVALID_CONNECTION, ++ _("VPN username contains a control character.")); ++ return FALSE; ++ } ++ + password = nm_setting_vpn_get_secret(s_vpn, NM_L2TP_KEY_PASSWORD); + if (!password || !strlen(password)) { + g_dbus_method_invocation_return_error_literal(invocation, diff -Nru network-manager-l2tp-1.20.20/debian/patches/CVE-2026-75131.patch network-manager-l2tp-1.20.20/debian/patches/CVE-2026-75131.patch --- network-manager-l2tp-1.20.20/debian/patches/CVE-2026-75131.patch 1970-01-01 00:00:00.000000000 +0000 +++ network-manager-l2tp-1.20.20/debian/patches/CVE-2026-75131.patch 2026-09-06 06:20:10.000000000 +0000 @@ -0,0 +1,44 @@ +diff --git a/src/nm-l2tp-service.c b/src/nm-l2tp-service.c +index 17692a0..92c6741 100644 +--- a/src/nm-l2tp-service.c ++++ b/src/nm-l2tp-service.c +@@ -287,6 +287,20 @@ string_contains_control_char(const char *value) + return FALSE; + } + ++static char * ++escape_pppd_quoted_word(const char *value) ++{ ++ GString *escaped = g_string_sized_new(strlen(value)); ++ ++ for (const char *p = value; *p; p++) { ++ if (*p == '\\' || *p == '"') ++ g_string_append_c(escaped, '\\'); ++ g_string_append_c(escaped, *p); ++ } ++ ++ return g_string_free(escaped, FALSE); ++} ++ + static void + validate_one_property(const char *key, const char *value, gpointer user_data) + { +@@ -1353,6 +1367,8 @@ nm_l2tp_config_write(NML2tpPlugin *plugin, NMSettingVpn *s_vpn, GError **error) + if (!value || !*value) + value = nm_setting_vpn_get_user_name(s_vpn); + if (value && *value) { ++ g_autofree char *escaped_value = NULL; ++ + if (string_contains_control_char(value)) { + g_set_error(error, + NM_VPN_PLUGIN_ERROR, +@@ -1361,7 +1377,8 @@ nm_l2tp_config_write(NML2tpPlugin *plugin, NMSettingVpn *s_vpn, GError **error) + close(fd); + return FALSE; + } +- write_config_option(fd, "user \"%s\"\n", value); ++ escaped_value = escape_pppd_quoted_word(value); ++ write_config_option(fd, "user \"%s\"\n", escaped_value); + } + for (int i = 0; ppp_auth_options[i].name; i++) { + value = nm_setting_vpn_get_data_item(s_vpn, ppp_auth_options[i].name); diff -Nru network-manager-l2tp-1.20.20/debian/patches/CVE-2026-75883.patch network-manager-l2tp-1.20.20/debian/patches/CVE-2026-75883.patch --- network-manager-l2tp-1.20.20/debian/patches/CVE-2026-75883.patch 1970-01-01 00:00:00.000000000 +0000 +++ network-manager-l2tp-1.20.20/debian/patches/CVE-2026-75883.patch 2026-09-06 06:20:10.000000000 +0000 @@ -0,0 +1,303 @@ +diff --git a/src/nm-l2tp-service.c b/src/nm-l2tp-service.c +index 92c6741..4693cc9 100644 +--- a/src/nm-l2tp-service.c ++++ b/src/nm-l2tp-service.c +@@ -301,6 +301,55 @@ escape_pppd_quoted_word(const char *value) + return g_string_free(escaped, FALSE); + } + ++static gboolean ++parse_uint_property(const char *value, guint *out) ++{ ++ guint64 parsed = 0; ++ const char *p; ++ ++ if (!value || !value[0]) ++ return FALSE; ++ ++ for (p = value; *p; p++) { ++ guint digit; ++ ++ if (!g_ascii_isdigit(*p)) ++ return FALSE; ++ digit = (guint) (*p - '0'); ++ if (parsed > (G_MAXUINT - digit) / 10) ++ return FALSE; ++ parsed = parsed * 10 + digit; ++ } ++ ++ if (out) ++ *out = (guint) parsed; ++ return TRUE; ++} ++ ++/* ++ * Quote one pppd options-file argument. pppd honours double quotes and a ++ * backslash escape, but kl2tpd historically did not. The kl2tpd path is ++ * therefore also changed below to pass only `file ` to ++ * pppd, so that pppd itself performs this parsing. ++ */ ++static char * ++ppp_quote_value(const char *value) ++{ ++ GString *quoted; ++ ++ if (!value || string_contains_control_char(value)) ++ return NULL; ++ ++ quoted = g_string_new("\""); ++ for (const char *p = value; *p; p++) { ++ if (*p == '\\' || *p == '"') ++ g_string_append_c(quoted, '\\'); ++ g_string_append_c(quoted, *p); ++ } ++ g_string_append_c(quoted, '"'); ++ return g_string_free(quoted, FALSE); ++} ++ + static void + validate_one_property(const char *key, const char *value, gpointer user_data) + { +@@ -318,7 +367,7 @@ validate_one_property(const char *key, const char *value, gpointer user_data) + + for (i = 0; info->table[i].name; i++) { + const ValidProperty prop = info->table[i]; +- long int tmp; ++ guint tmp; + + if (strcmp(prop.name, key)) + continue; +@@ -345,15 +394,17 @@ validate_one_property(const char *key, const char *value, gpointer user_data) + } + return; + case G_TYPE_UINT: +- errno = 0; +- tmp = strtol(value, NULL, 10); +- if (errno == 0) ++ if (parse_uint_property(value, &tmp) && ++ ((strcmp(prop.name, NM_L2TP_KEY_MRU) && ++ strcmp(prop.name, NM_L2TP_KEY_MTU) && ++ strcmp(prop.name, NM_L2TP_KEY_MRRU)) || ++ (tmp >= 128 && tmp <= 16384))) + return; /* valid */ + + g_set_error(info->error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS, +- _("invalid integer property '%s'"), ++ _("invalid unsigned integer property '%s'"), + key); + return; + case G_TYPE_BOOLEAN: +@@ -536,14 +587,15 @@ free_l2tpd_args(GPtrArray *args) + static gboolean + str_to_int(const char *str, long int *out) + { ++ char * end = NULL; + long int tmp_int; + +- if (!str) ++ if (!str || !str[0]) + return FALSE; + + errno = 0; +- tmp_int = strtol(str, NULL, 10); +- if (errno == 0) { ++ tmp_int = strtol(str, &end, 10); ++ if (errno == 0 && end != str && *end == '\0') { + *out = tmp_int; + return TRUE; + } +@@ -564,6 +616,24 @@ write_config_option(int fd, const char *format, ...) + va_end(args); + } + ++static gboolean ++write_ppp_quoted_option(int fd, const char *name, const char *value, GError **error) ++{ ++ g_autofree char *quoted = ppp_quote_value(value); ++ ++ if (!quoted) { ++ g_set_error(error, ++ NM_VPN_PLUGIN_ERROR, ++ NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS, ++ _("pppd option '%s' contains an unsafe control character"), ++ name); ++ return FALSE; ++ } ++ ++ write_config_option(fd, "%s %s\n", name, quoted); ++ return TRUE; ++} ++ + typedef struct { + const char *name; + GType type; +@@ -1119,7 +1189,12 @@ nm_l2tp_config_write(NML2tpPlugin *plugin, NMSettingVpn *s_vpn, GError **error) + write_config_option(fd, "encap = \"udp\"\n"); + write_config_option(fd, "[tunnel.t1.session.s1]\n"); + write_config_option(fd, "pseudowire = \"ppp\"\n"); +- write_config_option(fd, "pppd_args = \"%s/ppp-options\"\n", rundir); ++ /* ++ * kl2tpd splits this file on literal spaces and does not implement ++ * pppd's quoting grammar. Give it a fixed, data-free argv wrapper; ++ * pppd will parse the real root-owned options file itself. ++ */ ++ write_config_option(fd, "pppd_args = \"%s/ppp-options-kl2tpd\"\n", rundir); + } else { + /* xl2tpd config */ + filename = g_strdup_printf("%s/xl2tpd.conf", rundir); +@@ -1161,13 +1236,32 @@ nm_l2tp_config_write(NML2tpPlugin *plugin, NMSettingVpn *s_vpn, GError **error) + + close(fd); + ++ if (priv->l2tp_daemon == NM_L2TP_L2TP_DAEMON_KL2TPD) { ++ filename = g_strdup_printf("%s/ppp-options-kl2tpd", rundir); ++ fd = open(filename, ++ O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC | O_NOFOLLOW, ++ S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); ++ if (fd == -1 || fchown(fd, 0, 0) != 0 || fchmod(fd, 0644) != 0) { ++ if (fd != -1) ++ close(fd); ++ g_free(filename); ++ return nm_l2tp_ipsec_error(error, _("Could not write kl2tpd pppd argument wrapper.")); ++ } ++ write_config_option(fd, "file %s/ppp-options\n", rundir); ++ close(fd); ++ } ++ + /* PPP options */ + + filename = g_strdup_printf("%s/ppp-options", rundir); +- fd = open(filename, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); ++ fd = open(filename, ++ O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC | O_NOFOLLOW, ++ S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + g_free(filename); + +- if (fd == -1) { ++ if (fd == -1 || fchown(fd, 0, 0) != 0 || fchmod(fd, 0644) != 0) { ++ if (fd != -1) ++ close(fd); + return nm_l2tp_ipsec_error(error, _("Could not write ppp options.")); + } + +@@ -1236,8 +1330,18 @@ nm_l2tp_config_write(NML2tpPlugin *plugin, NMSettingVpn *s_vpn, GError **error) + + value = nm_setting_vpn_get_data_item(s_vpn, NM_L2TP_KEY_MRRU); + if (value) { ++ guint parsed; ++ ++ if (!parse_uint_property(value, &parsed) || parsed < 128 || parsed > 16384) { ++ g_set_error(error, ++ NM_VPN_PLUGIN_ERROR, ++ NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS, ++ _("invalid MRRU value")); ++ close(fd); ++ return FALSE; ++ } + write_config_option(fd, "multilink\n"); +- write_config_option(fd, "mrru %s\n", value); ++ write_config_option(fd, "mrru %u\n", parsed); + } + + if (!nm_setting_vpn_get_data_item(s_vpn, NM_L2TP_KEY_REQUIRE_MPPE) && +@@ -1340,26 +1444,44 @@ nm_l2tp_config_write(NML2tpPlugin *plugin, NMSettingVpn *s_vpn, GError **error) + write_config_option(fd, "need-peer-eap\n"); + if (tls_key_out_filename) { + if (g_file_test(tls_key_out_filename, G_FILE_TEST_EXISTS)) { +- write_config_option(fd, "key \"%s\"\n", tls_key_out_filename); ++ if (!write_ppp_quoted_option(fd, "key", tls_key_out_filename, error)) { ++ close(fd); ++ return FALSE; ++ } + } + } else { +- write_config_option(fd, "key \"%s\"\n", tls_key_filename); ++ if (!write_ppp_quoted_option(fd, "key", tls_key_filename, error)) { ++ close(fd); ++ return FALSE; ++ } + } + + if (tls_cert_out_filename) { + if (g_file_test(tls_cert_out_filename, G_FILE_TEST_EXISTS)) { +- write_config_option(fd, "cert \"%s\"\n", tls_cert_out_filename); ++ if (!write_ppp_quoted_option(fd, "cert", tls_cert_out_filename, error)) { ++ close(fd); ++ return FALSE; ++ } + } + } else { +- write_config_option(fd, "cert \"%s\"\n", tls_cert_filename); ++ if (!write_ppp_quoted_option(fd, "cert", tls_cert_filename, error)) { ++ close(fd); ++ return FALSE; ++ } + } + + if (tls_ca_out_filename) { + if (g_file_test(tls_ca_out_filename, G_FILE_TEST_EXISTS)) { +- write_config_option(fd, "ca \"%s\"\n", tls_ca_out_filename); ++ if (!write_ppp_quoted_option(fd, "ca", tls_ca_out_filename, error)) { ++ close(fd); ++ return FALSE; ++ } + } + } else if (tls_ca_filename) { +- write_config_option(fd, "ca \"%s\"\n", tls_ca_filename); ++ if (!write_ppp_quoted_option(fd, "ca", tls_ca_filename, error)) { ++ close(fd); ++ return FALSE; ++ } + } + } else { + /* Username; try L2TP specific username first, then generic username */ +@@ -1424,14 +1546,34 @@ nm_l2tp_config_write(NML2tpPlugin *plugin, NMSettingVpn *s_vpn, GError **error) + + value = nm_setting_vpn_get_data_item(s_vpn, NM_L2TP_KEY_MRU); + if (value) { +- write_config_option(fd, "mru %s\n", value); ++ guint parsed; ++ ++ if (!parse_uint_property(value, &parsed) || parsed < 128 || parsed > 16384) { ++ g_set_error(error, ++ NM_VPN_PLUGIN_ERROR, ++ NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS, ++ _("invalid MRU value")); ++ close(fd); ++ return FALSE; ++ } ++ write_config_option(fd, "mru %u\n", parsed); + } else { + write_config_option(fd, "mru 1400\n"); + } + + value = nm_setting_vpn_get_data_item(s_vpn, NM_L2TP_KEY_MTU); + if (value) { +- write_config_option(fd, "mtu %s\n", value); ++ guint parsed; ++ ++ if (!parse_uint_property(value, &parsed) || parsed < 128 || parsed > 16384) { ++ g_set_error(error, ++ NM_VPN_PLUGIN_ERROR, ++ NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS, ++ _("invalid MTU value")); ++ close(fd); ++ return FALSE; ++ } ++ write_config_option(fd, "mtu %u\n", parsed); + } else { + /* Default MTU to 1400, which is also what Microsoft Windows uses */ + write_config_option(fd, "mtu 1400\n"); +@@ -2259,6 +2401,10 @@ real_disconnect(NMVpnServicePlugin *plugin, GError **err) + unlink(filename); + g_free(filename); + ++ filename = g_strdup_printf(RUNSTATEDIR "/nm-l2tp-%s/ppp-options-kl2tpd", priv->uuid); ++ unlink(filename); ++ g_free(filename); ++ + filename = g_strdup_printf(RUNSTATEDIR "/nm-l2tp-%s/ppp-options", priv->uuid); + unlink(filename); + g_free(filename); diff -Nru network-manager-l2tp-1.20.20/debian/patches/series network-manager-l2tp-1.20.20/debian/patches/series --- network-manager-l2tp-1.20.20/debian/patches/series 2021-10-25 08:22:26.000000000 +0000 +++ network-manager-l2tp-1.20.20/debian/patches/series 2026-09-06 06:20:10.000000000 +0000 @@ -1 +1,4 @@ # Patches for network-manager-l2tp +CVE-2026-19624.patch +CVE-2026-75131.patch +CVE-2026-75883.patch