diff --git a/CMakeLists.txt b/CMakeLists.txt index 2b5a129..e2c52d8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,8 +2,8 @@ project (ndhcp) cmake_minimum_required (VERSION 2.6) -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -s -std=gnu99 -pedantic -Wall -lcap -DHAVE_CLEARENV -DLINUX") -set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} -s -std=gnu99 -pedantic -Wall -lcap -DHAVE_CLEARENV -DLINUX") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -s -std=gnu99 -pedantic -Wall -lcap -D_GNU_SOURCE -DHAVE_CLEARENV -DLINUX") +set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} -s -std=gnu99 -pedantic -Wall -lcap -D_GNU_SOURCE -DHAVE_CLEARENV -DLINUX") include_directories("${PROJECT_SOURCE_DIR}/ncmlib") add_subdirectory(ncmlib) diff --git a/DESIGN b/DESIGN deleted file mode 100644 index 400e19d..0000000 --- a/DESIGN +++ /dev/null @@ -1,70 +0,0 @@ -Goals: - -1. Security - - a. Divide into seperate processes that each have the minimal - system access necessary to complete their task. - - b. Use a well defined IPC mechanism to facilitate cooperation - between processes. In this case, UNIX domain sockets are - used, since they allow for UNIX DAC (on Linux, at least). - - c. Write each program to be secure; don't rely on the - privilege seperations for security. - - d. Simple error handling is favored rather than complex error - handling that may possibly be caused to "recover" in an - exploitable way. - - e. Don't make stupid assumptions. Implement only the minimal - functionality necessary to perform a task. Expect brain - damaged or malicious inputs. - - f. Run inside a chroot, with minimal privileges via - capabilities or MAC. - -2. Reliability - - a. Don't try to handle severe errors. - - b. Log errors if program state is still sane. - - c. Recover from predictable problems if necessary. Make sure - that recovery behavior is well understood and defined. - - d. Complicated or unsafe recoveries should not be performed; - instead the program should promptly exit. Dead programs - don't cause exploits. - -3. Portability - - a. Portability is good, but portability may not be as wide as - a less secure program. Capabilities or MAC are not well - standardized, but remain necessary features. - -4. Miscellaneous - - a. Speed: If we aren't required to sacrifice anything more - important, it's always good to be fast. - - a. Size: If we aren't required to sacrifice anything more - important, it's always good to be frugal. - -Layout: - -ndhc daemon (root -> chroot -> drop all !(CAP_NET_BROADCAST|CAP_NET_RAW) - -> nopriv) - -* handles dhcp protocol issues, netlink hw link notifications, and ARP checks -* keeps track of leases -* talks to ifchd to perform tasks that require - higher privileges than CAP_NET_BROADCAST or CAP_NET_RAW - -ifchd daemon (root -> openfd -> chroot -> drop all !CAP_NET_ADMIN -> nopriv) - -* listens for interface change requests via UNIX domain socket -* restricts valid IP ranges that will be accepted -* performs interface changes -* keeps rw fds for system files (such as /etc/resolv.conf) that must - be modified outside the chroot - diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f694c5a --- /dev/null +++ b/Makefile @@ -0,0 +1,42 @@ +# This is a pretty basic makefile. I generally use CMake, so this is mostly +# for distros that want to avoid build dependencies. Produced exes will be +# at './build/ndhc' and './build/ifchd'. + +NCM_SRCS = $(sort $(wildcard ncmlib/*.c)) +IFCHD_SRCS = $(sort $(wildcard ifchd/*.c)) +NDHC_SRCS = $(sort $(wildcard ndhc/*.c)) +NCM_OBJS = $(NCM_SRCS:.c=.o) +IFCHD_OBJS = $(IFCHD_SRCS:.c=.o) +NDHC_OBJS = $(NDHC_SRCS:.c=.o) +NCM_INC = -I./ncmlib +BUILD_DIR = build +OBJ_DIR = objs + +CC = gcc +AR = ar +RANLIB = ranlib +CFLAGS = -O2 -s -std=gnu99 -pedantic -Wall -D_GNU_SOURCE -DHAVE_CLEARENV -DLINUX + +all: makedir ncmlib.a ifchd ndhc + +clean: + rm -Rf $(OBJ_DIR) $(BUILD_DIR) + +makedir: + mkdir -p $(OBJ_DIR)/ndhc $(OBJ_DIR)/ifchd $(OBJ_DIR)/ncmlib $(BUILD_DIR) + +%.o: %.c + $(CC) $(CFLAGS) $(NCM_INC) -c -o $(OBJ_DIR)/$@ $< + +ncmlib.a: $(NCM_OBJS) + $(AR) rc $(BUILD_DIR)/$@ $(subst ncmlib/,$(OBJ_DIR)/ncmlib/,$(NCM_OBJS)) + $(RANLIB) $(BUILD_DIR)/$@ + +ifchd: $(IFCHD_OBJS) + $(CC) $(CFLAGS) $(NCM_INC) -o $(BUILD_DIR)/$@ $(subst ifchd/,$(OBJ_DIR)/ifchd/,$(IFCHD_OBJS)) $(BUILD_DIR)/ncmlib.a -lcap + +ndhc: $(NDHC_OBJS) + $(CC) $(CFLAGS) $(NCM_INC) -o $(BUILD_DIR)/$@ $(subst ndhc/,$(OBJ_DIR)/ndhc/,$(NDHC_OBJS)) $(BUILD_DIR)/ncmlib.a -lcap + +.PHONY: all clean + diff --git a/README b/README index b618a98..8ba882e 100644 --- a/README +++ b/README @@ -1,4 +1,4 @@ -ifchd, copyright (c) 2004-2011 Nicholas Kain. Licensed under GNU GPL. +ndhc + ifchd, copyright (c) 2004-2011 Nicholas Kain. Licensed under GNU GPL2. Requirements: @@ -47,12 +47,19 @@ 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. +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. 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: @@ -129,8 +136,7 @@ esac directions, the script will of course require modifications. 4o) If you encounter problems, I suggest running both ifchd and ndhc in the - foreground, and perhaps compiling ndhc with extra debugging output - (uncomment DEBUG=1 in the Makefile). + foreground and examining the printed output. BEHAVIOR NOTES @@ -144,31 +150,38 @@ 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. -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. - - PORTING NOTES ------------- -There are seven major functions that ifchd depends upon that are not generally -portable. First, it uses 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. Second, 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. -Third and fourth, ifchd configures network interfaces and routes. Interface -and route configuration is entirely non-portable, usually requiring calls to -the catch-all ioctl(), and will almost certainly require platform-dependent -code. Fifth and sixth, both ifchd and ndhc use epoll() and signalfd(), which -are Linux-specific. Seventh, ndhc uses netlink sockets extensively for -both fetching data and hardware link state change notification events. +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 @@ -177,3 +190,80 @@ 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 all that great of a choice, 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. + + + diff --git a/ifchd/ifchd.c b/ifchd/ifchd.c index 447a5e0..b7cad32 100644 --- a/ifchd/ifchd.c +++ b/ifchd/ifchd.c @@ -38,7 +38,6 @@ #include #include -#define _GNU_SOURCE #include #include "ifchd-defines.h" diff --git a/ndhc.sh b/ndhc.sh deleted file mode 100644 index 1fe0080..0000000 --- a/ndhc.sh +++ /dev/null @@ -1,93 +0,0 @@ -# Copyright (c) 2007-2008 Roy Marples -# All rights reserved. Released under the 2-clause BSD license. - -ndhc_depend() -{ - program start /sbin/ndhc - after interface - provide dhcp -} - -_config_vars="$_config_vars dhcp ndhc" - -ndhc_start() -{ - local args= opt= opts= pidfile="/var/run/ndhc-${IFACE}.pid" - local sendhost=true - local leasefile="/var/state/${IFACE}.lease" - - eval args=\$ndhc_${IFVAR} - - # Get our options - eval opts=\$dhcp_${IFVAR} - [ -z "${opts}" ] && opts=${dhcp} - - # # Map some generic options to ndhc - # for opt in ${opts}; do - # case "${opt}" in - # nodns) args="${args} --env PEER_DNS=no";; - # nontp) args="${args} --env PEER_NTP=no";; - # nogateway) args="${args} --env PEER_ROUTERS=no";; - # nosendhost) sendhost=false; - # esac - # done - - # [ "${metric:-0}" != "0" ] && args="${args} --env IF_METRIC=${metric}" - - ebegin "Running ndhc" - - case " ${args} " in - *" --quit "*|*" -q "*) x="/sbin/ndhc";; - *) x="start-stop-daemon --start --exec /sbin/ndhc \ - --pidfile ${pidfile} --";; - esac - - case " ${args} " in - *" --hostname="*|*" -h "*|*" -H "*);; - *) - if ${sendhost}; then - local hname="$(hostname)" - if [ "${hname}" != "(none)" ] && [ "${hname}" != "localhost" ]; then - args="${args} --hostname='${hname}'" - fi - fi - ;; - esac - - # delay until carrier is up - ip link set "${IFACE}" up - ip link show "${IFACE}" | grep NO-CARRIER >/dev/null 2>&1 - while [ "$?" != "1" ]; do - sleep 1 - ip link show "${IFACE}" | grep NO-CARRIER >/dev/null 2>&1 - done - - eval "${x}" "${args}" -r `cat /etc/firewall/tmp/OLDEXTIP` \ - -n -i "${IFACE}" -u "ndhc" -C "/var/lib/ndhc" \ - -p "${pidfile}" -l "${leasefile}" >/dev/null - eend $? || return 1 - - _show_address - return 0 -} - -ndhc_stop() -{ - local pidfile="/var/lib/ndhc/var/run/ndhc-${IFACE}.pid" opts= - [ ! -f "${pidfile}" ] && return 0 - - # Get our options - eval opts=\$dhcp_${IFVAR} - [ -z "${opts}" ] && opts=${dhcp} - - ebegin "Stopping ndhc on ${IFACE}" - case " ${opts} " in - *" release "*) - start-stop-daemon --stop --quiet --oknodo --signal USR2 \ - --exec /sbin/ndhc --pidfile "${pidfile}" - ;; - esac - - start-stop-daemon --stop --exec /sbin/ndhc --pidfile "${pidfile}" - eend $? -}