I recently realized that Apple had added some new payload keys to the firewall payload (com.apple.security.firewall) with macOS Monterey 12.

EnableLogging and LoggingOption were added in macOS 12 while AllowSigned and AllowSignedApp were added in macOS 12.3

The first thing I noticed was the LoggingOption key, with 3 possible values – throttled, brief, and detail. Curious to know what each of these options meant (because of course they aren’t documented anywhere), I decided to try and enable each one and see what the output looked like.

What a rabbit hole that turned into.

Problem 1: Finding the Logs

The unified logging system is available in iOS 10.0 and later, macOS 10.12 and later, tvOS 10.0 and later, and watchOS 3.0 and later. This system supersedes the Apple System Logger (ASL) and Syslog APIs.

https://developer.apple.com/documentation/os/logging

By now it’s no secret that _most_ logging in macOS is now going through the unified logging system. The most notable exception is still /var/log/install.log (for now). For this reason, and probably just pure muscle memory at this point, I decided to check /var/log first. Two filenames instantly caught my attention

-rw-r--r--   1 root wheel     0B Jan 24 19:84 alf.log
-rw-r--r--   1 root wheel     0B Jan 24 19:84 appfirewall.log

But of course, they’re both 0B – empty.

As expected, despite still having an entry in /etc/asl.conf, the Apple System Logger (ASL) is no longer shipping logs to /var/log/appfirewall.log (or /var/log/alf.log for that matter).

# Snippet from /etc/asl.conf
# Facility com.apple.alf.logging gets saved in appfirewall.log
? [= Facility com.apple.alf.logging] file appfirewall.log file_max=5M all_max=50M

Logging with a stream

MacAdmins attempting to find the correct log in a stream full of logs

Without knowing the subsystem that the ALF would be logging to, I blindly started with a log stream command, and what a mistake that was. I was instantly overwhelmed with logs spewing at me faster than could ever be considered useful. In fact, I have created two new aliases that more accurately reflect the command.

alias firehose='log stream'
alias WHARRGARBL='log stream'

After that brief lapse in judgement, I started with the daemon I knew ALF was running as – socketfilterfw

log stream --level info --predicate 'process == "socketfilterfw"'

At first nothing was showing up, so I needed to force the ALF to produce a log. The quickest thing I could think of was to spin up a simple HTTP server and connect to it.

python3 -m http.server

Success! Only… not.

1984-01-24 19:84:42.004200-0400  localhost socketfilterfw[33534]: [com.apple.alf:] <private>

While we aren’t able to see any useful output from the unified logging system (yet), we did learn what subsystem ALF is logging as – com.apple.alf – which allows us to narrow down our log predicate if we want.

log stream --level info --predicate 'subsystem == "com.apple.alf"'

Problem 2: <private> in logs

The unified logging system uses privacy options to hide or show interpolated variables in a message. By default, the system doesn’t redact integer, floating-point and Boolean values, but it does redact the contents of dynamic strings and complex dynamic objects.

Redact Sensitive User Data from a Log Message – https://developer.apple.com/documentation/os/logging/generating_log_messages_from_your_code#3665948

Apple is either explicitly choosing to keep the output of these logs as private, or (more likely) they haven’t updated the source code for socketfilterfw to be aware of the new os_log %{public}s syntax. Either way, by default, we do not see any useful information output to the unified logging system. They are all replaced by the ever useful <private> placeholder.

socketfilterfw: [com.apple.alf:] <private>

Enable-Private-Data

The workaround for this is to enable private data for the ALF logging subsystem. This is accomplished by installing a signed profile with a SystemLogging (com.apple.system.logging) payload. If the profile is not signed, it will not work.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"&gt;
<plist version="1.0">
<dict>
<key>PayloadContent</key>
<array>
<dict>
<key>PayloadDisplayName</key>
<string>Enable ALF unified logging system private data</string>
<key>PayloadIdentifier</key>
<string>com.apple.system.logging.enable-private-data.alf.dc1396d9-8cf0-4bd8-b917-2eb93fae6a35</string>
<key>PayloadType</key>
<string>com.apple.system.logging</string>
<key>PayloadUUID</key>
<string>dc1396d9-8cf0-4bd8-b917-2eb93fae6a35</string>
<key>PayloadVersion</key>
<integer>1</integer>
<key>Subsystems</key>
<dict>
<key>com.apple.alf</key>
<dict>
<key>DEFAULT-OPTIONS</key>
<dict>
<key>Enable-Private-Data</key>
<true/>
</dict>
</dict>
</dict>
</dict>
</array>
<key>PayloadDescription</key>
<string>Enable ALF unified logging system private data</string>
<key>PayloadDisplayName</key>
<string>Enable Logging (com.apple.alf)</string>
<key>PayloadIdentifier</key>
<string>com.apple.system.logging.enable-private-data.alf</string>
<key>PayloadRemovalDisallowed</key>
<false/>
<key>PayloadScope</key>
<string>System</string>
<key>PayloadType</key>
<string>Configuration</string>
<key>PayloadUUID</key>
<string>db2352fc-d6d4-4fc6-9f99-f46e929db719</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</plist>

Hat tip to Allen Golbig (@golby) for showing me this can be done for just the subsystem and not needing to enabled it for the entire system. Original from https://github.com/usnistgov/macos_security/blob/main/includes/com.apple.alf.private_data.mobileconfig

Testing and sample log output

To get a real-world idea of what output these different logging options would generate, I decided to test launching services that would open a TCP port (LISTEN) and connect to those services from another device on the network (CONNECT).

  1. http.server with Python
    Running the command python3 -m http.server opened port 8000 and I connected using curl from another Mac, generating the Python logs below
  2. SSH
    Enabled Remote Login and connected from another Mac.
# /usr/libexec/ApplicationFirewall/socketfilterfw --setloggingopt throttled
Python: Allow TCP LISTEN  (in:0 out:1)
Python: Allow TCP CONNECT (in:5 out:0)
sshd-keygen-wrapper: Allow TCP CONNECT (in:1 out:0)

# /usr/libexec/ApplicationFirewall/socketfilterfw --setloggingopt brief
Python is listening from (TCP)
Allow Python connecting from (TCP)
Allow sshd-keygen-wrapper connecting from (TCP)

# /usr/libexec/ApplicationFirewall/socketfilterfw --setloggingopt detail
# proto=6 - 6 is the assigned protocol number for TCP
Python is listening from 0.0.0.0:8000 proto=6
Allow Python connecting from 192.168.84.36:60162 to port 8000 proto=6
Allow sshd-keygen-wrapper connecting from 192.168.42.84:60154 to port 22 proto=6

Probably the most interesting thing I noticed was with the throttled log option. As one might expect with an option named throttled, it appears that logging is buffered for a small time before actually producing any log out. The interesting part, however, is that rather than dumping 5 lines to the log for 5 separate connections, only 1 line (Python: Allow TCP CONNECT (in:5 out:0)) was generated and sent to the log. It appears that the in:5 is meant to denote the 5 connections I made with curl before the buffer was flushed to generate the log output.

Other than that, the output from the throttled and brief options seem pretty worthless. All 3 options are pretty worthless without the Enable-Private-Data configuration profile installed.

As always, I’m happy to discuss with others about their experiences with these arcane edges of macOS. Feel free to ping me on Twitter or in the MacAdmins Slack.

References