Bug 219792 - cifs: Net_ns leakage occurs after the mounting fails
Summary: cifs: Net_ns leakage occurs after the mounting fails
Status: NEW
Alias: None
Product: File System
Classification: Unclassified
Component: CIFS (show other bugs)
Hardware: All Linux
: P3 normal
Assignee: fs_cifs
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2025-02-17 12:25 UTC by wangzhaolong1
Modified: 2025-02-24 06:32 UTC (History)
0 users

See Also:
Kernel Version:
Subsystem:
Regression: No
Bisected commit-id:


Attachments

Description wangzhaolong1 2025-02-17 12:25:52 UTC
When an exception occurs on the server's port 445, mounting the CIFS file system in the network namespace fails, followed by a network namespace reference count leak issue.

I've integrated the reproduction process into the test cases, and the specific scripts are as follows:

```bash

#!/bin/bash
#
# This is a test library for shell.
#

[ -n "$TST_LIB_LOADED" ] && return 0

export TST_PASS=0
export TST_FAIL=0
export TST_BROK=0
export TST_WARN=0
export TST_CONF=0
export TST_COUNT=0
export TST_COLOR_ENABLED=1
export TST_LIB_LOADED=1

trap "tst_brk TBROK 'test interrupted'" INT

tst_flag2color()
{
	local ansi_color_blue='\033[1;34m'
	local ansi_color_green='\033[1;32m'
	local ansi_color_magenta='\033[1;35m'
	local ansi_color_red='\033[1;31m'
	local ansi_color_yellow='\033[1;33m'

	case "$1" in
	TPASS) printf $ansi_color_green;;
	TFAIL) printf $ansi_color_red;;
	TBROK) printf $ansi_color_red;;
	TWARN) printf $ansi_color_magenta;;
	TINFO) printf $ansi_color_blue;;
	TCONF) printf $ansi_color_yellow;;
	esac
}

tst_color_enabled()
{
	[ $TST_COLOR_ENABLED -eq 1 ]  && return 1 || return 0
}

tst_print_colored()
{
	tst_color_enabled
	local color=$?

	[ "$color" = "1" ] && tst_flag2color "$1"
	printf "$2"
	[ "$color" = "1" ] && printf '\033[0m'
}

tst_exit()
{
	local ret=0

	if [ $TST_FAIL -gt 0 ]; then
        	ret=$((ret|1))
	fi                          
                            
	if [ $TST_BROK -gt 0 ]; then
        	ret=$((ret|2))      
	fi                          
                            
	if [ $TST_WARN -gt 0 ]; then
        	ret=$((ret|4))      
	fi                          
                            
	if [ $TST_CONF -gt 0 ]; then
        	ret=$((ret|32))     
	fi

    cat >&2 << EOF

Summary:
passed   $TST_PASS
failed   $TST_FAIL
broken   $TST_BROK
skipped  $TST_CONF
warnings $TST_WARN
EOF
	
	exit $ret                       
}

_tst_inc_res()
{
	case "$1" in
	TPASS) TST_PASS=$((TST_PASS+1));;
	TFAIL) TST_FAIL=$((TST_FAIL+1));;
	TBROK) TST_BROK=$((TST_BROK+1));;
	TWARN) TST_WARN=$((TST_WARN+1));;
	TCONF) TST_CONF=$((TST_CONF+1));;
	TINFO) ;;
	*) tst_brk TBROK "Invalid res type '$1'";;
	esac
}

tst_res()
{
	local res=$1
        if type caller >/dev/null 2>&1;then
		caller_info=$(caller 2) ||
			caller_info=$(caller 1) ||
			caller_info=$(caller 0)

		caller_info=$(echo "${caller_info}" | awk 'info=$NF":"$1 {print info}')
        fi

	shift

	tst_color_enabled
	local color=$?
	
	TST_COUNT=$(($TST_COUNT+1))	

	_tst_inc_res "$res"
	printf "$(date) $TST_ID $TST_COUNT ${caller_info} "
	tst_print_colored $res "$res: "
	echo -e "$@"
}

tst_brk()
{
	local res=$1
	shift

	tst_res "$res" "$@"
	tst_exit
}

tst_begin_test()
{
	if [ -w /dev/kmsg ]; then
		export tst_date_time=`date +"%F %T%N"`
		echo "run test at $tst_date_time" > /dev/kmsg
	fi
}

_dmesg_since_test_start()
{
	if which tac > /dev/null 2>&1; then
		dmesg -T | tac | sed -ne "0,\#run test at $tst_date_time#p" | tac
	else
		dmesg -T >/dev/null 2>&1 && dmesg_cmd="dmesg -T" || dmesg_cmd="dmesg"
		tac_cmd="awk '{ lines[NR] = \$0 } END { for (i = NR; i > 0; i--) print lines[i] }'"
		eval "${dmesg_cmd} | ${tac_cmd} | sed -ne \"1,\#run test at ${tst_date_time}#p\" | ${tac_cmd}"
	fi
}

tst_check_dmesg_for()
{
	keywords=$@
	if [ -z "$keywords" ];then
		tst_res TWARN "tst_check_dmesg_for parameter is null"
	else
		_dmesg_since_test_start | grep -E -q "$keywords"
	fi
}

_tst_expect_pass()
{
	local fnc="$1"
	shift

	eval $@
	if [ $? -eq 0 ]; then
		tst_res TPASS "$@ passed as expected"
		return 0
	else
		$fnc TFAIL "$@ failed unexpectedly"
		return 1
	fi
}

_tst_expect_fail()
{
    local fnc="$1"
    shift

    eval $@
    if [ $? -ne 0 ]; then
        tst_res TPASS "$@ failed as expected"
        return 0
    else
        $fnc TFAIL "$@ pass unexpectedly"
        return 1
    fi
}

tst_expect_pass()
{
	_tst_expect_pass tst_res "$@"
}

tst_expect_fail()
{
    _tst_expect_fail tst_res "$@"
}

# check dmesg log for WARNING/Oops/etc.
tst_check_dmesg()
{
	ret=$(_dmesg_since_test_start | grep -E -q \
		-e "kernel BUG at" \
		-e "WARNING:" \
		-e "BUG:" \
		-e "WARNING:" \
		-e "Oops:" \
		-e "possible recursive locking detected" \
		-e "Internal error" \
		-e "suspicious RCU usage" \
		-e "possible circular locking dependency detected" \
		-e "general protection fault" \
		-e "BUG.*remaining" \
		-e "UBSAN" \
		-e "KASAN" \
		-e "Call trace")
	if [ $? -eq 0 ]; then
		tst_res TWARN "_check_dmesg: something found in dmesg\n$(_dmesg_since_test_start)"
	fi
}

# generate random number
#
# tst_rand          random between [0, 32767]
# tst_rand $1 $2    random between [$1, $2]  ($2-$1 should less than 32767)
tst_rand_int()
{
	if [ $# -eq 0 ]; then
		echo $RANDOM
	elif [ $# -eq 2 ]; then
		local min=$1
		local max=$2
		local offset=$(($max-$min+1))
		echo $(($RANDOM%$offset+$min))
	else
		tst_res TWARN "tst_rand only accepts 0 or 2 parameters"
	fi
}

if [ -z "$TST_ID" ]; then
	_tst_filename=$(basename $0) || \
		tst_brk TCONF "Failed to set TST_ID from \$0 ('$0')"
	TST_ID=${_tst_filename%%.*}
fi
export TST_ID="$TST_ID"

# ------------------------------------------------------------------------

workdir=$(pwd)
tmp_dir="${workdir}/tmp"

kernel_version=$(uname -r)
smb_conf=${tmp_dir}/smb.conf
test_dir=${tmp_dir}/share
mount_dir=${tmp_dir}/mp

trap "do_post_2" exit

stop_smbd() {
    pidof smbd >/dev/null 2>&1 && killall smbd
    count=1
    while true; do
        pidof smbd >/dev/null 2>&1 || return 0
        sleep 2
        count=$((count + 1))
        if [ "${count}" -ge 10 ]; then
            echo "smbd pid:$(pidof smbd)"
            return 1
        fi
    done
}

prepare_cifs() {
    local ret=0

    if ! type smbd >/dev/null 2>&1; then
        echo "smbd is not found"
        return 1
    fi

    mkdir -p "${test_dir}"
    cat >>"${smb_conf}" <<EOF
[global]
    workgroup = SAMBA
    security = user

    map to guest = Bad User

[share]
    path = ${test_dir}
    browseable = yes
    read only = no
    guest ok = yes
    force user = root
    read only = no
EOF
    stop_smbd || return 1
    # 启动smbd
    smbd -s "${smb_conf}" -D

    # 添加防火墙规则,阻止访问445端口
    iptables -A INPUT -p tcp --dport 445 -j REJECT
}

do_pre() {
    tst_res TINFO "[start]do pre"
    tst_expect_pass prepare_cifs
    tst_res TINFO "[end]do_pre"
}

do_test() {
    tst_res TINFO "[start]do test"
    local net_ns=cifs_ns
    local test_veth=veth1
    local test_veth_peer=veth2
    local server_ip=192.168.11.2

    mkdir -p "${mount_dir}"
    ip netns add ${net_ns}
    ip link add ${test_veth} type veth peer name ${test_veth_peer}
    ip link set ${test_veth} up
    ip addr add ${server_ip}/24 dev ${test_veth}

    ip link set ${test_veth_peer} netns ${net_ns}
    nsenter --net=/run/netns/${net_ns} bash -c "
    ip link set lo up
    ip link set ${test_veth_peer} up
    ip addr add 192.168.11.3/24 dev ${test_veth_peer}
    # 挂载CIFS
    mount -t cifs //${server_ip}/share ${mount_dir} -o username=nobody,guest
"
    tst_expect_fail "grep ${mount_dir} /proc/mounts"

    sleep 5

    tst_expect_pass "ip netns del ${net_ns}"
    tst_expect_fail "ip a | grep veth1"
    tst_res TINFO "[end]do_test"
}

do_post_1() {
    tst_res TINFO "[start]do post"
    stop_smbd
    type smbd >/dev/null 2>&1 && smbd -D
    [ -d "${tmp_dir}" ] && tst_expect_pass rm -rf "${tmp_dir}"
    
    # 删除防火墙规则
    iptables -D INPUT -p tcp --dport 445 -j REJECT
    
    tst_res TINFO "[end]do_post"
}

do_post_2() {
    do_post_1
    tst_check_dmesg
    tst_exit
}

run_testcase() {
    tst_begin_test
    do_pre
    [ "${TST_FAIL}" -ne 0 ] && exit 1
    do_test
}

run_testcase

```

When the test case fails, you can use the following command to check for leaked network namespaces:

ip a | grep -A 5 veth1

```
# ip a | grep -A 5 veth1
11: veth1@if10: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 92:ad:c9:2b:dd:ad brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 192.168.11.2/24 scope global veth1
       valid_lft forever preferred_lft forever
    inet6 fe80::90ad:c9ff:fe2b:ddad/64 scope link proto kernel_ll 
       valid_lft forever preferred_lft forever
```
Comment 1 wangzhaolong1 2025-02-18 07:53:19 UTC
The problem process is as follows:

```
mount.cifs                        cifsd

cifs_do_mount
  cifs_mount
    cifs_mount_get_session
      cifs_get_tcp_session
        get_net()  /* First get net */
        ip_connect
          /* Try port 445 */
          generic_ip_connect
            get_net()
            ->connect() /* failed */
            put_net()   /* This call is balanced */
          /* Try port 139 */
          generic_ip_connect
            get_net() /* Missing matching put_net() for this get_net() */
      cifs_get_smb_ses
        cifs_negotiate_protocol
          smb2_negotiate
            SMB2_negotiate
              cifs_send_recv
                wait_for_response
                                 cifs_demultiplex_thread
                                   cifs_read_from_socket
                                     cifs_readv_from_socket
                                       cifs_reconnect
                                         cifs_abort_connection
                                           sock_release();
                                           server->ssocket = NULL;
                                           /* Missing put_net() here. */
                                           generic_ip_connect
                                             get_net()
                                             ->connect() /* Failed */
                                             put_net()
                                             sock_release();
                                             server->ssocket = NULL;
          free_rsp_buf
    ...
                                   clean_demultiplex_info
                                     /* It's only called once here. */
                                     put_net()
```
Comment 2 wangzhaolong1 2025-02-24 06:32:54 UTC
For my test case, the following kernel config is necessary:

CONFIG_CIFS
CONFIG_VETH
CONFIG_NFT_COUNTER
CONFIG_NF_REJECT_IPV4
CONFIG_NFT_COMPAT
CONFIG_NF_TABLES
CONFIG_NETFILTER_NETLINK

Note You need to log in before you can comment on or make changes to this bug.