FreeBSD - a lesson in poor defaults

Last updated: 06/21/2020. @blakkheim

This page lists some of the changes I make to a vanilla install of FreeBSD for security hardening. Some changes to increase network performance or make things a bit more more sane are also included. It only covers basic changes that a sysadmin can make to a running system.

It could also be considered a commentary piece on the state of security in FreeBSD's development ecosystem, highlighting their strong resistance to change and unwillingness to replace old cruft with modern alternatives.

The project's security page says the following:

FreeBSD takes security very seriously and its developers are constantly working on making the operating system as secure as possible.

But is that really true? Let's find out.


Table of Contents


OpenSSH Modifications

FreeBSD has a history of making "interesting" choices with regard to the version of OpenSSH they bundle in the base system, often deliberately going against upstream in the name of retaining backward compatibility or to gain perceived performance improvements. Disabling or ignoring security features in favor of performance seems to be a recurring theme, as we'll see later on.

It is my belief that quite a few poor decisions have been made in this area. As a primary example, they insisted on maintaining the HPN-SSH patchset and enabling it by default for quite a long time.

You might say "Well OK, but what's actually wrong with those patches?"

OpenSSH increased the channel limits enough to support a cross-country gigabit connection without slowdown years ago. For most users, this means that the HPN patches are an unnecessary complexity with little or no benefit. In addition to that, they would frequently hold FreeBSD back from updating their version of OpenSSH because of HPN backporting and manual refactoring of the patchset.

Support for tcp_wrappers was abandoned long ago in OpenSSH upstream, but FreeBSD still patched it back in and enabled it for everyone.

The same was true for weak DSA host keys, which they switched back on for compatibility with older clients.

FreeBSD also re-enabled insecure encryption ciphers in their build after they were disabled upstream, with backward compatibility apparently being more important to them than the security of their users.

If we don't deprecate insecure options bit by bit somewhere in the ecosystem, we end up with a situation like OpenSSL. Pressure has to be applied somewhere. One can be part of that team, or one can play against them. In this case, FreeBSD is the team trying to increase the risk.

They've made local changes to their OpenSSH build and its default config files that left their users vulnerable when other platforms were unaffected. Here's a (possibly incomplete) list of examples:

And the PAM issues in these two:

A list of FreeBSD's modifications to both the code and config files can be seen here for the base system (possibly outdated/unmaintained) and here for the ports version.

Thankfully, as hinted above, it's fairly easy to install OpenSSH from ports with all the FreeBSDisms removed. Their openssh-portable port has a hefty set of options you can toggle:

BSM=off:           OpenBSM Auditing
DOCS=on:           Build and/or install documentation
HPN=off:           HPN-SSH patch
KERB_GSSAPI=off:   Kerberos/GSSAPI patch (req: GSSAPI)
LDNS=on:           SSHFP/LDNS support
LIBEDIT=on:        Command line editing via libedit
NONECIPHER=off:    NONE Cipher support
PAM=on:            Pluggable authentication module support
TCP_WRAPPERS=on:   tcp_wrappers support
XMSS=off:          XMSS key support (experimental)
MIT=off:           MIT Kerberos (security/krb5)
HEIMDAL=off:       Heimdal Kerberos (security/heimdal)
HEIMDAL_BASE=off:  Heimdal Kerberos (base)

Users of the project's binary package repo (and ports/poudriere users that don't explicitly change their build options) will get an OpenSSH with this set of options.

Besides the modifications mentioned above, two other patches in particular have been popular with FreeBSD users: threaded AES-CTR and the "NONE" cipher.

Threaded AES-CTR, as the name might imply, introduces threads to the code. OpenSSH developerss have publicly said threads are too risky and won't be added. What's more, it's largely obsoleted by AES-NI in modern CPUs and the fact that ChaCha20-Poly1305 (the current default cipher) is even faster when taking the message authentication code (MAC) into consideration.

The NONE cipher is somewhat of a misfeature, removing the encryption bits and only keeping the data integrity. It allows users to accidentally shoot themselves in the foot pretty easily. The trade-off in performance isn't really worth it either, as the bottlenecks one might experience have a lot more to do with the MAC than the actual encryption overhead.

I recommend disabling all of the port options. The majority of users don't need the extra risk that any of these non-standard patches introduce.

As for the /usr/local/etc/ssh/sshd_config file, I would recommend enabling only modern crypto. However, cryptography is a very complicated and important topic, so you would be better served to research each algorithm and come to your own conclusions instead of just taking my word. Thankfully, upstream OpenSSH cares about security and continues to remove old, broken algorithms as time goes on. FreeBSD can't keep patching them back in and maintaining their backports. It's simply too much work.

This isn't meant to be an entire SSH tutorial, but consider changing your default port to something other than 22 if you want less spam in your logs, set up public keys and disable password authentication... the usual stuff.

The following config lines are just to revert FreeBSD's local changes that introduce new risks:

ChallengeResponseAuthentication no         
UsePAM                          no 
VersionAddendum                 none  # Prevent some OS information from being
                                      # leaked by your sshd, another one of
                                      # FreeBSD's "enhancements" in both the
                                      # base system and ports version.
X11Forwarding                   no        

In addition to improved security, using the port of OpenSSH allows you to upgrade to newer versions much faster than waiting for a new release of the base system. As an example, FreeBSD has comically been limited to "upgrading" their base OpenSSH to a release that's three versions behind the current one at the time because of their local patches. Note that you may need further configuration changes if using the bundled OpenSSH, as its options aren't as easily fixable.

Mailer Daemon

FreeBSD includes Sendmail in the base system and enables it by default.

I'd like to share a quote from one of the earliest security advisories FreeBSD ever published:

The sendmail mail transfer agent has a rather poor reputation for security related problems. FreeBSD ships a version of sendmail that has all known security problems fixed, but this doesn't mean there won't be more found in the future.
And it turns out whoever wrote that back in 1996 wasn't wrong. (There are many more where those came from.)

Even with Sendmail's reputation, FreeBSD has kept it around and left it on by default for all their users.

I think most users will agree that this old program is mostly just a bother. A full mail server, especially one that's been full of holes since the 90s, shouldn't be in the base system at all if you ask me. Something simple for local mail delivery should be enough.

I stop all the related services because it makes startup slower and I'd like as few things running in the background as possible. The lines needed to disable Sendmail will be in the rc.conf section later on.

If you actually run a mail server, there are better options like Postfix or OpenSMTPD. If you only need to send mail through a third party provider like Gmail, msmtp is a another lightweight alternative. All three are in ports.

Firewall

There are three firewalls included with FreeBSD: IPFW, PF, and IPFilter. None of them are enabled by default.

I don't know anything about IPFilter, nor do I know anyone that uses it, so we'll pretend it doesn't exist. IPFW is the native firewall. It was written by FreeBSD, for FreeBSD, with their usual coding standards. The result is about what you might expect. PF is the OpenBSD firewall. It was ported from OpenBSD to FreeBSD. Which one you use mostly comes down to preference. Since I'm more familiar with PF, that's what I use. Unfortunately, the version of PF included with FreeBSD hasn't been synced with upstream since 2009. Do you want such a critical component of your OS or network device left largely unmaintained?

Check the documentation for whichever firewall you decide to use for proper configuration instructions.

Ports and Packages

Other than just manually compiling things yourself, there are three main ways to install software on FreeBSD:

There are pros and cons to each.

pkg pros:

pkg cons: ports pros: ports cons: poudriere pros: poudriere cons: If you only have one FreeBSD system, using ports might be the easiest option. If you have multiple systems, poudriere makes the whole process a lot easier. If you're a total beginner or don't care what options things are built with, using pkg and the prebuilt binaries is the quickest and easiest option.

However, more security concerns arise...

For starters, both the ports system and pkg will do a lot of things as root when it's not needed at all. I brought this up to a member of the ports security team and he simply shrugged it off. Just because portsnap checks the snapshots it fetches against a public key, he figured there was nothing to worry about. I have to question their credibility sometimes.

It's true that verifying the files it fetches would indeed be a good thing... if that was done before the more dangerous operations... but it wasn't. The data integrity check was done very late in the process, giving plenty of opportunity for exploits against the other tools called by the shell script, all running as root and taking untrusted input from the internet. Both portsnap and freebsd-update have a serious design flaw here that could be easily fixed. Perhaps they have the utmost confidence in the tools being bug-free, but I try to be a bit more realistic.

And it turns out I was right, detailed extensively here. The short version is basically "anyone using these tools can be remotely compromised at the root level."

The tools also run gunzip (as root) before the data has been verified, so it seems FreeBSD hasn't learned much since that catastrophe.

Similar issues were brought up on their mailing lists, though nothing ever came of those discussions either.

These bugs and poor design choices have left FreeBSD users vulnerable to a root-level compromise every time they update their system or ports tree. Think about that.

Despite the issues being brought up on their lists in April 2014, despite public exploits being published in May 2016, and despite multiple big news sites picking up the story, they were all left unfixed until October 2016. The FreeBSD security team left all users vulnerable to these exploits for a very long time.

But back to ports and pkg, where similar design flaws will probably never be fixed.

There's more risk involved than just letting root go out to the internet to download files. Here's a summary of what happens in the process of building ports:

1. Fetch and update the ports tree
2. Fetch the software's source code
3. Verify the checksum of the file(s)
4. Extract the source tarball
5. Run the configure script, apply local patches, and build the application
6. Create a package from the built files
7. Install the package to your system (if desired)

So how many of these actually need to be done as root? Only the last one. And how many of these are done as root by default in FreeBSD? All of them.

  PID USERNAME    THR PRI NICE   SIZE    RES STATE   C   TIME    WCPU COMMAND
84266 root          1  52    0 36272K  8056K wait    0   0:00   0.68% cc
84191 root          1  52    0  9116K  1332K wait    0   0:00   0.29% make
84267 root          1  47    0 36416K 20484K zio->i  1   0:00   0.00% cc

Maybe people don't realize the risk of actually building all these third party tools with root privileges. Have you read every line of those 25,000+ configure scripts? I've seen some configure scripts running ping to phone home and all kinds of weird stuff. Since everything runs as root, all it takes is one malicious command tucked away in a build script somewhere to completely compromise the host if you use ports in its default configuration.

This is made even worse by the negligence of some port maintainers, as was the case in the cryptographic bypass and MITM-based compromise against certain ports.

Surprisingly, FreeBSD does have some support for doing package builds as a non-root user. Though I'm told staging is integrated into all their ports now, the default is still to do everything as root. Why?

To work around this issue, I tried manually introducing some privsep in the build process on my machine with a "_ports" user. You'd need to chown a few directories or make them writable by this user. Many changes were needed in the /etc/make.conf file, and it appears that it's really just not designed to be done this way, so I'd call it more of an experiment than a solution. Why is all this needed? It's like things were designed to be as troublesome to secure as possible so no one ever tries it. Hey, works as intended if so.

The poudriere tool uses FreeBSD's jail system for some filesystem isolation during the process, so it's a little safer than using ports in this regard. However, the distfiles are still fetched as root, the portsnap/svn commands are run as root, etc. That's on the host system, by the way, not jailed. All these tasks are trivial to isolate with different users, but poudriere doesn't do that. The only operation that poudriere does as an unprivileged user is the compiling, and that change took years of pressure to make.

The pkg tool itself also runs everything as root, from fetching and verifying the packages to untarring them and registering their installation.

  PID USERNAME    THR PRI NICE   SIZE    RES STATE   C   TIME    WCPU COMMAND
84554 root          1  22    0 42996K  7452K select  1   0:00   0.59% pkg

Just for comparison, look at the security history of Debian's pkg equivalent.

The "but packages are signed!" defense I've gotten from some users really demonstrates a lack of understanding of what's actually going on when you run that pkg command.

FreeBSD's binary packages are also fetched over plaintext HTTP by default, even though the main mirror site supports HTTPS.

The library used to download both packages and ports distfiles (libfetch) is certainly not immune to serious security problems, so why let a trivial bug turn into a privilege escalation nightmare? Actually, for FreeBSD, I guess there's no "escalation" since it's already running as root the whole time...

NTP

Many of FreeBSD's security advisories have been related to the NTP daemon included in the base system, known as the "reference implementation." Here's another (possibly incomplete) list: Expect to see many more of these as time goes on. Some also contain multiple vulnerabilities.

The ntpd code was written mainly by time geeks and scientists instead of people who actually run network-facing services. Obviously I want to keep my clock synced to the correct time, so what do I do here? Luckily, there are a number of alternative NTP daemons to choose from.

One FreeBSD committer is (or was) working on an NTP implementation called ntimed. I haven't tried it myself. Can't be any worse than what FreeBSD ships now though.

Another option, the one I use, is called OpenNTPD. Based on the name, see if you can guess where it originates. A simple /usr/local/etc/ntp.conf may look something like this:

constraint from "https://www.freebsd.org"
servers    pool.ntp.org

Note that the constraints option is not available for FreeBSD's default package. If you want that additional security benefit, you need to use LibreSSL instead of OpenSSL. Details on that later.

Much like the firewall section, this is up to your personal preference. There are trade-offs for both. OpenNTPD has an excellent security track record and a simple config syntax, but ntimed will likely give you better microsecond precision. Whichever you choose, just don't use the base one.

sysctl.conf

The /etc/sysctl.conf file is a great place to tweak lots of things, but my example is mainly to fix poor defaults related to network performance and security. The documentation isn't great. Here's what I came up with:

hw.kbd.keymap_restrict_change=4
kern.elf32.aslr.enable=1
kern.elf32.aslr.honor_sbrk=0
kern.elf32.aslr.pie_enable=1
kern.elf64.aslr.enable=1
kern.elf64.aslr.honor_sbrk=0
kern.elf64.aslr.pie_enable=1
kern.ipc.shm_use_phys=1
kern.randompid=1
net.inet.ip.check_interface=1
net.inet.ip.process_options=0      # Enable if you need IGMP or multicast.
net.inet.ip.random_id=1
net.inet.ip.redirect=0
net.inet.tcp.cc.algorithm=cubic
net.inet.icmp.drop_redirect=1
net.inet.tcp.drop_synfin=1
net.inet.sctp.blackhole=2
net.inet.tcp.blackhole=2
net.inet.udp.blackhole=1           # Note the blackhole options can sometimes
                                   # make debugging network issues more difficult.
net.inet.tcp.icmp_may_rst=0
security.bsd.hardlink_check_gid=1  # These two options will break poudriere's
security.bsd.hardlink_check_uid=1  # compiling privsep.
security.bsd.see_other_gids=0
security.bsd.see_other_uids=0
security.bsd.stack_guard_page=1
security.bsd.unprivileged_proc_debug=0
security.bsd.unprivileged_read_msgbuf=0

The following descriptions were taken from "sysctl -d" output with some minor grammar fixes:

hw.kbd.keymap_restrict_change - Restrict ability to change keymap

kern.elf32.aslr.enable - ELF32: enable address map randomization

kern.elf64.aslr.enable - ELF64: enable address map randomization

kern.elf32.aslr.honor_sbrk - ELF32: assume sbrk is used

kern.elf64.aslr.honor_sbrk - ELF64: assume sbrk is used

kern.elf32.aslr.pie_enable - ELF32: enable address map randomization for PIE binaries

kern.elf64.aslr.pie_enable - ELF64: enable address map randomization for PIE binaries

kern.ipc.shm_use_phys - Enable/Disable locking of shared memory pages in core

kern.randompid - Random PID modulus. (FreeBSD does not randomize process IDs by default.)

net.inet.icmp.drop_redirect - Ignore ICMP redirects

net.inet.ip.check_interface - Verify packet arrives on correct interface

net.inet.ip.process_options - Enable IP options processing ([LS]SRR, RR, TS)

net.inet.ip.random_id - Assign random ip_id values (FreeBSD does not randomize IP IDs by default.)

net.inet.ip.redirect - Enable sending IP redirects

net.inet.sctp.blackhole - Enable SCTP blackholing (SCTP being enabled in the kernel is another horrible default. Long history of security problems. Highly recommend removing it from your kernel.)

net.inet.tcp.blackhole - Do not send RST on segments to closed ports

net.inet.tcp.cc.algorithm - Default TCP congestion control algorithm (FreeBSD uses newreno by default, which is severely outdated and often results in poor upload speeds for LFNs.)

net.inet.tcp.drop_synfin - Drop TCP packets with SYN+FIN set

net.inet.tcp.icmp_may_rst - Certain ICMP unreachable messages may abort connections in SYN_SENT

net.inet.udp.blackhole - Do not send port unreachables for refused connects

security.bsd.hardlink_check_gid - Unprivileged processes cannot create hard links to files owned by other groups

security.bsd.hardlink_check_uid - Unprivileged processes cannot create hard links to files owned by other users

security.bsd.see_other_gids - Unprivileged processes may see subjects/objects with different real gid

security.bsd.see_other_uids - Unprivileged processes may see subjects/objects with different real uid

security.bsd.stack_guard_page - Insert stack guard page ahead of the growable segments (Also see The Stack Clash for a report on how poorly this was implemented.)

security.bsd.unprivileged_proc_debug - Unprivileged processes may use process debugging facilities

security.bsd.unprivileged_read_msgbuf - Unprivileged processes may read the kernel message buffer Some of these seem like painfully obvious choices. Why require every user to do so much research and configuration just to make up their system a bit more sane?

Periodic

Many useless things, some even potentially a security risk, will be running in the background by default. A lot of them are poorly documented or maybe not documented at all. If you configure your system to send email, expect some extremely long daily reports (with nothing useful in them) to be sent to root's inbox. I don't need my disks being thrashed every night because someone thought it was a good idea to enable every check under the sun. FreeBSD is very similar to Windows in this way.

Have a look at the /etc/defaults/periodic.conf file. It shows which scripts from the base system are run by default when periodic is called from cron. In a standard configuration, all periodic scripts can be seen in either /etc/periodic (for base daemons) or /usr/local/etc/periodic (for ports). When you install a port or package, it may also add new periodic scripts and even enable them by default - something to be aware of.

For reference, this is the /etc/periodic.conf that I use to disable much of the background activity.

daily_backup_aliases_enable="NO"
daily_backup_pkg_enable="NO"
daily_backup_pkgdb_enable="NO"
daily_clean_preserve_enable="NO"
daily_clean_rwho_enable="NO"
daily_status_pkg_changes_enable="NO"
daily_status_security_chkmounts_enable="NO"
daily_status_security_chkportsum_enable="NO"
daily_status_security_chksetuid_enable="NO"
daily_status_security_enable="NO"
daily_status_security_ipfdenied_enable="NO"
daily_status_security_ipfwdenied_enable="NO"
daily_status_security_neggrpperm_enable="NO"
daily_status_security_pfdenied_enable="NO"
daily_status_security_pkg_checksum_enable="NO"
daily_status_security_pkgaudit_enable="NO"
monthly_accounting_enable="NO"
monthly_statistics_enable="NO"
monthly_statistics_report_devices="NO"
monthly_status_security_enable="NO"
weekly_locate_enable="NO"
weekly_noid_enable="NO"
weekly_status_pkg_enable="NO"
weekly_status_pkg_enable="NO"
weekly_status_security_enable="NO"
weekly_whatis_enable="NO"

That list may not be what everyone wants, and that's ok. Since your needs will dictate which scripts you want running, I'll only suggest disabling one in particular:

daily_status_security_pkgaudit_enable="NO"

I most certainly don't want pkg (running as root, remember?) going out to the internet every night to fetch a list of vulnerable ports. Who thought this was safe?

Being alerted to vulnerabilities in your installed packages is nice, but there's simply no need to be doing this operation as root. The dangerous combination of laziness and poor software design is quite prevalent here. The ports committers often don't even update that vulnerability database when new public flaws are found, so this provides little benefit anyway.

SSL/TLS Library

OpenSSL is included with the FreeBSD base system, and there are two main issues with that: First, some supported versions of FreeBSD included an unsupported version of OpenSSL, requiring the security team to backport all the fixes themselves (or, more realistically, just leave them unfixed forever). Second, OpenSSL itself has many problems you've probably heard about like lack of code review and their unwillingness to remove support for arcane systems that no one has used in many years. You know... maybe OpenSSL and FreeBSD are a perfect fit in that regard. Long list, right? It should actually be longer. At some point FreeBSD started importing OpenSSL's security updates with no mention of their security implications. This means proper advisories were never published, and -RELEASE users never got those fixes until they upgraded to a new OS version.

Similar to the NTP list, I would expect many more of these to come out. Also note that many of these advisories contain multiple vulnerabilities.

FreeBSD releases have even been delayed due to OpenSSL security bugs on more than one occasion.

And what about when FreeBSD leaves your version vulnerable for months at a time or more?

We're dealing with highly insecure software, maintained by a largely-inactive "security team." It's an obvious recipe for disaster.

So how can we make things better? In FreeBSD, your SSL library choices essentially come down to these three:

My recommendation is to use LibreSSL from ports and avoid base system utilities when interfacing with SSL/TLS if possible. The relevant /etc/make.conf line (or poudriere equivalent) to switch over is:

DEFAULT_VERSIONS+=ssl=libressl-devel     # or "libressl" for older stable version

Time has shown that switching to LibreSSL will cut down the number of vulnerabilities you have to deal with by a considerable margin, as well as the average severity of them. In contrast to FreeBSD's bundled version of OpenSSL, you'll also actually get the security fixes when a new version is released, assuming your ports are kept up to date.

Swap

I think swap should always be encrypted. Some FreeBSD developers disagree. It's surprising just how much private data in memory gets written to disk. Someone I know has run hexdump on his swap partition and found PGP private keys in plain view. Your SSH key might be password-protected on disk, but it could end up in swap sooner or later... with no password needed. Am I the only one who sees a problem here?

Here's an example /etc/fstab line for a standard swap partition:

# Device                Mountpoint      FStype  Options         Dump    Pass#
/dev/ada0p3             none            swap    sw              0       0

Now here's the same thing with the swap automatically encrypted:

# Device                Mountpoint      FStype  Options         Dump    Pass#
/dev/ada0p3.eli         none            swap    sw              0       0

All you need to do is add ".eli" to the device name. A one-time key will be generated and destroyed when swap is unmounted, so the swap contents should be unrecoverable. If you had unencrypted swap previously, consider using dd to write random data over it before encrypting. It's so easy. Why not just do this for everyone? Why risk it?

Permissions

Standard unix stuff. Run users with umask 077, chmod 700 home directories, don't let users read the firewall rules or other important config files. This prevents a lot of things from being leaked if a compromised process has read access to the filesystem. I think jails and "container culture" have made a lot of people really lazy about this kind of thing, but I still do it. Many important files and directories are world-readable by default in FreeBSD.

rc.conf

In addition to the items for what I've gone over already, I don't know why FreeBSD doesn't clear /tmp on startup, but that's expected behavior for me. If we're not going to be doing any remote logging, I'd prefer that syslogd doesn't open any sockets. Note that the microcode_update_enable line (which loads the updated Intel/AMD CPU microcode for security fixes) only works if the devcpu-data package/port is installed.

These are the relevant lines in my /etc/rc.conf file:

clear_tmp_enable="YES"
microcode_update_enable="YES"
ntpd_enable="NO"
openntpd_enable="YES"
openssh_enable="YES"
pf_enable="YES"
sendmail_enable="NO"
sendmail_msp_queue_enable="NO"
sendmail_outbound_enable="NO"
sendmail_submit_enable="NO"
sshd_enable="NO"
syslogd_flags="-ss"

Make sure you understand the difference between sshd_enable and openssh_enable if you're doing this on a remote machine.

loader.conf

The /boot/loader.conf file is similar to /etc/sysctl.conf, but its values are loaded much earlier. You probably have one already. This is a short list of things I recommend adding to it:

cc_cubic_load="YES"
kern.random.fortuna.minpoolsize="128"
machdep.hyperthreading_allowed="0"
hw.spec_store_bypass_disable="1"
hw.mds_disable="3"

The CUBIC TCP congestion control algorithm requires a kernel module to be loaded or it won't work. The default "newreno" algorithm is old and not suitable for modern networks.

We increase the minimum entropy pool size necessary to cause a reseed. The default is a bit low for my taste.

Intel's hyperthreading technology (also known as SMT, Simultaneous MultiThreading) has proven itself to be insecure, so it should be disabled here.

Finally, FreeBSD thought it would be a good idea to fix a number of CPU vulnerabilities but leave the fixes turned off by default for performance reasons. If you're a proactive user that enables them manually to protect yourself, well, that might not even be good enough either... since they didn't even get one of the fixes right. Nevertheless, I recommend enabling them if you have an affected CPU.

Compile-Time Options

Some non-default security features in FreeBSD require you to compile your own kernel, userland, and/or third party packages with modified options. Using the project-provided binaries will leave you without the protections. Most of these options are defined in the /etc/src.conf and /etc/make.conf files. The man pages for src.conf (primarily for including/excluding features of the base OS in your custom build) and make.conf (for other build options) can be helpful if you've never used either one before.

I can recommend the following /etc/src.conf lines for security improvements:

WITHOUT_FREEBSD_UPDATE=yes
WITHOUT_NTP=yes
WITHOUT_PORTSNAP=yes
WITHOUT_SENDMAIL=yes
WITHOUT_SOURCELESS=yes
WITHOUT_TCP_WRAPPERS=yes
WITHOUT_KERBEROS=yes        # Exclude if you explicitly need kerberos
WITHOUT_PAM_SUPPORT=yes     # Exclude if you explicifly need PAM
WITH_BIND_NOW=yes
WITH_RETPOLINE=yes
WITH_KERNEL_RETPOLINE=yes
WITH_PIE=yes

Many other WITH/WITHOUT knobs exist, allowing you to rip out parts of the OS that you don't need (and will only waste time recompiling for every update) so give the man page a read and come up with your own combination. Mine's 83 lines long.

Once you rebuild the OS with your modifications, be sure to run the make targets that also delete leftover files, like delete-old and delete-old-libs. Because FreeBSD's developers seem to have trouble keeping the list of old files up to date, even when users submit bug reports doing the work for them, there will likely still be leftover files that those make targets do not actually clean up. Finding and removing them must be done manually, or you can create installation media from your custom build and reinstall with that.

Because there is no one-size-fits-all list of settings, no example make.conf will be provided here. However, here are two quick tips:

For the kernel, most options are defined in the /usr/src/sys/*/conf/GENERIC config file. If you're going the source-only route to compile in the missing security options, I'd also recommend copyingGENERIC to a new custom config file (/usr/src/sys/amd64/conf/mykernel, for example) and stripping out the things you don't need. Less running code provides a reduced attack surface, and "don't enable it if you don't need it" is a good general policy.

Additionally, because the -RELEASE branch does not get all the security fixes backported properly like FreeBSD's documentation would lead you to believe, I recommend using the -STABLE branch if you are going to rebuild a modified or hardened version of the OS. Doing so will usually provide the majority of security fixes that were never backported... but not always.

There have been even more security fixes that land in -CURRENT and never get merged back ("MFCed") to -RELEASE or -STABLE, meaning anyone who wants all the public fixes will have to either read every commit message and manually merge those fixes back to their source tree before rebuilding, or run the bleeding edge -CURRENT branch to get them. Before you decide to do so, be aware that sometimes not even that is good enough. Sometimes security fixes are committed to their tree but not actually connected to the build, leaving everyone vulnerable for months or more.

For third party packages, how their options are defined will depend on whether you are using the ports system directly or the poudriere bulk building tool. Ports will read /etc/make.conf and poudriere will read /usr/local/etc/poudriere.d/make.conf.

If desired, duplicating the WITH_BIND_NOW, WITH_PIE, and WITH_RETPOLINE options for package builds can be accomplished with these lines:

CFLAGS+= -mretpoline
CFLAGS+= -fPIE
CXXFLAGS+= -mretpoline
CXXFLAGS+= -fPIE
LDFLAGS+= -pie
LDFLAGS+= -Wl,-zretpolineplt
LDFLAGS+= -Wl,-znow

Since these are not default options, and thus very few people test them, some ports will fail to compile. Such failures should be reported to the port maintainers and/or upstream developers. To add an exception for any failing ports, the following syntax can be used:

.if empty(.CURDIR:M*/lang/python*)
CFLAGS+= -fPIE
CXXFLAGS+= -fPIE
LDFLAGS+= -pie
.endif

Here the PIE options should be removed from the first block and placed in this if statement to exclude the python port(s) from being built with those flags. The python ports may or may not actually need this. It's just an example.

Closing

By sharing this page, I hoped to start a discussion about changing some of the default settings in FreeBSD. I think many were decided on long ago and no one thinks about them anymore. Perhaps more important than that, though, is the mindset of many FreeBSD developers - one that blatantly disregards security in favor of performance and appeasing their enterprise consumers. This must change before anything else can improve. (Some of their users like it that way though.)

The resistance from the security team to phase out legacy options makes me wonder if they should be called The Backwards Compatibility Team instead.

The FreeBSD Security Officer's mission is to protect the FreeBSD user community by keeping the community informed of bugs, exploits, popular attacks, and other risks; by acting as a liaison on behalf of the FreeBSD Project with external organizations regarding sensitive, non-public security issues; and by promoting the distribution of information needed to safely run FreeBSD systems, such as system administration and programming tips.
(From their security charter page.)

In my view, the security team of today seems to be doing the exact opposite of a number of those tasks. I'd really like to see some things re-evaluated for the safety of their userbase. Fix the problems rather than shipping poor defaults that users must clean up.

Case in point: there's a section of their "security" man page that details some of the security-related sysctls that are disabled by default. Why? Why not just fix them and give users a sane foundation to begin with?

That said, FreeBSD's history of security problems goes far beyond just poor default settings in the OS. There's a severe lack of transparency in the security team's disclosure policy too, which leaves their users vulnerable to attack for long periods of time. Many known vulnerabilities are left unpatched in FreeBSD for months on end.

Oh, did I say months? I meant years.

It seems even FreeBSD's own developers are realizing how much of a circus it is.

Even if there are public exploits on news sites and social media, FreeBSD's security team often takes their sweet time fixing them, especially in the -RELEASE branch. Too often do I see security fixes merged into -CURRENT and simply left there. Sometimes you'll get lucky and it will be merged back to -STABLE, but it's increasingly rare for the main -RELEASE branch (the one most users run) to get all of the much-needed patches in a timely manner, if ever at all.

The extra legwork of maintaining so many supported branches means that none of them ever get the developers' full attention, and you're bound to run into problems in one branch that were fixed (or never happened) in another.

On a similar note, it's increasingly common for security fixes to go in without any mention of security at all. No advisory published. Nothing. If you use the affected -RELEASE, you simply do not get the fix. You're not even told it exists.

This problem is even worse in ports, where an innocent-sounding "update to version xyz" commit message can often hide substantial security fixes... which then don't get merged back to the quarterly branch... which is the default on new installs.

But maybe that's less of an issue when the security fixes never get committed in the first place.

FreeBSD is severely lacking modern exploit mitigation techniques, only getting basic ASLR in their development branch in 2019. It's disabled by default, of course.

For comparison, OpenBSD introduced ASLR in 2003, Linux in 2005, and Windows in 2007.

A FreeBSD developer posted an experimental early version of the ASLR patch in 2016, but explains that it was to be intentionally weak (sorry, I mean "non-aggressive") by default. When the weakened ASLR was finally available to users, a few of them found that some software in the base system didn't work anymore, due to bugs or certain assumptions in the code.

"Disabling ASLR, kern.elf64.aslr.enable=0, before starting ntpd manually is a workaround, but this is not viable in the long run," writes one affected user on their mailing list.

The author of their ASLR implementation replied to this message, asking "why not?"

I think that describes FreeBSD's overall attitude towards security quite well.

Building upon this, many other (more modern) mitigation techniques don't seem to be planned as far as I can tell. As far as exploit mitigations go, FreeBSD is still stuck in the stone ages.

Network-facing services running on FreeBSD are particularly vulnerable to attack as a result of this.

Even local systems are comparatively vulnerable, thanks to the ever-present "performance first, security last" mindset:


Revision 349589
Author: mckusick
Log Message: Add a new "untrusted" option to the mount command. Its purpose is to notify the kernel that the file system is untrusted and it should use more extensive checks on the file-system's metadata before using it. This option is intended to be used when mounting file systems from untrusted media such as USB memory sticks or other externally-provided media.

I see an obvious logic error in that commit... and it's not in the code.

FreeBSD's malloc (jemalloc) is very forgiving of buggy/hostile code compared to something like otto malloc.

FreeBSD's net installer fetches the unsigned OS files as root over plaintext (FTP/HTTP) protocols.

It took FreeBSD four years to switch their arc4random algorithm from RC4 (known to be broken) to ChaCha20, even when multiple patches were presented to fix it.

There's often very little review (or none at all) of the crypto code and security fixes in general.

This has even resulted in catastrophic failures like the RNG being broken for months without anyone noticing.

Or the time when all high quality entropy sources were accidentally disabled.

Or the time when kernel code preventing shell injection was removed without any review or approval of the change.

It seems that anyone with SVN access can commit whatever they want (or, more often, what their parent company wants) without any communication with other FreeBSD developers or any code review. This "commit-then-discuss" culture usually leads to long arguments on the mailing lists, glaring security problems, and developers leaving the project over political commits. Most of this could probably be avoided if some review had taken place first.

Remember that line from the beginning of this page?

FreeBSD takes security very seriously and its developers are constantly working on making the operating system as secure as possible.
I think it's safe to say that's a big lie.

The FreeBSD Foundation sometimes gets over a million dollars in yearly donations, so you might be wondering why the project can't get any of this stuff right. Well, it seems they've got other plans for how to use your money.

Commenting on this very article, one FreeBSD developer had this to say:

[...] I’ve tried getting defaults changed, as a project committer. The reactions I’m conditioned to expect are “we don’t know if that’s safe to change or what it will break” (even though tons of users make the change for best practices); “get a ports exp-run done” which may happen, but results seem to be ignored because nobody else cares; “Please provide extremely detailed performance benchmarks” and feel like you’re expected to produce a master’s thesis on the topic; and finally, “our downstream vendors will be affected”.

So I kind of gave up on getting those changes made.
In conclusion, I can't recommend FreeBSD for any task where security matters. The OS is about as secure as a Linux box from the 1990s, and the developers seem more interested in keeping corporate donors happy than making a good operating system.

Thanks for reading. Follow me on Twitter (@blakkheim) and send your feedback.

If you found anything on this page useful, feel free to...


Addendum

Five months after this document was published, FreeBSD disabled DSA hostkey and SSHv1 server support in their bundled version of OpenSSH. This was done more than a year after upstream OpenSSH removed them.

Five months after this document was published, FreeBSD introduced minimal privilege separation into their pkg tool. Though reverted just one day after being committed, pkg privsep was revisited two months later and once again reverted in December. If you're having trouble keeping track of all the flip-flops, that means it's still going out to the internet as root.

Ten months after this document was published, FreeBSD disabled the HPN patchset in their bundled version of OpenSSH.

One year after this document was published, FreeBSD enabled some of the sysctl recommendations by default in their development branch's installer. It was reverted just one month later. Due to FreeBSD's release schedule, it wouldn't have reached the majority of their users for quite some time anyway. Users who upgrade between releases (rather than reinstalling) wouldn't have seen those options at all. When it was introduced, multiple developers on the mailing list were in favor of reverting the change. With a development community like that, it's not hard to see why little progress is ever made.

Nearly two years after this document was published, FreeBSD disabled the HPN patchset in the ports version of OpenSSH. Removal of HPN from both base and ports appears to be due to build failures rather than security considerations.

Nearly two years after this document was published, FreeBSD switched the poudriere tool's default build user from root to an unprivileged one, but left the distfile fetching operation (and others) running as root.

Four years after this document was published, FreeBSD stopped making the /root directory world-readable on new installations.