333 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			333 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
ndhc + ifchd, Copyright (C) 2004-2011 Nicholas J. Kain.
 | 
						|
See LICENSE for licensing information.  In short: Two-clause / New BSD.
 | 
						|
 | 
						|
Requirements:
 | 
						|
 | 
						|
Linux kernel
 | 
						|
GNU Make (tested: 3.82) or CMake (tested: 2.8)
 | 
						|
libcap (available via ftp.kernel.org)
 | 
						|
 | 
						|
INTRODUCTION
 | 
						|
------------
 | 
						|
 | 
						|
ndhc consists of a set of daemons that cooperate in order to provide
 | 
						|
privilege-seperated dhcp client services.  Each daemon runs with the minimal
 | 
						|
necessary privileges in order to perform its task.  Currently, ndhc consists of
 | 
						|
two daemons: the eponymous ndhc and ifchd.
 | 
						|
 | 
						|
ndhc communicates with dhcp servers and handles the vagaries of the dhcp
 | 
						|
client protocol.  It runs as a non-root user inside a chroot.  ndhc retains
 | 
						|
only the minimum necessary set of privileges required to perform its duties.
 | 
						|
These powers include the ability to bind to a low port, the ability to open a
 | 
						|
raw socket, and the ability to communicate on broadcast channels.  ndhc holds
 | 
						|
no other powers and is restricted to a chroot that contains nothing more than a
 | 
						|
domain socket filesystem object and a urandom device node.
 | 
						|
 | 
						|
ifchd handles interface change requests.  It listens on a UNIX domain socket
 | 
						|
for such requests, and denies any client that does not match an authorized gid,
 | 
						|
uid, or pid.  ifchd runs as a non-root user inside a chroot, and retains only
 | 
						|
the power to configure network interfaces.  ifchd is designed so that it has
 | 
						|
the ability to service multiple client requests simultaneously; a single ifchd
 | 
						|
is sufficient for multiple ndhc clients.  Only exotic setups should require
 | 
						|
this functionality, but it does exist.
 | 
						|
 | 
						|
Note that ndhc does not support the entire DHCP client protocol.  Notably, DHCP
 | 
						|
options concatenation and IPv4 Link Local Addressing as defined in RFC3927 are
 | 
						|
not and will not be supported.
 | 
						|
 | 
						|
On the other hand, ndhc fully implements RFC5227's address conflict detection
 | 
						|
and defense.  Great care is taken to ensure that address conflicts will be
 | 
						|
detected, and ndhc also has extensive support for address defense.  Care is
 | 
						|
taken to prevent unintentional ARP flooding under any circumstance.
 | 
						|
 | 
						|
ndhc also monitors hardware link status via netlink events and reacts
 | 
						|
appropriately when interface carrier status changes or an interface is
 | 
						|
explicitly deconfigured.  This functionality can be useful on wired networks
 | 
						|
when transient carrier downtimes occur (or cables are changed), but it is
 | 
						|
particularly useful on wireless networks.
 | 
						|
 | 
						|
FEATURES
 | 
						|
--------
 | 
						|
 | 
						|
Privilege-seperated.  Neither ifchd or ndhc runs as full root, and capabilities
 | 
						|
are divided between the programs.  Both programs run in a chroot.
 | 
						|
 | 
						|
Robust.  ndhc performs no runtime heap allocations -- malloc() is never called
 | 
						|
(and neither is brk(), mmap(), etc), and ndhc never performs recursive calls
 | 
						|
and only stack-allocates fixed-length types, so stack depth is bounded, too.
 | 
						|
ifchd lightly uses malloc(), but no heap allocations have long lifetimes, and
 | 
						|
are bounded from being large.
 | 
						|
 | 
						|
Active defense of IP address and IP collision avoidance.  ndhc fully implements
 | 
						|
RFC5227.  It is capable of both a normal level of tenacity in defense, where
 | 
						|
it will eventually back off and request a new lease if a peer won't relent
 | 
						|
in the case of a conflict, and of relentlessly defending a lease forever.  In
 | 
						|
either mode, it rate-limits defense messages, so it can't be tricked into
 | 
						|
flooding by a hostile peer or DHCP server, either.
 | 
						|
 | 
						|
Small.  Both ndhc and ifchd avoid unnecessary outside dependencies and are
 | 
						|
written in plain C.  The only library used is libcap, as the raw raw kernel API
 | 
						|
for capabilities is not guaranteed to stay stable.
 | 
						|
 | 
						|
Fast.  ndhc filters input using the BPF/LPF mechanism so that uninteresting
 | 
						|
packets are dropped by the operating system before ndhc even sees the data.
 | 
						|
ndhc also only listens to DHCP traffic when it's necessary.
 | 
						|
 | 
						|
Flexible.  ndhc can request particular IPs, send user-specified client IDs,
 | 
						|
write a file that contains the current lease IP, write PID files, etc.  One
 | 
						|
ifchd session can service multiple ndhc sessions.
 | 
						|
 | 
						|
Aware of the hardware link status.  If you disconnect an interface on which
 | 
						|
ndhc is providing dhcp service, it will be aware.  When the link status
 | 
						|
returns, ndhc will fingerprint the reconnected network and make sure that it
 | 
						|
corresponds to the one on which it has a lease.  If the new network is
 | 
						|
different, it will forget about the old lease and request a new one.
 | 
						|
 | 
						|
USAGE
 | 
						|
-----
 | 
						|
 | 
						|
1) Compile and install ifchd and ndhc.
 | 
						|
    a) gmake
 | 
						|
    b) Install the build/ifchd and build/ndhc executables in a normal place.  I
 | 
						|
       would suggest /usr/sbin or /usr/local/sbin.
 | 
						|
 | 
						|
1alt) Compile and install ifchd and ndhc.
 | 
						|
    a) Create a build directory:
 | 
						|
       mkdir build && cd build
 | 
						|
    b) Create the makefiles:
 | 
						|
       cmake ..
 | 
						|
    c) Build ifchd and ndhc:
 | 
						|
       make
 | 
						|
    d) Install the ifchd/ifchd and ndhc/ndhc executables in a normal place.  I
 | 
						|
       would suggest /usr/sbin or /usr/local/sbin.
 | 
						|
 | 
						|
2) Time to create the jail in which ifchd and ndhc will run.
 | 
						|
    a) Become root and create new group "ifchd".
 | 
						|
 | 
						|
        $ su -
 | 
						|
        # umask 077
 | 
						|
        # groupadd ifchd
 | 
						|
 | 
						|
    b) Create new users "ifchd" and "dhcp".  The primary group of these
 | 
						|
       users should be "ifchd".
 | 
						|
 | 
						|
        # useradd -d /var/lib/ndhc -s /sbin/nologin -g ifchd ifchd
 | 
						|
        # useradd -d /var/lib/ndhc -s /sbin/nologin -g ifchd dhcp
 | 
						|
 | 
						|
    b) Create the jail directory and set its ownership properly.
 | 
						|
 | 
						|
        # mkdir /var/lib/ndhc
 | 
						|
        # chown root.root /var/lib/ndhc
 | 
						|
        # chmod a+rx /var/lib/ndhc
 | 
						|
        # cd /var/lib/ndhc
 | 
						|
        # mkdir var
 | 
						|
        # mkdir var/state
 | 
						|
        # mkdir var/run
 | 
						|
        # chown -R ifchd.ifchd var
 | 
						|
        # chmod -R a+rx var
 | 
						|
        # chmod g+w var/run
 | 
						|
 | 
						|
    c) Create a urandom device for ndhc to use within the jail.
 | 
						|
 | 
						|
        # mkdir dev
 | 
						|
        # mknod dev/urandom c 1 9
 | 
						|
        # mknod dev/null c 1 3
 | 
						|
        # chown -R root.root dev
 | 
						|
        # chmod a+rx dev
 | 
						|
        # chmod a+r dev/urandom
 | 
						|
        # chmod a+rw dev/null
 | 
						|
 | 
						|
        d) (optional) If you wish for logging to properly work, you
 | 
						|
           will need to properly configure your logging daemon so that it
 | 
						|
           opens a domain socket in the proper location within the jail.
 | 
						|
           Since this varies per-daemon, I cannot provide a general
 | 
						|
           configuration.
 | 
						|
 | 
						|
3)  At this point the jail is usable; ifchd and ndhc are ready to
 | 
						|
    be used.  As an example of a sample configuration, here is my
 | 
						|
    rc.dhcp:
 | 
						|
 | 
						|
--START--
 | 
						|
 | 
						|
#!/bin/sh
 | 
						|
case "$1" in
 | 
						|
        start)
 | 
						|
                ifchd -i wan0 -p /var/run/ifchd.pid -u ifchd -g ifchd -U dhcp \
 | 
						|
                      -G ifchd -c /var/lib/ndhc &> /dev/null
 | 
						|
                ndhc -b -i wan0 -u dhcp -C /var/lib/ndhc &> /dev/null
 | 
						|
                ;;
 | 
						|
        stop)
 | 
						|
                killall ndhc ifchd
 | 
						|
                ;;
 | 
						|
esac
 | 
						|
 | 
						|
--END--
 | 
						|
 | 
						|
    This script works fine with my personal machines, which are set up
 | 
						|
    exactly as I have outlined above.  If you have not entirely followed my
 | 
						|
    directions, the script will of course require modifications.
 | 
						|
 | 
						|
4o) If you encounter problems, I suggest running both ifchd and ndhc in the
 | 
						|
    foreground and examining the printed output.
 | 
						|
 | 
						|
 | 
						|
BEHAVIOR NOTES
 | 
						|
--------------
 | 
						|
 | 
						|
ifchd does not enable updates of the local hostname and resolv.conf by default.
 | 
						|
If you wish to enable these functions, use the --resolve (-r) and --hostname
 | 
						|
(-o) flags.  See ifchd --help.
 | 
						|
 | 
						|
ifchd can be set such that it only allows clients to configure particular
 | 
						|
network interfaces.  The --interface (-i) argument does the trick, and may
 | 
						|
be used multiple times to allow multiple interfaces.
 | 
						|
 | 
						|
PORTING NOTES
 | 
						|
-------------
 | 
						|
 | 
						|
DHCP clients aren't naturally very portable.  It's necessary to perform a lot
 | 
						|
of tasks that are platform-specific.  ndhc is rather platform-dependent, and it
 | 
						|
extensively uses Linux-specific features.  Some of these features are also
 | 
						|
available on the BSDs.
 | 
						|
 | 
						|
1) Both ndhc and ifchd use the SO_PEERCRED flag of getsockopt() to discriminate
 | 
						|
authorized connections by uid, gid, and pid.  Similar functionality exists in
 | 
						|
at least the BSDs; however, it has a different API.
 | 
						|
 | 
						|
2) ifchd takes advantage of Linux capabilities so that it does not need full
 | 
						|
root privileges.  Capabilities were a proposed POSIX feature that was not made
 | 
						|
part of the official standard, so any implemention that may exist will be
 | 
						|
system-dependent.
 | 
						|
 | 
						|
3) ifchd configures network interfaces and routes.  Interface and route
 | 
						|
configuration is entirely non-portable, usually requiring calls to the
 | 
						|
catch-all ioctl(), or even more unusual mechanisms like netlink sockets.
 | 
						|
 | 
						|
4) ndhc uses netlink sockets extensively for both fetching data and hardware
 | 
						|
link state change notification events.
 | 
						|
 | 
						|
5) ndhc uses the Berkeley Packet Filter / Linux Packet Filter interfaces to
 | 
						|
drop unwanted packets in kernelspace.  This functionality is available on
 | 
						|
most modern unix systems, but it is not standard.
 | 
						|
 | 
						|
6) ndhc uses epoll() and signalfd().  These are Linux-specific.
 | 
						|
 | 
						|
7) Numerous socket options are used, and the AF_PACKET socket family is used
 | 
						|
for raw sockets and ARP.  These are largely Linux-specific, too.
 | 
						|
 | 
						|
8) ndhc uses strlcpy() and strlcat().  Native versions are provided.
 | 
						|
Some standard C libraries include a native implementation of strlcpy() and
 | 
						|
strlcat().  Such defines may conflict with my implementations in strl.c/strl.h.
 | 
						|
It is up to the user whether the standard C library implementations should be
 | 
						|
used.  Note that some machines implement strlcpy() and strlcat() with
 | 
						|
nonstandard semantics (notably Solaris).  On these systems, using the
 | 
						|
system-provided implementations may lead to security problems.  Such problems
 | 
						|
are the fault of the vendor.  If you are unsure whether your system is correct
 | 
						|
or not, I suggest using the implementation that I provide.
 | 
						|
 | 
						|
HISTORY
 | 
						|
-------
 | 
						|
 | 
						|
I started writing ndhc back in 2004.  My ISP at the time required a dhcp
 | 
						|
client for connection authentication, and I was not comfortable with any
 | 
						|
of the existing clients, which all ran as root and had colorful security
 | 
						|
histories.  DHCP is generally not a routed protocol, and lacks real
 | 
						|
authentication mechanisms in real world deployments (some largely
 | 
						|
abandoned RFCs for such behavior do exist), so no program existed to
 | 
						|
fill the niche of a truly secure DHCP client.
 | 
						|
 | 
						|
My router/server at the time ran a custom Linux distro that was designed
 | 
						|
for extreme security.  A root privileged DHCP client would be nearly the
 | 
						|
only root-owned process running on the machine, so I was highly motivated
 | 
						|
to develop an alternative.
 | 
						|
 | 
						|
ifchd was first written entirely from scratch.  It did not take long to write,
 | 
						|
since it is by design rather simple, and I was already familiar with
 | 
						|
the quirks of Linux capabilities.  That left me with the choice of adapting
 | 
						|
an existing DHCP client or writing my own from scratch.
 | 
						|
 | 
						|
At the time, I just wanted something that would work, so my choice was to
 | 
						|
adapt udhcpc to work with ifchd.  udhcpc was chosen since it was intended to
 | 
						|
be used with resource-constrained or embedded systems, and was thus very
 | 
						|
small.  ISC dhclient was another alternative, but it is an extremely large
 | 
						|
program, and it would have been very hard to audit it for correctness.
 | 
						|
 | 
						|
udhcpc was not did not really fit my requirements well, since it was designed
 | 
						|
to be small at all costs, sacrificing correctness when necessary.  The code was
 | 
						|
hard to follow, and had many quirks.  Bounds-checking was rare, type aliasing
 | 
						|
common, and state transitions were convoluted.  Not all of the client was
 | 
						|
asynchronous, and no precautions were taken against conflicting peers.  ARP was
 | 
						|
not used at all.
 | 
						|
 | 
						|
However, it was small.  With a lot of work, I ripped out the script-calling
 | 
						|
mechanisms and replaced them with ifchd requests.  Bounds-checking was
 | 
						|
aggressively (and somewhat hamfistedly) retrofitted into the code.  It was
 | 
						|
cleaned to a degree, and importantly it worked for connecting to my ISP.
 | 
						|
 | 
						|
Then I changed ISPs.  My new ISP used PPPoE, not dhcp.  Around the same time, I
 | 
						|
also switched to using Gentoo rather than a hand-built distribution.  I didn't
 | 
						|
have time to maintain the old custom setup, and it was very hard keeping up
 | 
						|
with library vulnerabilties in eg, zlib or openssl, and ensuring that all
 | 
						|
installed binaries, dynamic and static, were updated.  ndhc was abandoned for
 | 
						|
many years.  It wasn't needed on my server, and it was "too much effort" to
 | 
						|
deviate from the stock distro dhcp clients on other machines.
 | 
						|
 | 
						|
Then, around 2008, I changed ISPs again.  This time my new ISP used dhcp and
 | 
						|
not PPPoE.  So, after a few months, I decided to dust off the old ndhc/ifchd
 | 
						|
project and adapt it to my modern standards and machines.
 | 
						|
 | 
						|
ifchd was in good shape and required little work.  I ended up rewriting
 | 
						|
ndhc.  The only parts that remained from the original were the parts that
 | 
						|
I had already rewritten before, and some of those were rewritten, too.
 | 
						|
 | 
						|
The end result is a modern DHCP client is largely RFC-compliant, except where
 | 
						|
the RFCs dictate behavior that would be problematic, overly complex, useless,
 | 
						|
or exploitable.  DHCP is poorly specified, and real-world servers and clients
 | 
						|
vary a lot from the RFCs, so these conditions are necessary for a useful
 | 
						|
program.
 | 
						|
 | 
						|
Although ndhc's implementation and behavior are different, I have to credit
 | 
						|
the idea of using netlink events to discover hardware link status transitions
 | 
						|
to Stefan Rompf and his 'dhcpclient' program.  The Linux netlink events that
 | 
						|
are used are otherwise rather obscure and poorly documented, and I wouldn't
 | 
						|
have known about them otherwise.
 | 
						|
 | 
						|
GRSECURITY NOTES
 | 
						|
----------------
 | 
						|
 | 
						|
Make sure that CONFIG_GRKERNSEC_CHROOT_CAPS is disabled.  Otherwise, ifchd will
 | 
						|
lose its capabilities (in particular, the ability to reconfigure interfaces)
 | 
						|
when it chroots.
 | 
						|
 | 
						|
DHCP PROTOCOL QUIRKS
 | 
						|
--------------------
 | 
						|
 | 
						|
Send a packet that has an options field set to:
 | 
						|
'DHCP-OPTION-OVERLOAD:3'
 | 
						|
Then in the file and sname fields:
 | 
						|
'DHCP-OPTION-OVERLOAD:3'
 | 
						|
I suspect some bad dhcp programs will hang given this input.
 | 
						|
 | 
						|
Options concatenation is a minefield of poor specification.  There's a
 | 
						|
follow-up RFC to make proper behavior somewhat more defined, but it's still
 | 
						|
overly complex.
 | 
						|
 | 
						|
DHCP explicitly specifies that there is no minimum lease time and also
 | 
						|
specifies that the minimum default rebinding time is leasetime*0.875 and
 | 
						|
the minimum default renewing time is leasetime*0.500.  All times are relative
 | 
						|
to the instant when the lease is bound and are specified in seconds.  Taken
 | 
						|
together, this means that a client strictly implementing the RFC should
 | 
						|
accept a lease that either is perpetually rebinding (lease == 1s) or instantly
 | 
						|
expires (lease == 0s).  ndhc ignores the RFC and specifies a minimum lease
 | 
						|
time of one minute.
 | 
						|
 | 
						|
Renew and rebind times are optionally specified and may take on any value.
 | 
						|
This means that a malicious server could demand a rebind time before a renew
 | 
						|
time, or make these times ridiculously short, or specify both times past
 | 
						|
that of the lease duration.  ndhc avoids all of this nonsense by simply
 | 
						|
ignoring these options and using the default values specified by the RFC.
 | 
						|
 | 
						|
There are other quirks, but these are just several interesting ones that
 | 
						|
immediately occur to me while I'm writing this document.
 | 
						|
 |