Class 28 CS 439 25 April 2013 On the board ------------ 1. Last time: networking 2. Finish up networking 3. Stack smashing --------------------------------------------------------------------------- 1. Last time 2. Finish up networking I. Transport layer Motivation: failure, demultiplexing, flow control, etc. DRAW PICTURE: layer role TCP UDP ICMP("ping") {flow control, port space} IP {forwarding} Ethernet {framing} radio copper_wires fiber {signal propagation} Several types of error can affect packet delivery --Bit errors (e.g., electrical interference, cosmic rays) --Packet loss (packets dropped when queues fill on overload) --Link and node failure In addition, properly delivered frames can be delayed, reordered, even duplicated How much should OS (or the networking modules) expose to application? --Some failures cannot be masked (e.g., server dead) --Others can be (e.g., retransmit lost packet) --But masking errors may be wrong for some applications (e.g., old audio packet no longer interesting if too late to play) UDP and TCP most popular protocols on IP --Both use 16-bit _port_ number as well as 32-bit IP address --Applications _bind_ to a port and receive traffic to that port (discuss later what the interface is) UDP: User Datagram Protocol --Exposes packet-switched nature of Internet --Sent packets may be dropped, reordered, even duplicated (but generally not corrupted). Application's problem to deal with these errors TCP: Transmission Control Protocol --Provides illusion of a reliable "pipe" between two processes on two different machines --Masks lost and reordered packets so apps don't have to worry --Handles congestion and flow control Uses of TCP --Most applications use TCP --Easier interface to program to (reliability) --Automatically avoids congestion (don't need to worry about taking down network) Many issues involved in implementing TCP: --Wants multiple packets outstanding --But want to react to congestion in the network (want to save network from congestion collapse) --TCP has to "learn" parameters per-connection --Connection set-up and tear-down is complicated --sender never knows if it's last packet was lost --so has to keep state around after connection close --Tons of hacks for good performance Issues directly for OS too: --Have to track unacknowledged data --Keep a copy around until recipient acknowledges it --Keep timer around to retransmit if no ack --Receiver must keep out of order segments and reassemble --When to wake process receiving data? --E.g., sender calls write (fd, message, 8000); --First TCP segment arrives, but is only 512 bytes --Could wake recipient, but useless w/o full message --TCP sets PUSH bit at end of 8000 bytes, to force write data --When to send short segment, vs. wait for more data --Usually send only one unacked short segment --But bad for some apps, so provide NODELAY option --Must ack received segments very quickly --Otherwise, effectively increases RTT, increasing bandwidth-delay product but without increase in bandwidth --> useful throughput declines Servers typically listen on well-known ports SSH: 22 Email: 25 Finger: 79 Web / HTTP: 80 --Example: Interacting with www.cs.utexas.edu --Browser resolves IP address of www.cs.utexas.edu --Browser connects to TCP port 80 on that IP address --Over TCP connection, browser requests and gets home page J. [last time] Application layer K. What is the interface to the networking stack? --Application programmer classically sees *sockets*. Inspired by pipes int pipe(int fds[2]) --Allow Inter-process communication on one machine --Writes to fds[1] will be read on fds[0] --Can give each file descriptor to a different process (with fork) The idea is: let's do the same thing across machines: **SOCKETS** Write data on one machine, read it on another *sockets* can represent many different network protocols, but: --classically an interface to TCP/IP and UDP --sometimes an interface to IP or Ethernet (raw sockets) --sockets API /* senders and receivers */ int sockfd = socket(AF_INET, SOCK_STREAM|SOCK_DGRAM|, 0); [note: with AF_INET in the first position, the setting of SOCK_STREAM vs SOCK_DGRAM controls whether the app's data is going to go over TCP or UDP]. [with UDP sockets, send atomic messages that may be reordered or lost] [with TCP sockets, bytes written on one end are read on the other, provided no failures. but no guarantees that reads will return the full amount requested ... or that the data will be packetized according to the number of times the sender called send(). With TCP, you *must* sit there in a loop and keep reading. You know you're done because either (a) the application-level protocol is expected to understand where message boundaries begin and end or (b) the first machine closed its connection to the server] int rc = close(); select(); struct sockaddr_in { short sin_family; short sin_port; uint32_t sin_addr; char sin_zero[8]; }; /* senders */ int rc = connect(sockfd, &addr, addrlen); int rc = send(sockfd, buf, len, 0); int rc = sendto(sockf, buf, len, 0, &addr, addrlen, 0); /* receivers */ int rc = bind(sockfd, &addr, addrlen); int rc = listen(sockfd, backlog_len); int rc = accept(sockfd, &addr, &adddrlen); int rc = recv(sockfd, buf, len, 0); int rc = recvfrom(sockfd, buf, len, 0, &addr, &addrlen); NOTES: * connections are named by 5 components: protocol (TCP), local IP address, local port, remote IP address, remote port * UDP does not require connected sockets * OS tracks all of this state in a PCB (protocol control block). --What does kernel see, and what interfaces does it invoke? TX direction: --usually gets payloads from higher levels and implements TCP/IP, UDP, IP, and part of Ethernet --usually hands most of an Ethernet frame to the network device --but not always: could imagine a Web server implemented entirely in the kernel, or even a Web server implemented on a network card RX direction: --when a packet arrives, use 5-tuple (above) to find PCB and figure out what to do with packet Note that to avoid lots of copies, OS may not actually store packets contiguously. May store linked list of buffers. Each buffer is either a packet header or a payload Network interface cards (NICs) --Used to be dumb --Now sometimes do lots of stuff Kernels also do *routing* --A machine has multiple NICs connected to different networks, kernel gets a packet (either from one of the NICs or from an application), now which NIC does it go out? --kernel generally looks at the destination address of the packet and does a lookup in a table that it maintains: [IP address, prefix-length] --> next-hop next-hop is the physical interface to send the packet out This is the same routing function that Internet routers do there are data structures to make it efficient in time and space (radix trees are a decent first cut) 3. Stack smashing --history --('buffer overflow' is one way to conduct a stack smashing attack.) --note how exploit works --primitive form of linking, at exploit time! --relies on fork/exec separation --demo [NOTE: fork/exec separation is what allows us to write tcpserve: after the fork() but before exec() of buggy-server, child rearranges its file descriptors to be the socket itself. Also, this sample code gives you a chance to see sockets in action.] --UTCS host runs server. as Parth. --my laptop runs honest client --my laptop runs dishonest client --note: if this server had been running as root, we'd have been able to get a root shell --and if the user/syscall interface doesn't check its arguments properly, can buffer overflow that interface --in practice, once you have a user account on a machine, it's often possible to get root access (why? because the syscall interface is really hard to secure, as a matter of practice.) --other versions of these attacks --return-to-libc --return-oriented programming [DRAW PICTURE] --overwriting function pointers --smashing the heap --how do people defend against these things? --W ^ X (map the stack pages as non-executable, if the hardware allows it). But there are some issues.... --the original 386 did not allow it with page tables. However, all x86 chips that support extended page tables (which are used to help users get at >4GB of physical memory even if the machine is 32 bits) also support an XD bit in those page tables, which means "don't execute code in this page". We haven't worked with this bit in this class, but the architecture on modern 32-bit x86 supports it. --Even on x86s that don't suport extended page tables, segmentation would help with do-not-execute (since the permissions in the segment descriptor can express this). The disadvantage here is that the compiler needs to lay out the code and stack to match what the segments would require. --The bummer with W ^ X, even when it *is* supported, is this: some languages not only don't need it but also are actively harmed by W ^ X. The core of the issue is that a program written in a safe language (Perl, Python, Java, etc.) does not need W ^ X whereas lots of C programs do. Meanwhile some machines *always* enforce W ^ X, even for programs that do not need it. Such enforcement constrains certain languages, namely those that need to do runtime code generation. --Address space randomization. This provides some help but obviously doesn't help our vulnerable server because our server tells the client where the buffer is. --StackGuard (in gcc). --another defense: don't use C! CPUs are so fast that a language with bounds checking probably isn't going to pay a huge performance penalty relative to one without bounds checks --unfortunately, this is an arms race, and each time a new defense arises, a new attack arises too. here's the most advanced current technique, and it defeats many of the above defenses: --smash the stack with a bunch of return addresses. each return address points to the needed instruction followed by "ret" (requires the attacker to have previously identified these instructions in the code). not too hard in CISC code like on x86, where there are lots of sequences of code embedded in the binary, even sequences that the programmer didn't mean (because instructions are not fixed length). result: the control flow bounces around all of these byte sequences in memory, executing exactly what the attacker wanted, but not executing off of the stack. --this is called "return-oriented programming". defending against it is hard (though if people use only safe languages, that is, languages that do bounds checking and other pointer checks, such attacks will be much, much harder) --Question: can we instead confine processes and users so that when they're broken into, the damage is limited?