File CVE-2022-46174.patch of Package aws-efs-utils.28250
Summary:
This removes all potential concurrency issues during tls port selection
and ensures that concurrent mounts will never select the same port.
Patch for https://github.com/aws/efs-utils/commit/f3a8f88167d55caa2f78aeb72d4dc1987a9ed62d
Index: efs-utils-1.7/src/mount_efs/__init__.py
===================================================================
--- efs-utils-1.7.orig/src/mount_efs/__init__.py
+++ efs-utils-1.7/src/mount_efs/__init__.py
@@ -169,7 +169,7 @@ def get_tls_port_range(config):
return lower_bound, upper_bound
-def choose_tls_port(config):
+def choose_tls_port(config, state_file_dir):
lower_bound, upper_bound = get_tls_port_range(config)
tls_ports = list(range(lower_bound, upper_bound))
@@ -183,10 +183,17 @@ def choose_tls_port(config):
sock = socket.socket()
for tls_port in ports_to_try:
+ mount = find_existing_mount_using_tls_port(state_file_dir, tls_port)
+ if mount:
+ logging.debug(
+ "Skip binding TLS port %s as it is already assigned to %s",
+ tls_port,
+ mount,
+ )
+ continue
try:
sock.bind(('localhost', tls_port))
- sock.close()
- return tls_port
+ return sock
except socket.error:
continue
@@ -422,23 +429,52 @@ def bootstrap_tls(config, init_system, d
if not os.path.exists(state_file_dir):
create_state_file_dir(config, state_file_dir)
- tls_port = choose_tls_port(config)
- options['tlsport'] = tls_port
- verify_level = int(options.get('verify', DEFAULT_STUNNEL_VERIFY_LEVEL))
- options['verify'] = verify_level
-
- stunnel_config_file = write_stunnel_config_file(config, state_file_dir, fs_id, mountpoint, tls_port, dns_name, verify_level)
+ tls_port_sock = choose_tls_port(config, state_file_dir)
+ tls_port = get_tls_port_from_sock(tls_port_sock)
- tunnel_args = ['/usr/sbin/stunnel', stunnel_config_file]
+ try:
+ options['tlsport'] = tls_port
+ verify_level = int(options.get('verify', DEFAULT_STUNNEL_VERIFY_LEVEL))
+ options['verify'] = verify_level
+
+ stunnel_config_file = write_stunnel_config_file(config, state_file_dir, fs_id, mountpoint, tls_port, dns_name, verify_level)
+
+ tunnel_args = ['/usr/sbin/stunnel', stunnel_config_file]
+
+ temp_tls_state_file = write_tls_tunnel_state_file(
+ fs_id,
+ mountpoint,
+ tls_port,
+ -1,
+ tunnel_args,
+ [stunnel_config_file],
+ state_file_dir
+ )
+ finally:
+ logging.debug("Closing socket used to choose TLS port %s.", tls_port)
+ tls_port_sock.close()
- # launch the tunnel in a process group so if it has any child processes, they can be killed easily by the mount watchdog
+ # launch the tunnel in a process group so if it has any child processes,
+ # they can be killed easily by the mount watchdog
logging.info('Starting TLS tunnel: "%s"', ' '.join(tunnel_args))
tunnel_proc = subprocess.Popen(
- tunnel_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, preexec_fn=os.setsid, close_fds=True)
+ tunnel_args,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ preexec_fn=os.setsid,
+ close_fds=True
+ )
logging.info('Started TLS tunnel, pid: %d', tunnel_proc.pid)
- temp_tls_state_file = write_tls_tunnel_state_file(fs_id, mountpoint, tls_port, tunnel_proc.pid, tunnel_args,
- [stunnel_config_file], state_file_dir)
+ temp_tls_state_file = write_tls_tunnel_state_file(
+ fs_id,
+ mountpoint,
+ tls_port,
+ tunnel_proc.pid,
+ tunnel_args,
+ [stunnel_config_file],
+ state_file_dir
+ )
try:
yield tunnel_proc
@@ -677,6 +713,26 @@ def check_unsupported_options(options):
del options[unsupported_option]
+def find_existing_mount_using_tls_port(state_file_dir, tls_port):
+ if not os.path.exists(state_file_dir):
+ logging.debug(
+ "State file dir %s does not exist, assuming no existing mount using tls port %s",
+ state_file_dir,
+ tls_port,
+ )
+ return None
+
+ for fname in os.listdir(state_file_dir):
+ if fname.endswith(".%s" % tls_port):
+ return fname
+
+ return None
+
+
+def get_tls_port_from_sock(tls_port_sock):
+ return tls_port_sock.getsockname()[1]
+
+
def main():
parse_arguments_early_exit()
Index: efs-utils-1.7/test/mount_efs_test/test_bootstrap_tls.py
===================================================================
--- efs-utils-1.7.orig/test/mount_efs_test/test_bootstrap_tls.py
+++ efs-utils-1.7/test/mount_efs_test/test_bootstrap_tls.py
@@ -30,9 +30,11 @@ MOCK_CONFIG = MagicMock()
@pytest.fixture(autouse=True)
def setup(mocker):
+ sock = MagicMock()
+ sock.getsocketname.return_value = ['', DEFAULT_TLS_PORT]
mocker.patch('mount_efs.start_watchdog')
mocker.patch('mount_efs.get_tls_port_range', return_value=(DEFAULT_TLS_PORT, DEFAULT_TLS_PORT + 10))
- mocker.patch('mount_efs.choose_tls_port', return_value=DEFAULT_TLS_PORT)
+ mocker.patch('mount_efs.choose_tls_port', return_value=sock)
mocker.patch('mount_efs.write_tls_tunnel_state_file')
mocker.patch('mount_efs.write_stunnel_config_file', return_value=EXPECTED_STUNNEL_CONFIG_FILE)
mocker.patch('os.rename')
Index: efs-utils-1.7/test/mount_efs_test/test_choose_tls_port.py
===================================================================
--- efs-utils-1.7.orig/test/mount_efs_test/test_choose_tls_port.py
+++ efs-utils-1.7/test/mount_efs_test/test_choose_tls_port.py
@@ -26,34 +26,40 @@ def _get_config():
return config
-def test_choose_tls_port_first_try(mocker):
- mocker.patch('socket.socket', return_value=MagicMock())
+def test_choose_tls_port_first_try(mocker, tmpdir):
+ sock_mock = MagicMock()
+ sock_mock.getsocketname.return_value = ['', 20049]
+ mocker.patch('socket.socket', return_value=sock_mock)
- tls_port = mount_efs.choose_tls_port(_get_config())
+ state_file_dir = str(tmpdir)
+ sock = mount_efs.choose_tls_port(_get_config(), state_file_dir)
- assert DEFAULT_TLS_PORT_RANGE_LOW <= tls_port <= DEFAULT_TLS_PORT_RANGE_HIGH
+ assert DEFAULT_TLS_PORT_RANGE_LOW <= sock.getsocketname()[1] <= DEFAULT_TLS_PORT_RANGE_HIGH
-def test_choose_tls_port_second_try(mocker):
+def test_choose_tls_port_second_try(mocker, tmpdir):
bad_sock = MagicMock()
bad_sock.bind.side_effect = [socket.error, None]
+ bad_sock.getsocketname.return_value = ['', 20050]
mocker.patch('socket.socket', return_value=bad_sock)
+ state_file_dir = str(tmpdir)
- tls_port = mount_efs.choose_tls_port(_get_config())
+ sock = mount_efs.choose_tls_port(_get_config(), state_file_dir)
- assert DEFAULT_TLS_PORT_RANGE_LOW <= tls_port <= DEFAULT_TLS_PORT_RANGE_HIGH
+ assert DEFAULT_TLS_PORT_RANGE_LOW <= sock.getsocketname()[1] <= DEFAULT_TLS_PORT_RANGE_HIGH
assert 2 == bad_sock.bind.call_count
-def test_choose_tls_port_never_succeeds(mocker, capsys):
+def test_choose_tls_port_never_succeeds(mocker, capsys, tmpdir):
bad_sock = MagicMock()
bad_sock.bind.side_effect = socket.error()
mocker.patch('socket.socket', return_value=bad_sock)
+ state_file_dir = str(tmpdir)
with pytest.raises(SystemExit) as ex:
- mount_efs.choose_tls_port(_get_config())
+ mount_efs.choose_tls_port(_get_config(), state_file_dir)
assert 0 != ex.value.code
@@ -61,3 +67,4 @@ def test_choose_tls_port_never_succeeds(
assert 'Failed to locate an available port' in err
assert DEFAULT_TLS_PORT_RANGE_HIGH - DEFAULT_TLS_PORT_RANGE_LOW == bad_sock.bind.call_count
+