Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

os_asl_log_files_*_configure are completely broken #350

Open
nihil-admirari opened this issue Jan 21, 2024 · 0 comments
Open

os_asl_log_files_*_configure are completely broken #350

nihil-admirari opened this issue Jan 21, 2024 · 0 comments
Assignees

Comments

@nihil-admirari
Copy link
Contributor

nihil-admirari commented Jan 21, 2024

os_asl_log_files_owner_group_configure and os_asl_log_files_permissions_configure both use the following code to collect the names of log files:

/usr/bin/grep -e '^>' /etc/asl.conf /etc/asl/* | /usr/bin/awk '{ print $2 }')

According to man asl.conf:

  1. Lines starting with > configure output options for files or directories to be referenced later by query rules. os_asl_log_files_permissions_configure sets 640 regardless, making directories non-listable.
  2. Query rules can have file and directory (store_dir is used de facto even though it is not mentioned in the man) configured inline. Above code completely ignores files configured inline.
  3. Filenames are not required to be absolute:

    If the pathname specified is not an absolute path, syslogd will treat the given path as relative to /var/log (for /etc/asl.conf), or for other output modules relative to /var/log/module/NAME where NAME is the module name.

  4. If log rotation is in use, actual filename will have a timestamp or a counter appended to it.
  5. Ownership is root:admin by default, but can be configured directly within configuration files by setting uid=0 gid=0, and similar for permissions mode=0640 (or mode 0750 for directories). Provided fixes do not modify configuration files themselves, which means that newly created log files won't have correct group and mode set.
  6. According to man asl.conf there is an additional parameter
    access     Sets read access controls for messages that match the
               associated query pattern.  syslogd will restrict read
               access to matching messages to a specific user and
               group.  The user ID number and group ID number must fol-
               low the ``access'' keyword as parameters.
    
    Files which have it, configure access to root:admin (0:80). I've no idea whether it should be hardened to root:wheel.

Examples

On my machine (Sonoma 14.2.1):

/usr/bin/grep -e '^>' /etc/asl.conf /etc/asl/* | /usr/bin/awk '{ print $2 }')

produces

/var/log/asl/Logs/aslmanager
system.log
cdscheduler.log
/var/log/com.apple.contacts.ContactsAutocomplete
/var/log/CoreDuet/coreduetd.log
/var/log/mail.log
/private/var/log/keybagd.log
/private/var/log/keybagd.log
/Library/Logs/CrashReporter
  1. /var/log/com.apple.contacts.ContactsAutocomplete
    /Library/Logs/CrashReporter
    

    are directories and should have 750 permissions, not 640.

  2. /etc/asl.conf has ? [= Facility com.apple.alf.logging] file appfirewall.log file_max=5M all_max=50M, but appfirewall.log is not mentioned in the output at all.

    /etc/asl/com.apple.MessageTracer has * store_dir /var/log/DiagnosticMessaged ttl=30, which is also missed.

    There are other missing log files beyond these two.

  3. system.log was mentioned in /etc/asl.conf, which means it's actually /var/log/system.log on disc.

    cdscheduler.log was mentioned in /etc/asl/com.apple.cdscheduler, which means it's actually /var/log//module/com.apple.cdscheduler/cdscheduler.log on disc.

  4. /var/log/asl/Logs/aslmanager has style=lcl-b and is actually stored as /var/log/asl/Logs/aslmanager.%Y%m%dT%H%M%S%z on disc. stat doesn't glob potential suffixes:

    /usr/bin/stat -f '%Su:%Sg:%N' $(/usr/bin/grep -e '^>' /etc/asl.conf /etc/asl/* | /usr/bin/awk '{ print $2 }')

Suggestions for filesystem checks

Log filenames collection:

logfiles=$(
    for f in /etc/asl{.conf,/*}; do
        if [[ $f == /etc/asl.conf ]]; then
            log_dir=/var/log/
        else
            log_dir="/var/log/module/$(basename "$f")/"
        fi

        awk -v log_dir="$log_dir" '
            function print_log(j) {
                print $j ~ "^/" ? $j : log_dir$j
            }

            $1 == ">" { print_log(2) }

            $1 ~ /^[?*]$/ {
                for (i = 2; i <= NF; ++i) {
                    if ($i ~ "^(file|directory|store_dir)$") {
                        print_log(++i)
                    }
                }
            }
        ' "$f"
    done | sort -u
)

Checking for ownership.

Notes:

  1. Uses zsh globbing, won't run in bash.
  2. awk 'END { print NR }' doesn't require xargs to remove spaces compared to wc -l.
/usr/bin/find "${(f)^logfiles}"*(N) ! -user root -or ! -group wheel \
    | /usr/bin/awk 'END { print NR }'

Fixing ownership:

/usr/bin/find "${(f)^logfiles}"*(N) ! -user root -or ! -group wheel \
    -execdir /usr/sbin/chown root:wheel {} +

Checking for permissions:

/usr/bin/find "${(f)^logfiles}"*(N) \
    \( -type d ! -perm 750 \) -or \( ! -type d ! -perm 640 \) \
| /usr/bin/awk 'END { print NR }'

Fixing permissions:

/usr/bin/find "${(f)^logfiles}"*(N) -type d ! -perm 750 \
    -execdir /bin/chmod 750 {} +
/usr/bin/find "${(f)^logfiles}"*(N) ! -type d ! -perm 640 \
    -execdir /bin/chmod 640 {} +

Suggestions for configuration file checks

All code below assumes that:

  1. Only one of file, directory, or store_dir is actually possible.
  2. Appending uid=0 gid=0 mode=0640 to the very end is correct and actually affects the file in question (rather than breaking the configuration file completely).
  3. If a file was configured with > then a reference to it from ? or * doesn't need to be configured again (unless inline overrides for uid, gid or mode are present).

Checking uid and gid in configuration files:

uid_gid_err=$(
    awk -v l=0 -v err=0 '
        function count_err(has_uid, has_gid, i) {
            has_uid = has_gid = 0
            for (i = 2; i <= NF; ++i) {
                if ($i ~ /^uid=/) {
                    ++has_uid
                    if ($i != "uid=0") { ++err }
                } else if ($i ~ /^gid=/) {
                    ++has_gid
                    if ($i != "gid=0") { ++err }
                }
            }
            if (has_uid != 0 || has_uid != 1) { ++err }
            if (has_gid != 1) { ++err }
        }

        function file_seen(j, i) {
            for (i in logfiles) {
                if (logfiles[i] == $j) {
                    return 1
                }
            }
            return 0
        }

        $1 == ">" {
            logfiles[l++] = $2
            count_err()
        }

        $1 == "?" || $1 == "*" {
            for (i = 2; i <= NF; ++i) {
                if ($i ~ /^(file|directory|store_dir)$/) {
                    if (!file_seen(++i) || $0 ~ /[[:space:]][ug]id=/) {
                        count_err()
                    }
                    break
                }
            }
        }

        END { print err }
    ' /etc/asl{.conf,/*}
)
echo "$uid_gid_err"

Fixing uid and gid in configuration files:

for conf in /etc/asl{.conf,/*}; do
    conf_content=$(< "$conf")
    awk -v l=0 '
        function fix_uid_gid() {
            gsub(/([[:space:]]+[ug]id=[[:digit:]]+)+([[:space:]]+|$)/, " ")
            sub(/[[:space:]]*$/, " uid=0 gid=0")
        }

        function file_seen(j, i) {
            for (i in logfiles) {
                if (logfiles[i] == $j) {
                    return 1
                }
            }
            return 0
        }

        $1 == ">" {
            logfiles[l++] = $2
            fix_uid_gid()
        }

        $1 == "?" || $1 == "*" {
            for (i = 2; i <= NF; ++i) {
                if ($i ~ /^(file|directory|store_dir)$/) {
                    if (!file_seen(++i) || $0 ~ /[[:space:]][ug]id=/) {
                        fix_uid_gid()
                    }
                    break
                }
            }
        }

        { print }
    ' > "$conf" <<< "$conf_content"
done

Checking mode in configuration files (cannot really know whether the file is a directory, assumes that directory already have execute permissions set):

mode_err=$(
    awk -v l=0 -v err=0 '
        function count_err(has_mode, i) {
            has_mode = 0
            for (i = 2; i <= NF; ++i) {
                if ($i ~ /^mode=/) {
                    ++has_mode
                    if ($i != "mode=0750" || $i != "mode=0640") { ++err }
                }
            }
            if (has_mode != 1) { ++err }
        }

        function file_seen(j, i) {
            for (i in logfiles) {
                if (logfiles[i] == $j) {
                    return 1
                }
            }
            return 0
        }

        $1 == ">" {
            logfiles[l++] = $2
            count_err()
        }

        $1 == "?" || $1 == "*" {
            for (i = 2; i <= NF; ++i) {
                if ($i ~ /^(file|directory|store_dir)$/) {
                    if (!file_seen(++i) || $0 ~ /[[:space:]]mode=/) {
                        count_err()
                    }
                    break
                }
            }
        }

        END { print err }
    ' /etc/asl{.conf,/*}
)

Fixing mode in configuration files:

for conf in /etc/asl{.conf,/*}; do
    conf_content=$(< "$conf")
    awk -v l=0 '
        function fix_mode(mode) {
            mode = $0 ~ /[[:space:]]mode=07/ ? " mode=0750" : " mode=0640"
            gsub(/([[:space:]]+mode=[[:digit:]]+)+([[:space:]]+|$)/, " ")
            sub(/[[:space:]]*$/, mode)
        }

        function file_seen(j, i) {
            for (i in logfiles) {
                if (logfiles[i] == $j) {
                    return 1
                }
            }
            return 0
        }

        $1 == ">" {
            logfiles[l++] = $2
            fix_mode()
        }

        $1 == "?" || $1 == "*" {
            for (i = 2; i <= NF; ++i) {
                if ($i ~ /^(file|directory|store_dir)$/) {
                    if (!file_seen(++i) || $0 ~ /[[:space:]]mode=/) {
                        fix_mode()
                    }
                    break
                }
            }
        }

        { print }
    ' > "$conf" <<< "$conf_content"
done

Changes these configuration file fixes make can be inspected by running:

for conf in /etc/asl{.conf,/*}; do
    conf_content=$(< "$conf")
    diff --color=always --unified "$conf" <(awk -v l=0 '
        COPY FIX FOR EITHER UID/GID OR MODE HERE
    ' <<< "$conf_content")
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants