acassen / keepalived

Keepalived
https://www.keepalived.org
GNU General Public License v2.0
4.01k stars 736 forks source link

`misc_path` option unescaping issues #2481

Closed corubba closed 1 month ago

corubba commented 1 month ago

Describe the bug

During the parsing of the config file, the quoted string value of the misc_path option is unescaped twice, resulting in behaviour violating the documented escaping rules. Additionally, the "greedy" handling of the hexadecimal \xXX escape sequence makes it potentially discard characters.

To Reproduce

1) Create the to-be-checked file with touch /tmp/my\\x20file (the shell requires the one backslash to be escaped) 2) Doublecheck it's correctly created; there should be a backslash in the name

% ls /tmp/m*
'/tmp/my\x20file'

3) Start keepalived using the (minimal) config from below 3) Wait for the healthcheck command to be executed 3) See the command and healthcheck failing, while it should succeed

Expected behavior

The quoted string value of the misc_path option is unescaped/used following the documented rules.

Keepalived version

Initially encountered in v2.2.7, still reproducable with the current master branch.

Keepalived v2.3.1 (10/18,2024), git commit v2.3.1-88-g604c0e9c

Copyright(C) 2001-2024 Alexandre Cassen, <acassen@gmail.com>

Built with kernel headers for Linux 6.10.0
Running on Linux 6.11.3-arch1-1 #1 SMP PREEMPT_DYNAMIC Thu, 10 Oct 2024 20:11:06 +0000
Distro: Arch Linux

configure options:

Config options:  NFTABLES LVS VRRP VRRP_AUTH VRRP_VMAC OLD_CHKSUM_COMPAT INIT=systemd SYSTEMD_NOTIFY

System options:  VSYSLOG MEMFD_CREATE IPV6_MULTICAST_ALL LIBKMOD IPV4_DEVCONF LIBNL3 RTA_ENCAP RTA_EXPIRES RTA_NEWDST RTA_PREF FRA_SUPPRESS_PREFIXLEN FRA_SUPPRESS_IFGROUP FRA_TUN_ID RTAX_CC_ALGO RTAX_QUICKACK RTEXT_FILTER_SKIP_STATS FRA_L3MDEV FRA_UID_RANGE RTAX_FASTOPEN_NO_COOKIE RTA_VIA FRA_PROTOCOL FRA_IP_PROTO FRA_SPORT_RANGE FRA_DPORT_RANGE RTA_TTL_PROPAGATE IFA_FLAGS LWTUNNEL_ENCAP_MPLS LWTUNNEL_ENCAP_ILA IPTABLES NET_LINUX_IF_H_COLLISION LIBIPVS_NETLINK IPVS_DEST_ATTR_ADDR_FAMILY IPVS_SYNCD_ATTRIBUTES IPVS_64BIT_STATS IPVS_TUN_TYPE IPVS_TUN_CSUM IPVS_TUN_GRE VRRP_IPVLAN IFLA_LINK_NETNSID GLOB_BRACE GLOB_ALTDIRFUNC INET6_ADDR_GEN_MODE VRF SO_MARK

Distro:

Details of any containerisation or hosted service (e.g. AWS)

Runs on bare metal, no container or hosted service.

Configuration file:

global_defs {
  enable_script_security
  script_user nobody
}

virtual_server 127.0.0.1 1234 {
  delay_loop 3
  real_server 127.0.0.1 2345 {
    MISC_CHECK {
      misc_path "/bin/test -f /tmp/my\\x20file"
    }   
  }
}

Notify and track scripts

None.

System Log entries

The output contains additional crude debug output I added to better show the problem. The strings are printed first plain character-wise, and then below character-wise their hexadecimal value. See "Additional context" below for a more detailed description.

Fri Oct 18 23:14:11 2024: Starting Keepalived v2.3.1 (10/18,2024), git commit v2.3.1-88-g604c0e9c
Fri Oct 18 23:14:11 2024: Running on Linux 6.11.3-arch1-1 #1 SMP PREEMPT_DYNAMIC Thu, 10 Oct 2024 20:11:06 +0000 (built for Linux 6.10.0)
Fri Oct 18 23:14:11 2024: Command line: 'bin/keepalived' '--use-file=escape_config' '--log-console' '--dont-fork'
Fri Oct 18 23:14:11 2024: Configuration file escape_config
Fri Oct 18 23:14:11 2024: NOTICE: setting config option max_auto_priority should result in better keepalived performance
Fri Oct 18 23:14:11 2024: Starting Healthcheck child process, pid=18952
Calling alloc_strvec_quoted_escaped() from misc_path_handler()
alloc_strvec_quoted_escaped() called with input:
m  i  s  c  _  p  a  t  h     "  /  b  i  n  /  t  e  s  t     -  f     /  t  m  p  /  m  y  \  \  x  2  0  f  i  l  e  "
6d 69 73 63 5f 70 61 74 68 20 22 2f 62 69 6e 2f 74 65 73 74 20 2d 66 20 2f 74 6d 70 2f 6d 79 5c 5c 78 32 30 66 69 6c 65 22
alloc_strvec_quoted_escaped() returns output:
m  i  s  c  _  p  a  t  h     /  b  i  n  /  t  e  s  t     -  f     /  t  m  p  /  m  y  \  x  2  0  f  i  l  e
6d 69 73 63 5f 70 61 74 68 20 2f 62 69 6e 2f 74 65 73 74 20 2d 66 20 2f 74 6d 70 2f 6d 79 5c 78 32 30 66 69 6c 65
Calling alloc_strvec_quoted_escaped() from set_script_params_array()
alloc_strvec_quoted_escaped() called with input:
/  b  i  n  /  t  e  s  t     -  f     /  t  m  p  /  m  y  \  x  2  0  f  i  l  e
2f 62 69 6e 2f 74 65 73 74 20 2d 66 20 2f 74 6d 70 2f 6d 79 5c 78 32 30 66 69 6c 65
alloc_strvec_quoted_escaped() returns output:
/  b  i  n  /  t  e  s  t     -  f     /  t  m  p  /  m  y     i  l  e
2f 62 69 6e 2f 74 65 73 74 20 2d 66 20 2f 74 6d 70 2f 6d 79 0f 69 6c 65
Fri Oct 18 23:14:11 2024: Virtual server [127.0.0.1]:none:1234: no protocol set - defaulting to UDP 
Fri Oct 18 23:14:11 2024: Virtual server [127.0.0.1]:udp:1234: no scheduler set, setting default 'wlc'
Fri Oct 18 23:14:11 2024: Virtual server [127.0.0.1]:udp:1234: no forwarding method set, setting default NAT 
Fri Oct 18 23:14:11 2024: Initializing ipvs
Fri Oct 18 23:14:11 2024: Gained quorum 1+0=1 <= 1 for VS [127.0.0.1]:udp:1234
Fri Oct 18 23:14:11 2024: Activating healthchecker for service [127.0.0.1]:udp:2345 for VS [127.0.0.1]:udp:1234
Fri Oct 18 23:14:11 2024: Startup complete
Calling system_call_script() from misc_check_thread()
system_call_script() will now execute:
/  b  i  n  /  t  e  s  t     -  f     /  t  m  p  /  m  y     i  l  e
2f 62 69 6e 2f 74 65 73 74    2d 66    2f 74 6d 70 2f 6d 79 0f 69 6c 65
Fri Oct 18 23:14:14 2024: Misc check for [[127.0.0.1]:udp:2345 VS [127.0.0.1]:udp:1234] by [/bin/test] failed with retry disabled (exited with status 1). 
Fri Oct 18 23:14:14 2024: Removing service [127.0.0.1]:udp:2345 from VS [127.0.0.1]:udp:1234
Fri Oct 18 23:14:14 2024: Lost quorum 1-0=1 > 0 for VS [127.0.0.1]:udp:1234
^C
Fri Oct 18 23:14:15 2024: Shutting down service [127.0.0.1]:udp:2345 from VS [127.0.0.1]:udp:1234
Fri Oct 18 23:14:15 2024: Stopping
Fri Oct 18 23:14:15 2024: Stopped
Fri Oct 18 23:14:15 2024: Stopped Keepalived v2.3.1 (10/18,2024), git commit v2.3.1-88-g604c0e9c

Did keepalived coredump?

No.

Additional context

When parsing the config file, the alloc_strvec_quoted_escaped() function is called twice, first in misc_path_handler() and then shortly after again in set_script_params_array() (which is called from misc_path_handler() too). Because of this, the value gets unescaped twice: The first time the my\\x20file gets turned into my\x20file, which gets turned into myile with the invisible "shift in" character 0x0f between the y and i; and this is what ends up in the command that gets executed as healthcheck.

The astute reader may wonder where the 0x0f comes from when the original escape sequence was \x20. When the parser encounters the \x sequence, it will continue to read characters until it finds a non-hexadecimal one (aka not [0-9a-fA-F]); so in this case it reads \x20f and stops only when it finds the i. Because the final value of the sequence is stored in a 8 bit char (and a signed one at that!) and shifted by 4 bit for every hexadecimal character, the 2 is "shifted out" and effectively discarded when reading the third character f. For any hexadecimal sequence only the last two characters survive, the sequences \x12 and \x0011fff773312 will result in the same final value 0x12. To this end, the octal escape sequence \NNN behaves as one would expect because it reads exactly three numbers following the backslash.

Since the value is unescaped twice, the workaround I found (and am using for the moment) is to just escape it twice too. Using the line misc_path "/bin/test -f /tmp/my\\\\x20file" (that is with four instead of two backslashes) in the config makes the healthcheck work as wanted. The first unescaping will turn my\\\\x20file into my\\x20file, and the second then into my\x20file. Below the matching console output.

Fri Oct 18 23:16:54 2024: Starting Keepalived v2.3.1 (10/18,2024), git commit v2.3.1-88-g604c0e9c
Fri Oct 18 23:16:54 2024: Running on Linux 6.11.3-arch1-1 #1 SMP PREEMPT_DYNAMIC Thu, 10 Oct 2024 20:11:06 +0000 (built for Linux 6.10.0)
Fri Oct 18 23:16:54 2024: Command line: 'bin/keepalived' '--use-file=escape_config' '--log-console' '--dont-fork'
Fri Oct 18 23:16:54 2024: Configuration file escape_config
Fri Oct 18 23:16:54 2024: NOTICE: setting config option max_auto_priority should result in better keepalived performance
Fri Oct 18 23:16:54 2024: Starting Healthcheck child process, pid=19695
Calling alloc_strvec_quoted_escaped() from misc_path_handler()
alloc_strvec_quoted_escaped() called with input:
m  i  s  c  _  p  a  t  h     "  /  b  i  n  /  t  e  s  t     -  f     /  t  m  p  /  m  y  \  \  \  \  x  2  0  f  i  l  e  "
6d 69 73 63 5f 70 61 74 68 20 22 2f 62 69 6e 2f 74 65 73 74 20 2d 66 20 2f 74 6d 70 2f 6d 79 5c 5c 5c 5c 78 32 30 66 69 6c 65 22
alloc_strvec_quoted_escaped() returns output:
m  i  s  c  _  p  a  t  h     /  b  i  n  /  t  e  s  t     -  f     /  t  m  p  /  m  y  \  \  x  2  0  f  i  l  e
6d 69 73 63 5f 70 61 74 68 20 2f 62 69 6e 2f 74 65 73 74 20 2d 66 20 2f 74 6d 70 2f 6d 79 5c 5c 78 32 30 66 69 6c 65
Calling alloc_strvec_quoted_escaped() from set_script_params_array()
alloc_strvec_quoted_escaped() called with input:
/  b  i  n  /  t  e  s  t     -  f     /  t  m  p  /  m  y  \  \  x  2  0  f  i  l  e
2f 62 69 6e 2f 74 65 73 74 20 2d 66 20 2f 74 6d 70 2f 6d 79 5c 5c 78 32 30 66 69 6c 65
alloc_strvec_quoted_escaped() returns output:
/  b  i  n  /  t  e  s  t     -  f     /  t  m  p  /  m  y  \  x  2  0  f  i  l  e
2f 62 69 6e 2f 74 65 73 74 20 2d 66 20 2f 74 6d 70 2f 6d 79 5c 78 32 30 66 69 6c 65
Fri Oct 18 23:16:54 2024: Virtual server [127.0.0.1]:none:1234: no protocol set - defaulting to UDP 
Fri Oct 18 23:16:54 2024: Virtual server [127.0.0.1]:udp:1234: no scheduler set, setting default 'wlc'
Fri Oct 18 23:16:54 2024: Virtual server [127.0.0.1]:udp:1234: no forwarding method set, setting default NAT 
Fri Oct 18 23:16:54 2024: Initializing ipvs
Fri Oct 18 23:16:54 2024: Gained quorum 1+0=1 <= 1 for VS [127.0.0.1]:udp:1234
Fri Oct 18 23:16:54 2024: Activating healthchecker for service [127.0.0.1]:udp:2345 for VS [127.0.0.1]:udp:1234
Fri Oct 18 23:16:54 2024: Startup complete
Calling system_call_script() from misc_check_thread()
system_call_script() will now execute:
/  b  i  n  /  t  e  s  t     -  f     /  t  m  p  /  m  y  \  x  2  0  f  i  l  e
2f 62 69 6e 2f 74 65 73 74    2d 66    2f 74 6d 70 2f 6d 79 5c 78 32 30 66 69 6c 65
Fri Oct 18 23:16:55 2024: Misc check for [[127.0.0.1]:udp:2345 VS [127.0.0.1]:udp:1234] by [/bin/test] succeeded.
^C
Fri Oct 18 23:16:55 2024: Shutting down service [127.0.0.1]:udp:2345 from VS [127.0.0.1]:udp:1234
Fri Oct 18 23:16:55 2024: Stopping
Fri Oct 18 23:16:55 2024: Stopped
Fri Oct 18 23:16:55 2024: Stopped Keepalived v2.3.1 (10/18,2024), git commit v2.3.1-88-g604c0e9c

The example here using the \x20 (which is a space) was choosen to be minimal and easily reproducible. The original motivation for executing a command with an embedded backslash escape sequence is for checking the status of an systemd unit using something like /bin/systemctl --system is-active systemd-nspawn@My\x20Container\x201.service. See the systemd unit name escaping for more info.

pqarmitage commented 1 month ago

@corubba Many thanks for the detailed description of this problem. This issue was caused by two problems: i) the greedy parsing of \xNN hex strings, and ii) the double evaluation of escaped characters.

Commit ecdb339 resolves the parsing of hex escape sequences, and commit af7aa8b resolves the double parsing of escape sequences.

During the resolution of these issues, I also found a problem of inappropriate logging of "unmatched quotes" warnings, which are resolved by commit 055a7b1. I also discovered that the parsing of /etc/iproute/rt* files was not ideal, and this is resolved by commit c875649.

Many thanks for reporting these issues.

corubba commented 1 month ago

Can confirm it works as expected now. Appreciate the fast fix!