CVE-2022-46174 (Python, published 2022-12-28T07:15Z)
Description: value: efs-utils is a set of Utilities for Amazon Elastic File System (EFS). A potential race condition issue exists within the Amazon EFS mount helper in efs-utils versions v1.34.3 and below. When using TLS to mount file systems, the mount helper allocates a local port for stunnel to receive NFS connections prior to applying the TLS tunnel. In affected versions, concurrent mount operations can allocate the same local port, leading to either failed mount operations or an inappropriate mapping from an EFS customer’s local mount points to that customer’s EFS file systems. This issue is patched in version v1.34.4. There is no recommended work around. We recommend affected users update the installed version of efs-utils to v1.34.4 or later.
Severity: nan (CVSS v3 base score: 4.2)
Weakness: CWE-362 \u2014 Concurrent Execution using Shared Resource with Improper Synchronization ('Race Condition')
The product contains a code sequence that can run concurrently with other code, and the code sequence requires temporary, exclusive access to a shared resource, but a timing window exists in which the shared resource can be modified by another code sequence that is operating concurrently.
Repository: https://github.com/aws/efs-utils
Files changed:
amazon-efs-utils.spec
build-deb.sh
config.ini
dist/amazon-efs-utils.control
src/mount_efs/__init__.py
src/watchdog/__init__.py
test/mount_efs_test/test_bootstrap_tls.py
test/mount_efs_test/test_choose_tls_port.py
Commit message: Fix potential tlsport selection collision by using state file as
tlsport lock file

This removes all potential concurrency issues during tls port selection
and ensures that concurrent mounts will never select the same port.


Vulnerable code:
Version   : 1.34.3
VERSION=1.34.3
version=1.34.3
Version: 1.34.3
VERSION = "1.34.3"
def choose_tls_port_and_get_bind_sock(config, options):
        tls_port_sock = find_tls_port_in_range_and_get_bind_sock(ports_to_try)
            tls_port_sock = find_tls_port_in_range_and_get_bind_sock(ports_to_try)
def find_tls_port_in_range_and_get_bind_sock(ports_to_try):
            logging.info(e)
    tls_port_sock = choose_tls_port_and_get_bind_sock(config, options)
    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,
        cert_details=cert_details,
VERSION = "1.34.3"

def test_choose_tls_port_first_try(mocker):
    tls_port_sock = mount_efs.choose_tls_port_and_get_bind_sock(_get_config(), options)
def test_choose_tls_port_second_try(mocker):
    tls_port_sock = mount_efs.choose_tls_port_and_get_bind_sock(_get_config(), options)
def test_choose_tls_port_never_succeeds(mocker, capsys):
        mount_efs.choose_tls_port_and_get_bind_sock(_get_config(), options)
def test_choose_tls_port_option_specified(mocker):
    tls_port_sock = mount_efs.choose_tls_port_and_get_bind_sock(_get_config(), options)
def test_choose_tls_port_option_specified_unavailable(mocker, capsys):
        mount_efs.choose_tls_port_and_get_bind_sock(_get_config(), options)
def test_choose_tls_port_under_netns(mocker, capsys):
    mount_efs.choose_tls_port_and_get_bind_sock(_get_config(), options)

Fixed code:
Version   : 1.34.4
* Tue Dec 13 2022 Ryan Stankiewicz <rjstank@amazon.com> - 1.34.4
- Fix potential tlsport selection collision by using state file as tlsport lock file.

VERSION=1.34.4
version=1.34.4
Version: 1.34.4
VERSION = "1.34.4"
def choose_tls_port_and_get_bind_sock(config, options, state_file_dir):
        tls_port_sock = find_tls_port_in_range_and_get_bind_sock(
            ports_to_try, state_file_dir
        )
            tls_port_sock = find_tls_port_in_range_and_get_bind_sock(
                ports_to_try, state_file_dir
            )
def find_tls_port_in_range_and_get_bind_sock(ports_to_try, state_file_dir):
        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
            logging.warning(e)
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 rewrite_tls_tunnel_state_file(state, state_file_dir, state_file):
    with open(os.path.join(state_file_dir, state_file), "w") as f:
        json.dump(state, f)
    return state_file


def update_tls_tunnel_temp_state_file_with_tunnel_pid(
    temp_tls_state_file, state_file_dir, stunnel_pid
):
    with open(os.path.join(state_file_dir, temp_tls_state_file), "r") as f:
        state = json.load(f)
    state["pid"] = stunnel_pid
    temp_tls_state_file = rewrite_tls_tunnel_state_file(
        state, state_file_dir, temp_tls_state_file
    )
    return temp_tls_state_file


    tls_port_sock = choose_tls_port_and_get_bind_sock(config, options, state_file_dir)

        # This temp state file is acting like a tlsport lock file, which is why pid =- 1
        temp_tls_state_file = write_tls_tunnel_state_file(
            fs_id,
            mountpoint,
            tls_port,
            -1,
            tunnel_args,
            [stunnel_config_file],
            state_file_dir,
            cert_details=cert_details,
        )
    update_tls_tunnel_temp_state_file_with_tunnel_pid(
        temp_tls_state_file, state_file_dir, tunnel_proc.pid
VERSION = "1.34.4"
    mocker.patch(
        "mount_efs.update_tls_tunnel_temp_state_file_with_tunnel_pid",
        return_value="~mocktempfile",
    )
    mocker.patch(
        "mount_efs.update_tls_tunnel_temp_state_file_with_tunnel_pid",
        return_value="~mocktempfile",
    )
    mocker.patch("mount_efs.find_existing_mount_using_tls_port", return_value=None)
import logging
import sys
import tempfile
import unittest
def test_choose_tls_port_first_try(mocker, tmpdir):
    tls_port_sock = mount_efs.choose_tls_port_and_get_bind_sock(
        _get_config(), options, str(tmpdir)
    )
def test_choose_tls_port_second_try(mocker, tmpdir):
    tls_port_sock = mount_efs.choose_tls_port_and_get_bind_sock(
        _get_config(), options, str(tmpdir)
    )
@unittest.skipIf(sys.version_info < (3, 6), reason="requires python3.6")
def test_choose_tls_port_collision(mocker, tmpdir, caplog):
    """Ensure we don't choose a port that is pending mount"""
    sock = MagicMock()
    mocker.patch("socket.socket", return_value=sock)
    mocker.patch(
        "random.shuffle",
        return_value=range(DEFAULT_TLS_PORT_RANGE_LOW, DEFAULT_TLS_PORT_RANGE_HIGH),
    )

    port_suffix = ".%s" % str(DEFAULT_TLS_PORT_RANGE_LOW)
    temp_state_file = tempfile.NamedTemporaryFile(
        suffix=port_suffix, prefix="~", dir=tmpdir
    )

    options = {}
    with caplog.at_level(logging.DEBUG):
        mount_efs.choose_tls_port_and_get_bind_sock(_get_config(), options, tmpdir)

    temp_state_file.close()
    sock.bind.assert_called_once_with(("localhost", DEFAULT_TLS_PORT_RANGE_LOW + 1))
    assert "Skip binding TLS port" in caplog.text


def test_choose_tls_port_never_succeeds(mocker, tmpdir, capsys):
        mount_efs.choose_tls_port_and_get_bind_sock(_get_config(), options, str(tmpdir))
def test_choose_tls_port_option_specified(mocker, tmpdir):
    tls_port_sock = mount_efs.choose_tls_port_and_get_bind_sock(
        _get_config(), options, str(tmpdir)
    )
def test_choose_tls_port_option_specified_unavailable(mocker, tmpdir, capsys):
        mount_efs.choose_tls_port_and_get_bind_sock(_get_config(), options, str(tmpdir))
def test_choose_tls_port_under_netns(mocker, tmpdir):
    mount_efs.choose_tls_port_and_get_bind_sock(_get_config(), options, str(tmpdir))