25 years of OpenBSD Security Tools: syslock and sysunlock
echo -n ' running syslock'; (/bin/sleep 10; /usr/local/bin/syslock -swf) &
echo -n ' running syslock'; (/bin/sleep 10; /usr/local/bin/syslock -swf) &
Posted by
Lippard
at
6/05/2026 08:00:00 AM
0
comments
Labels: OpenBSD, security, technology
I've been using and administering OpenBSD systems since 1999 (OpenBSD 2.5). During that time, I've written numerous scripts to make things easier, more automated, or more secure, or sometimes just to improve my understanding of how things work. When I started managing my home systems, I ran several Internet-exposed services on my home network (DNS, mail, web, SSH). I used djbdns, qmail, and Apache httpd at the start before switching to nsd/unbound for DNS and postfix for mail, and finally to OpenSMTPD for mail. When I got tired of excessive inbound traffic I moved my authoritative DNS to a provider while keeping an internal zone and resolvers, set up two cloud servers for mail and my public webserver. My home network became a hardened, minimal-exposure architecture that only allows Wireguard from expected sources and mail (after mutual TLS authentication with certificates) while continuing to run internal services.
Over the years I've made a number of these scripts available via my website and on Github once they were sufficiently mature, configurable, and with reasonable defaults in supplied sample configuration files. A set of them fit together in a coherent framework that I thought I'd write about in a series of blog posts for those who might be interested in either using them or learning from them. All are under BSD licenses which require no permission from me to use or rework into something else -- my intent is not to obtain a new user base that I need to support, though I'm happy to consider suggested enhancements.
The tools I will cover in these posts, while written for OpenBSD originally, also work on Linux and macOS, and likely on other BSDs. I use most on Kali and Proxmox (Debian Linux), as well as a few on macOS (which has roots in FreeBSD). All are written in perl and use OpenBSD-specific features when available (such as pledge and unveil, covered in an earlier blog post). Several use privilege separation to run as much functionality as possible as a non-privileged user.
The earliest tools, written initially between 1999 and 2004, were intended to provide log monitoring, file integrity monitoring, and to manage file system flags for making files immutable, a feature I hadn't seen widely used until macOS introduced a variant in 2015. Even among OpenBSD users, immutable flags are not commonly used and I recall early warnings that they were difficult to use because many files would have to be made immutable in order to avoid somewhat trivial bypasses.
The later-developed tools in the set I will cover, written in 2022-2024, are for file and software distribution across hosts and providing a perl interface to OpenBSD's signify cryptographic signing and verification tool for use by most of the tools in the set.
I'll note in my discussion how I've used large language models (LLMs) for security assessment, bug fixing, and enhancement on these tools, and where it has been useful and some of the obstacles I've run into along the way. I'll also describe how they work together in various ways, with a comprehensive view in the final wrap-up. Here's the planned schedule:
* June 2: this overview post
* June 5: syslock and sysunlock (initially written 2004, inspired by a much simpler tool called syslock by George Shaffer).
These tools allow you to define and manage groups of files which use BSD system and user immutable and append-only flags or Linux immutable or append-only flags. The use cases range from genuine security enhancement to error prevention.
* June 9: rsync-tools (rsync-client.pl/rsync-server.pl, initially written 2003; rrsync, a 2022 fork of the version initially written by Joe Smith in 2004 and modified by Wayne Davison, the primary developer of rsync; rsync-altroot.pl initially written 2002).
This is a collection of tools used with rsync to define sets of files to be kept synchronized between hosts, perform backups, and to place restrictions on rsync using various mechanisms.
* June 12: distribute.pl/install.pl (originally written 2022) and some extras (including Signify.pm, originally written 2024)
Two tools for distributing content (plain files, config files, certificates, and signed OpenBSD-style packages) to multiple hosts, verifying signatures and installing on the remote hosts. These are not limited to use on OpenBSD, as the install tool will work on Linux and macOS as well--so long as the content being distributed is usable on those operating systems, which includes architecture/OS-independent OpenBSD-style packages such as perl scripts--like all of the scripts in this blog series, which are available as signed OpenBSD-style packages. These are packaged with some extras (gendoas.pl with distribute.pl and pkg_info.pl with install.pl) and this blog post will also cover Signify.pm, a perl interface to OpenBSD's signify.
* June 16: reportnew (originally written 1999)
A log monitoring tool, inspired by swatch but written to support djbtools' cyclog and multilog format logs, Linux journal files, as well as BSD, Linux, and macOS process accounting logs, the latter of which may be a feature unique to this tool (I've not seen it in any other log monitoring tool).
* June 19: sigtree.pl (originally written 2000)
A file integrity monitoring tool, inspired by the original tripwire.
* June 23: Wrap-up post
Each post will discuss the origins of the tool(s), the use cases and problems solved, the security model, the limitations, and, in some cases, what might be added in the future. As each post is published, links will be added above.
If you want to skip ahead and look into these tools further, all are available on Github and on my website.
Posted by
Lippard
at
6/02/2026 08:00:00 AM
0
comments
Labels: OpenBSD, security, technology
OpenBSD 5.9 (current as of this post is 7.5) introduced the "pledge" system call and 6.4 introduced the "unveil" system call, which together provide a means of more granular control of system access by processes running on the system to enforce least privilege. When a program calls "pledge", it provides a list of categories of system calls (called "promises") that it is planning to make during the life of the running process (children have to make their own pledges and are not restricted), and attempts to make calls outside of those areas will cause the call to be blocked and the process to be killed. Additional calls to pledge cannot add new categories but it can remove them, so access can become more restrictive but not less restrictive.
"Unveil," by contrast, selectively exposes parts of the file system, by file path, with specific access, and the rest of the file system is correspondingly "veiled" or blocked from access. Successive calls to unveil can expand or override previous ones, expanding access to the file system, adding write and create permissions where there was previously read only, but only until unveil is called with no arguments, which locks the current state in place. Further attempts to call unveil after that result in a violation.
Violations of pledges or attempts to access file paths that are not unveiled show up in process accounting logs for the process with the process flags "P" or "U", respectively. (My "reportnew" log monitoring script knows how to monitor process accounting logs and can be easily set up to report on such violations.)
Perl scripts on OpenBSD can also use pledge and unveil, with two modules provided in the base operating system, "OpenBSD::Pledge" and "OpenBSD::Unveil". I've been adding this functionality to several of my commonly used scripts and have learned a few tips that I'd like to share.
Pledge:
* Check your call to pledge for errors. If you typo the name of a promise (category of system calls), or you provide pledge with a string of comma separated promises instead of an array or list, it will fail and nothing will be pledged.
* If you don't have any idea what promises are required, just use "error". With the error promise, instead of blocking the system call and killing the process, the result is logged to /var/log/messages and you can see what promises are required.
* The "stdio" promise is always included with OpenBSD::Pledge, so you don't need to list it.
* The "unveil" promise is required if you intend to use OpenBSD::Unveil.
* Calls to exec or system require the "proc" and "exec" promises; the new processes created as a result are not restricted and need to make their own use of pledge and unveil. (Note: this means that if you are calling a system command that writes to a file, but your script doesn't otherwise write to files, you do not need to pledge the "wpath" promise in your script.)
* If you otherwise fork a child process (e.g., explicitly using "fork" or Parallel::ForkManager or implicitly forking a child process using "open" to read from or write to a command), the promises pledged by the parent process are carried over to the child, which can then restrict them further. (Hat tip to Bryan Steele, who pointed this out on Bluesky without specifically referring to the Perl context.)
* If you use the DBI perl module with mariadb and are accessing a database through a named pipe on the same server, you'll need to pledge the "unix", "inet", and "prot_exec" promises. (This works even from a chroot jail if the named pipe or socket is a hard link from the jail.)
* This isn't a tip, but an observation: if you promise "proc" but not "exec," your system call will fail but your process will not be killed and the script will continue running.
Unveil:
* If you make use of other perl modules in your code with "use", they are loaded prior to your call to unveil and so you don't need to unveil directories like /usr/libdata/perl5 in order to use them. The exception is perl modules that include compiled shared objects (".so"), or which use "require" on other modules (loading them at runtime), in which case you do need unveil such directories, but only with "r" permission.
* If you use the DBI perl module with mariadb, you will need to unveil /var/run/mysql with "rw" and /usr/lib and /usr/local/lib with "rx".
* If you use calls to "system" or "open" which use pipes, globs, or file redirection, you need to unveil "/bin/sh" with "x" permission. You may be able to rewrite your code to avoid the requirement--can you call "system" with a command name and list of arguments rather than a string, and do any processing you need in your program instead of with the shell?
* If you use calls to "system" to execute system commands, you need to unveil them with "x" permission but in most cases you don't need to include "r".
* It is often much easier to unveil a directory rather than individual files; if you plan to check for the existence of a file and then create it if it doesn't exist, you need "rwc" on the containing directory.
* One of the biggest challenges sometimes is to find the source of an unveil violation; unveiling "/" with various permissions to see if it goes away, and then removing that and testing individual directories under the root directory in trial and error can help find things. That's how I first found the need to unveil "/bin/sh".
Finally, if you are writing perl modules it's helpful to document which promises need to be pledged and files and directories need to be unveiled in the calling scripts in order for them to function. It would be inappropriate to pledge or unveil within the module except in a context like a forked child process. I've done this with my Signify.pm wrapper for the OpenBSD "signify" command for signing and verifying files with detached signatures or gzip archives with embedded signatures in the gzip header comments.
If you've made use of pledge and unveil--in perl scripts or otherwise--what lessons have you learned?
[UPDATE: 8 February 2026]: Note that OpenBSD::Pledge still does not support execpromises, so you cannot place pledge's execpromises restrictions on anything executed with "system" or "exec."]
[UPDATE 1 April 2026]: The "tmppath" promise is being removed in favor of just using "unveil" for any permissions limitations on files in /tmp. Note that if permission is granted to a symbolic link, it also applies to the target of the link, so granting "rwc" to /tmp opens potential bypass of unveil restrictions via symlinks in /tmp.
[UPDATE 14 May 2026]: Even though neither pledge nor unveil are inherited across system calls, it is possible for an error in a perl script to cause unveil errors in another process invoked via system call -- not because of its own unveil environment, but by messing with low-numbered file descriptors (e.g., by messing with STDOUT or STDERR in an inappropriate way), which I found by accident while modifying a script to use direct calls to ping instead of using Net::Ping, and suddenly saw unveil violations from ping processes run by the _ping user. Specifically, such an error can cause libc to attempt a path-resolution write inside ping, which fails because ping unveils / with r for read-only permission.
[UPDATE: 6 June 2026]: If you pledge 'fattr' but not 'chown' in a script that uses Archive::Tar to extract files, it will generate an error when you extract a file with a group that root is not a member of, as this is a restriction even on root for 'fattr' (but not 'chown'). This was mystifying for a while because the chown error (which doesn't include the "Operation not permitted" message) came from Archive::Tar but everything still ended up in the correct state because the group ID was inherited from the containing directory. It was further complicated since I was locking and unlocking immutable flags (which was working just fine!) and it first seemed like the most likely culprit.
Posted by
Lippard
at
8/04/2024 10:26:00 AM
0
comments
Labels: cybersecurity, OpenBSD, security, technology
Unlike ChatGPT, Bard pretty much nailed it on the initial prompt. (However, Bard refused to answer my questions about the Mars Effect like the ones I gave to ChatGPT, even when reworded to be clear I was asking for descriptions of what the effect is and what explanations are offered rather than asking it to identify causes itself.)
It does lose points for grammar in the second sentence, though!
write a few paragraphs explaining immutable flags in OpenBSD including the commands for setting them and the difference between system and user immutable flagsSure. Here is a few paragraphs explaining immutable flags in OpenBSD, including the commands for setting them and the difference between system and user immutable flags.
Immutable Flags
Immutable flags are a security feature in OpenBSD that prevent files from being modified, moved, or deleted. They can be set on any file by the superuser, or on files owned by the current user with the chflags command.
To set the immutable flag on a file, use the following command:
chflags schg filename
To unset the immutable flag, use the following command:
chflags nouchg filename
System and User Immutable Flags
There are two types of immutable flags: system immutable and user immutable. System immutable flags are set by the superuser and can only be cleared by the superuser at security level 0 or -1. User immutable flags are set by the owner of the file and can be cleared by the owner of the file at any time.
Commands for Setting Immutable Flags
The following commands can be used to set immutable flags:
chflags schg filename: Sets the system immutable flag on filename.chflags uappnd filename: Sets the user append-only flag on filename.Difference Between System and User Immutable Flags
The main difference between system and user immutable flags is that system immutable flags can only be cleared by the superuser at security level 0 or -1, while user immutable flags can be cleared by the owner of the file at any time.
Example
The following example shows how to set the immutable flag on a file and then clear it:
# Set the immutable flag on the file /etc/passwd
chflags schg /etc/passwd
# Clear the immutable flag on the file /etc/passwd
chflags nouchg /etc/passwd
Benefits of Using Immutable Flags
Immutable flags can be used to improve the security of your system by preventing unauthorized changes to critical files. For example, you could use immutable flags to protect system configuration files, such as /etc/passwd and /etc/shadow, from being modified by attackers.
Disadvantages of Using Immutable Flags
There are a few disadvantages to using immutable flags. First, they can make it difficult to troubleshoot problems with your system. If a file that is set to immutable is corrupted, you will not be able to modify it to fix the problem. Second, immutable flags can slow down your system performance, especially if a large number of files are set to immutable.
Conclusion
Immutable flags are a powerful security feature that can be used to protect your system from unauthorized changes. However, it is important to weigh the benefits and disadvantages of using immutable flags before deciding whether or not to use them on your system.
Posted by
Lippard
at
4/19/2023 02:11:00 PM
0
comments
Labels: artificial intelligence, Bard, ChatGPT, OpenBSD, technology
I was thinking about writing a blog post about file system immutability flags in OpenBSD, and thought I'd ask ChatGPT to write it for me. It appears to have gleaned its content from OpenBSD man pages, but it is highly misleading and inaccurate, misrepresenting the key distinction between system and user immutability flags.
What it got right is that only the superuser (root) can set and unset the system immutability flag (schg/noschg) while users (if they own the file) can set and unset the user immutability flag (uchg/nouchg). But either flag can be set or unset on any kind of file.
The user immutability flag can be unset (nouchg) by either the superuser or the owner of the file regardless of the system security run level (-1 to 2), but the system immutability flag can only be unset (noschg) by the superuser when the system security run level is 0 (insecure mode) or -1 (permanently insecure mode).
During boot, the system normally starts in single-user mode at security run level 0. The init process raises the run level to 1 (secure mode) when booting to multi-user mode, and to 2 (highly secure mode) if the rc.securelevel config file so specifies. The system secure level can also be raised if the superuser changes it with the sysctl command, setting the kern.securelevel variable. The permanently insecure mode (-1) can be set with sysctl only while in insecure mode, which will cause the init process to not raise the security level to 1 during boot to multi-user mode.
The man page for securelevel gives an overview of the restrictions at each security level. I run my systems at secure level 2, highly secure mode. Secure mode (secure level = 1) and above prevent lowering the secure level except by the init process (when the system is shutdown back to single user mode), do not allow /dev/mem or /dev/kmem to be opened, make raw disk devices read only, and disallow unsetting of system immutability flags (or unsetting the related system append-only flags, sappnd, using nosappnd), along with a few other restrictions. Highly secure mode (securelevel = 2) also prevents setting the time backward or close to overflow, and prevents changing host firewall rules (pf filter and NAT rules), though IP addresses may still be added or removed from tables. The most significant difference between the system and user immutability flags is not what files they are set on, but that the user immutability flags can be turned off at any time by the file owner or superuser, while the system immutability flags can only be changed while in insecure modes, i.e., after a shutdown from multi-user operation.
I use system file immutability flags on most configuration files, system binaries, and system libraries as an obstacle to an attacker in the event of a compromise and escalation of privileges. If an attacker has console access to a system, they can bypass these flags by shutting down the system and unsetting the flags. If I left certain config files without file system immutability flags set, an attacker could modify one of those config files that leads to execution during system startup to insert commands to unset the flags, and then reboot the system so that those commands execute while the system is in insecure mode.
These flags can get in the way and cause problems without an efficient way to manage them, as they need to be unset in order to do system patches and updates--it's bad news if you try to do a system upgrade while many files are set with immutability flags. They also block features like OpenBSD's kernel address randomized link (KARL) and library reordering address space layout randomization (ASLR) features, which relink the kernel and certain key libraries on reboot to make blind return-oriented programming (BROP) attacks more difficult. I use scripts called syslock/sysunlock to manage the immutability flags. These scripts have a config file (kept immutable, of course) that specifies which files and directory contents to set immutable, and defines groups so that I can unlock what I need at a particular time with a single command. When I want to allow KARL and library ASLR to occur, I do a sysunlock -s (unlock system files) while in single user mode and then boot. I've also defined groups for things like "everything in /etc", "everything related to DNS", etc.
My rc.securelevel config file contains a command to sleep for a period of time, then do a syslock -s after raising the system security level, giving the system time to do the relinking, so I don't need to issue a command to re-lock those files. This is a process I also follow when installing kernel patches, as syspatch fails to do kernel relinking when in single-user mode. That is, I boot to single-user mode, unlock the system files with "sysunlock -s", then exit back to multi-user mode and the KARL process (/usr/libexec/reorder_kernel) and library ASLR (part of /etc/rc) run without error.
syslock/sysunlock may be found at https://www.discord.org/lippard/software/ and on Github at https://github.com/lippard661/syslock
Anyway, after that long introduction, here's what ChatGPT had to say about all this; my prompts are in red:
write a few paragraphs explaining immutable flags in OpenBSD including the commands for setting them and the difference between system and user immutable flags
In OpenBSD, immutable flags are used to protect files from being modified or deleted. These flags can be set on both system and user files, and are useful for preventing accidental or malicious changes to important system files.
To set an immutable flag on a file, the "chflags" command can be used. The command takes the form "chflags immutable [filename]" and can be used to set the flag on a single file or multiple files at once. To remove the immutable flag, the command "chflags noschg [filename]" can be used.
The difference between system and user immutable flags is the level of protection they provide. System immutable flags are set on system files and are only modifiable by the superuser. User immutable flags are set on user files and can be modified by the owner of the file. This allows users to protect their own files, while also allowing the system administrator to protect important system files.
It's important to note that while immutable flags provide a level of protection, they should not be considered as a replacement for proper file permissions and security practices. It's still important to use appropriate permissions to control access to files and to keep your system updated to protect against known vulnerabilities.
Can I set the user immutable flag on a system file or the system immutable flag on a user file?
In OpenBSD, the system immutable flag can only be set on system files, and the user immutable flag can only be set on user files. Attempting to set a user immutable flag on a system file or a system immutable flag on a user file will result in an error.
This is because the system immutable flag is intended for use on system files that should not be modified by anyone other than the superuser. Setting this flag on a user file would not provide any additional security, as the user already has full control over their own files. Similarly, the user immutable flag is intended for use on user files that should not be modified by the owner. Setting this flag on a system file would not provide any additional security, as the superuser already has the ability to modify system files.
It's important to use the appropriate immutable flag for the type of file you are working with to ensure that the file is protected as intended.
[UPDATE, 22 Jan 2023:]
That is incorrect. The system immutable flag can be set on user files and the user immutable flag can be set on system files. The difference is not what files they can be used on, but that the system immutable flag can only be unset by the superuser when the system is in insecure mode, while the user immutable flag can be unset by the file owner or superuser at any system security run level.
I apologize, you are correct. The system immutable flag can be set on user files, and the user immutable flag can be set on system files. The difference between the two flags is not the type of file they can be applied to, but rather the level of protection they provide and who can unset them.
The system immutable flag provides a higher level of protection and can only be unset by the superuser when the system is in insecure mode, This means that once the system flag is set, the file cannot be modified or deleted without booting in insecure mode.
On the other hand, the user immutable flag can be unset by the file owner or the superuser at any system security run level. This means that the file can be modified or deleted by the owner of the file or superuser at any time.
Again, I apologize for the confusion in my previous answer. Thank you for bringing this to my attention.
Posted by
Lippard
at
1/21/2023 11:19:00 AM
0
comments
Labels: artificial intelligence, ChatGPT, cybersecurity, OpenBSD, security
Posted by
Lippard
at
11/23/2015 08:37:00 AM
0
comments
Labels: OpenBSD, security, technology
script-security 2
tmp-dir <path to dir writable only by _openvpn user>
auth-user-pass-verify /usr/local/libexec/openvpn_bsdauth via-file
openssl rand -hex 20 > ~/.totp-key
#!/usr/bin/perl
use Convert::Base32;
open (FILE, "/home/vpnuser/.totp-key");
$secret = <FILE>;
close (FILE);
$code = pack ('H*', $secret);
print encode_base32($code)."\n";
:auth=-totp,passwd:\
:tc=default:
Match User <vpnuser>
PasswordAuthentication yes
AuthenticationMethods publickey,password:bsdauth
Posted by
Lippard
at
10/30/2013 05:44:00 PM
1 comments
Labels: OpenBSD, security, technology