Bug in BOOTP servers? (ISC Dhcp and Bootp-dd2)

About two weeks ago I posted an introduction on the BOOTP protocol, today I’ll talk about a possible bug I found in current implementations. I recommend those not familiar with this protocol read my previous post here before continuing.

Abstract

My current project at the Brazilian IBM LTC (Linux Technology Center) lab is to develop the IBM Installation Toolkit for Linux on Power, a LiveDVD environment and Linux installer — more on this in a yet to come post or at the official site. This product is also bootable through the network and although this usually works flawlessly, a couple of months ago I was identified a show-stopper issue that IMHO is a bad behavior in the current BOOTP server implementations. We’re able to make things work changing our side but anyway I’ll talk about this issue, caused by the way current (BOOTP) servers fill-in the “filename” field in BOOTP reply packages.

What does the RFC say?

The full BOOTP protocol is specified in the RFC 951 — have you seen the previous post? 🙂 — and the directions regarding the “filename” field are no exception as you can seen in the extract below:

We now check the boot file name field (file). The field will be null if the client is not interested in filenames, or wants the default bootfile. If the field is non-null, it is used as a lookup key in a database, along with the client’s IP address. If there is a default file or generic file possibly indexed by the client address) or a fully-specified path name that matches, then replace the ‘file’ field with the fully-specified path name of the selected boot file. If the field is non-null and no match was found, then the client is asking for a file we dont have; discard the packet, perhaps some other BOOTP server will have it.

So, in other words, the RFC defines that the server must have a database with legal filenames. When a request arrives the server must check whether the client know what it wants, if it doesn’t then it must reply with a default filename. So far, so good, however, if the client does know the file it’s looking for, the server should check this filename against its database, possibly use it as a lookup key and, if it has this file, craft an reply packet with the filename the client was asking for, possibly the full path for this file if this information was provided in its filename database. Finally, if the client knows the file it’s looking for and the server is unable to provide it, it should discard the packet.

How do current implementations behave?

Short note: when I say current implementations please read “ISC DHCP server v. 3.1.0 and 4.0.0.x as well as Bootp-dd2 4.3.x”. I’m using these servers because they are, by far, the most used BOOTP servers in the Linux community.

Current implementations follows the logic below:

  1. Before starting the server I can provide one filename for each client this server will listen to.
  2. Receive a BOOTP-request packet from one of my authorized clients.
  3. Look at our client database (use the MAC address or IP as lookup key). Does our configuration file provide a filename for this specific client?
  4. If we have a filename, use it, period
  5. If we don’t have a filename, copy the “filename” field provided by the client in the request packet to the reply packet.

Who is affected? What are the consequences?

Most network boot scenarios are composed of (1) a server that has a self-contained boot image for each client and (2) clients that don’t know anything but the fact they want to boot. In these situations what happens is that the clients send BOOTP requests with no filename provided, the server then replies with the only filename it knows of, the clients download this image and boot successfully.

Not everyone is lucky enough to have this simple case though. In our case due to some non-technical constraints we cannot use self-contained images, these images, aka El Torito images, provide everything a system needs to boot in a single file thus requiring only a single BOOTP negotiation in order to boot the system.

All those who use Yaboot (a boot loader similar to Lilo but designed for PowerPC boxes) in network boot environments are also susceptible to failures caused by this server “misbehavior”.

I’ll use Yaboot as an example of how Bad Things can happen. When loaded in a BOOTP environment, the Yaboot binary is the first file provided by the server. But, rather than being self-contained as the El Torito / zImage files, Yaboot requires several other files to continue the boot process, these files include a configuration file, the kernel image, and other important stuff. The problem is that some clients, in order to get all these files, issue several BOOTP requests, one after the other, for each file needed, and these multiple BOOTP requests can trigger the deffect.

What happens in these situations is:

  1. Client (C) sends request to server (S) without a filename.
  2. S sends reply to C with Yaboot filename (be that “yaboot”)
  3. C downloads and executes Yaboot that then requires a new file, say, the kernel (be that “linuxrc”)
  4. C sends request to S with filename = “linuxrc”
  5. S overrides the filename field and sends reply to C with filename = “yaboot”
  6. C downloads yaboot again instead of the kernel. Very Bad Things happen 😛

This happens for two reasons, the first and most obvious is that the server sends a wrong answer, but there is another one, not less important, that is the trust relationship the client has with its server. Note that the client knew in the 4th step it wanted “linuxrc” but in the 6th step it takes the server word and downloads “yaboot”. Some client implementations that are on the streets don’t believe that hard on the server and try to download “linuxrc” no matter the filename sent by the server; luckily this is what prevents these clients from facing this issue.

Possible solutions.

The first and most correct solution would be to implement what the RFC defines, to accomplish that the servers would require changes in a handful of important components like the configuration files parser, internal hash tables, etc. Is this worth it? Probably not.

Could we fix it in a less invasive way? Absolutely.

Here I propose a simple change that would require a single line patch that could be applied against any of the current servers and, IMHO would bring their behavior closer to the original specification.

The logic I would use is (parts in italics are my changes against current behavior seen above):

  1. Before starting the server I can provide one filename for each client this server will listen to.
  2. Receive a BOOTP-request packet from one of my authorized clients.
  3. Look at our client database (use the MAC address or IP as lookup key). Does our configuration file provide a filename for this specific client?
  4. Has the client provided a filename?
  5. If we have a filename and the client does NOT have a filename, use it.
  6. If we don’t have a filename or the client has provided its own filename, copy the “filename” field provided by the client in the request packet to the reply packet.

This has been tested to work in our previously failing scenario. I’ve sent a few emails to the ISC dhcp-hackers but haven’t got any satisfactory answer yet.

Security Concerns

Talking to the guys who work with me I was asked what would happen if a malicious client sent a BOOTP request with filename being “/etc/passwd” or any other import file. Wouldn’t that be a security concern?

My answer, no, it wouldn’t.

In this eventual case the server would send a reply with filename equals to “/etc/passwd”. The client could then try to download this file issuing a TFTP request for “/etc/passwd”, the TFTP server would then deny this request as (it’s expected) this file is not in the path exported by the TFTP server.

Note that the BOOTP server is not responsible for allowing or denying file transfers, it just knows about file names, not its contents.

Actual file transfers are held by the TFTP server. In current implementations nothing prevents a malicious client for issuing TFTP requests to get “/etc/passwd” or any other critical file, with my patch this would remain exactly the way it is, I mean, the TFTP server is and would still be the one responsible for granting or denying file access.

The patch

To implement this change in ISC DHCP 3.1.0 server a simple patch would need to be applied to the “dhcp-3.1.0/server/bootp.c” file as you can see below.

Version 4 of ISC DHCP requires exactly the same change and the latest version of BOOTP-DD2 a very similar patch.

Current implementation (with additional comments by me):

/* Figure out the filename. */
oc = lookup_option (&server_universe, options, SV_FILENAME); // Do we have a filename?
if (oc && // We do
evaluate_option_cache (&d1, packet, lease,
(struct client_state *)0,
packet -> options, options,
&lease -> scope, oc, MDL)) {
memcpy (raw.file, d1.data, // Use our filename, ie. d1.data
d1.len > sizeof raw.file ? sizeof raw.file : d1.len);
if (sizeof raw.file > d1.len)
memset (&raw.file [d1.len],
0, (sizeof raw.file) - d1.len);
data_string_forget (&d1, MDL);
} else // Ok, we don't, so use whatever was provided
memcpy (raw.file, packet -> raw -> file, sizeof raw.file);

My change would be simple, just add another condition to the main IF as this patch proposes:


diff -ru dhcp-3.1.0/server/bootp.c dhcp-3.1.0-patched/server/bootp.c
--- dhcp-3.1.0/server/bootp.c 2006-08-09 09:57:48.000000000 -0500
+++ dhcp-3.1.0-patched/server/bootp.c 2007-07-30 11:11:19.000000000 -0500
@@ -283,7 +283,7 @@

/* Figure out the filename. */
oc = lookup_option (&server_universe, options, SV_FILENAME);
- if (oc &&
+ if (!(packet -> raw -> file[0]) && oc &&
evaluate_option_cache (&d1, packet, lease,
(struct client_state *)0,
packet -> options, options,

So, are you still reading? Thanks a lot, please feel free to leave your opinion.

Cheers!

Download the patch here –> ISC DHCP bootp filename patch

One thought on “Bug in BOOTP servers? (ISC Dhcp and Bootp-dd2)

  1. Pingback: mdl file

Leave a Reply