FreeBSD 13 and W^X memory mapping policy for user processes
FreeBSD 13.0-RELEASE is out, and one of the new features is W^X memory mapping policy for user processes. This feature is also naturally available for users of 13.0-STABLE and 14.0-CURRENT.
Enable the feature, i.e. disallow pages to be executable and writable, by adding these lines to /etc/sysctl.conf
:
kern.elf32.allow_wx=0 kern.elf64.allow_wx=0
For immediate effect, run these commands as root
:
sysctl kern.elf32.allow_wx=0 sysctl kern.elf64.allow_wx=0
Some executables can’t live with these restrictions. It’s possible to mark an executable as needing its pages to be both executable and writable:
# elfctl -e +wxneeded /usr/local/bin/synth # elfctl -e +wxneeded /usr/local/lib/libreoffice/program/soffice.bin
Checking the results, reveals:
$ elfctl /usr/local/bin/synth File '/usr/local/bin/synth' features: noaslr 'Disable ASLR' is unset. noprotmax 'Disable implicit PROT_MAX' is unset. nostackgap 'Disable stack gap' is unset. wxneeded 'Requires W+X mappings' is set. la48 'amd64: Limit user VA to 48bit' is unset. noaslrstkgap 'Disable ASLR stack gap' is unset. $ elfctl /usr/local/lib/libreoffice/program/soffice.bin File '/usr/local/lib/libreoffice/program/soffice.bin' features: noaslr 'Disable ASLR' is unset. noprotmax 'Disable implicit PROT_MAX' is unset. nostackgap 'Disable stack gap' is unset. wxneeded 'Requires W+X mappings' is set. la48 'amd64: Limit user VA to 48bit' is unset. noaslrstkgap 'Disable ASLR stack gap' is unset.
Note, executables produced by lang/fpc
3.2.0 doesn’t carry the NT_FREEBSD_FEATURE_CTL
note section.
A few ports are unable to be built or live under such restrictions. So far, the list includes:
editors/libreoffice
java/openjdk8
lang/fpc
lang/mono
lang/python3x
ports-mgmt/synth
My shell script (not shown here) for building packages re-enables kern.elf64.allow_wx
, setting it to 1, allowing pages to be writable and executable, before running ports-mgmt/synth
. An exit handler in the same script will disable kern.elf64.allow_wx
, setting it to 0, disallowing pages to be writable and executable, once all packages are built.
Here’s a shell script I concocted to mark the /usr/local/bin/synth
binary as needing writable and executable pages:
#!/bin/sh #set -x set -eu -o pipefail # A quick reminder for myself: # Disable/disallow writable and executable (wx) pages system-wide: # sysctl kern.elf64.allow_wx=0 # sysctl kern.elf32.allow_wx=0 # Enable/allow writable and executable (wx) pages system-wide: # sysctl kern.elf64.allow_wx=1 # sysctl kern.elf32.allow_wx=1 ELFCTL="elfctl" GREP="grep" SYNTH="/usr/local/bin/synth" if ${ELFCTL} ${SYNTH} | ${GREP} "wxneeded" | ${GREP} -q "is set"; then echo "${0}: wxneeded is already set on ${SYNTH}." exit fi echo "${0}: elfctl(1) feature flags on ${SYNTH} before any changes:" ${ELFCTL} ${SYNTH} echo echo "${0}: Setting wxneeded on ${SYNTH} ..." ${ELFCTL} -e +wxneeded ${SYNTH} echo "... done." echo echo "${0}: elfctl(1) feature flags afterwards:" ${ELFCTL} ${SYNTH} # EOF
On a related note, if you use ASLR, then Firefox must be allowed to run without ASLR.
#!/bin/sh #set -x set -eu -o pipefail # A quick reminder for myself: # Disable aslr system-wide: # sysctl kern.elf64.aslr.enable=0 # sysctl kern.elf32.aslr.enable=0 # Enable aslr system-wide: # sysctl kern.elf64.aslr.enable=1 # sysctl kern.elf32.aslr.enable=1 ELFCTL="elfctl" GREP="grep" FIREFOX="/usr/local/lib/firefox/firefox" if ${ELFCTL} ${FIREFOX} | ${GREP} "noaslr " | ${GREP} -q "is set"; then echo "${0}: noaslr is already set on ${FIREFOX}." exit fi echo "${0}: elfctl(1) feature flags on ${FIREFOX} before any changes:" ${ELFCTL} ${FIREFOX} echo echo "${0}: Setting noaslr on ${FIREFOX} ..." ${ELFCTL} -e +noaslr ${FIREFOX} echo "... done." echo echo "${0}: elfctl(1) feature flags afterwards:" ${ELFCTL} ${FIREFOX} # EOF
MJ
I’m confused. You say to immediately enable set them to 0 in rc.conf then later in the shell script you set them to 1 to enable?
Trond Endrestøl
When these two sysctls are set to 1, we allow pages to be both writable and executable. When set to 0, we disallow pages to be both writable and executable. And it’s
/etc/sysctl.conf
, not/etc/rc.conf
.Some software can’t cope with this limitation. The Ada runtime of
ports-mgmt/synth
is one such example. The executables produced bylang/fpc
3.2.0 doesn’t even carry theNT_FREEBSD_FEATURE_CTL
note section, and such executables doesn’t like it whenW xor X = 1
.I have seen things go awry when running
make index
,make config-recursive
, andmake fetch-recursive
in the ports tree, when these two sysctls are set to 0. This happens for sure when any of the three commands above hitslang/fpc
and its subsidiaries.I reworded my blog post, and hopefully it’s more readable now.
Thank you for stopping by and leaving a comment.
Sean
It seems Let’s Encrypt’s certbot also doesn’t work, spewing:
An unexpected error occurred:
MemoryError: Cannot allocate write+execute memory for ffi.callback(). You might be running on a system that prevents this. For more information, see https://cffi.readthedocs.io/en/latest/using.html#callbacks
Trond Endrestøl
In that case, we should run
elfctl -e +wxneeded /usr/local/bin/python3.8
to mark this executable as needing write and execute.