1824802fb2
ndhc will fork off an ifchd child that it will communicate with via pipes rather than by connecting to a SO_PEERCRED AF_UNIX socket. The advantages include: 1. Simpler configuration. Much easier for users and packagers to set up. 2. Drastically less complex code for the ifch functionality. More code is removed than added, and the result is a lot less complex. 3. Potentially better security. The ifch can only service the parent ndhc process, and it is restricted to issuing modifications to the single interface that ndhc manages. 4. Less memory used on systems that allow overcommit. The downsides: 1. Possibly more memory used on systems that run multiple ndhcs and use strict commit limits. At the same time, use netlink rather than ioctls so that the interface ip, subnet, and broadcast address can be set simultaneously. This change reduces the netlink notification spam greatly. The current code builds but isn't yet complete. Subsequent commits will flesh things out and polish out some remaining issues.
318 lines
14 KiB
Plaintext
318 lines
14 KiB
Plaintext
ndhc + ifchd, Copyright (C) 2004-2014 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-separated 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.
|
|
|
|
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.
|
|
|
|
RFC3927's IPv4 Link Local Addressing is not supported. I have found v4 LLAs
|
|
to be more of an annoyance than a help. v6 LLAs work much better in practice.
|
|
|
|
FEATURES
|
|
--------
|
|
|
|
Privilege-separated. 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.
|
|
|
|
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.
|
|
|
|
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.
|
|
|