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
+
openSUSE Build Service is sponsored by