Repository navigation
v4.1.0 - A massive bugfix
·
95 commits
to main
since this release
Immutable
release. Only release title and notes can be modified.
Breaking changes
This update is a massive bugfix done with Claude. With several security issues fixed. The main advantage is that Claude fixes are easy to read and easily verifiable.
Back up your policy. After installing 4.1.0, run chsr upgrade --check to list the files to migrate, then chsr upgrade to save them. cargo xtask configure runs it for you.
I'm doing debian update shortly.
docs were reorganized. Also I took most of the time for this release to rewrite the docs by myself. Of course all fixes commits were done in few hours.
Following are Claude commits messages from 4.0.3.
v4.1.0
* tests: dosr -g nobody id is a tie in the user_group fixture
* tests: both tasks run id as nobody:nobody and only set different variables, so dosr refuses the command; -r A -t A_A selects one and runs with the nobody group
* fix: dosr refuses a command when the best tasks tie
* fix: when two tasks matched the command equally with the same privileges, the first one of the policy won; dosr now refuses with "Permission denied" and logs both tasks, as the documentation says, and the user can select one with -r and -t
* fix: only a tie between the best tasks is refused, a task needing fewer privileges still wins over tied ones, whatever the order of the tasks and of the policy files
* fix: the same task reached twice through the hierarchy plugin is not a tie
* tests: ties in any order, ties between worse tasks, selection with -t, hierarchy and policy directories
* tests: in-process fuzz targets
* tests: password_input and command_match use the fuzzing module instead of #[path] includes, command_match also checks that a match is sound
* tests: cli_args and environment call dosr in-process instead of a dosr binary, so they are coverage-guided and can run in parallel
* tests: new policy_decision, proc_stat and config_parse targets
* tests: the policies are embedded in the targets, nothing is read or written on the disk
* tests: command_match knows the globset syntax and checks that a pattern never matches a `.` or `..` component, cli_args checks that the arguments are passed as given
* chore: remove build.rs and setup_environment, no dosr binary is built anymore
* feat: fuzzing module exposing dosr parsers behind the fuzzing feature
* feat: password reading, command line, command matching, environment checks, /proc stat parsing and policy decision
* feat: the policy decision takes the content of a policy file and reads nothing from the disk
* chore: replace rpassword::fuzzing_exports
* tests: the policy decision of the module is checked on a fixture
* chore: dosr is a library crate, the binary only calls dosr_main
* chore: needed to fuzz dosr internals in-process, no behaviour change
* fix: dosr passes its arguments as they are given
* fix: the outer quotes of every argument were removed, recursively, so `dosr echo '"a"'` ran `echo a`; a leftover of the pest grammar that parsed a joined command line, the shell already removed its quoting before dosr runs
* fix: a rule of the policy can now match an argument that holds quotes, they were removed before matching
* chore: the values of -u, -r, -t, -D and -p are kept as given too; chsr still removes quotes for its own grammar
* fix: the working directory is entered as the target user
* fix: std applies Command::current_dir before the pre-exec steps, so the directory asked with -D, or forced by the policy, was entered with the rights of the user running dosr, before the switch to the target user
* fix: a pre-exec step at the new WORKDIR stage enters it after the credentials and the capabilities are set; the path is prepared before the fork, and a directory the target cannot enter is an error, like with sudo
* fix: dosr does not panic on arguments or variables that are not UTF-8
* fix: std::env::args() and std::env::vars() panicked on a value that is not UTF-8: one such variable in the environment of a user made dosr unusable for this user
* fix: an argument that is not UTF-8 is refused with an error, the policy matches text; a variable that is not UTF-8 is never passed to the command
* fix: the command line written in the logs on an error is read lossily and its control characters are escaped
* fix: `-p` without a prompt is an error instead of a panic
* fix: an unknown actor type in the policy is ignored instead of crashing dosr
* fix: `unimplemented!` was reached from the content of the policy and made dosr panic; an actor of a type that this version does not know now never matches, with a warning, and the other actors of the role still match
* fix: the matching actor of a role was logged at the info level on every run, it is now a debug message
* fix: the hierarchy plugin gives the tasks of the parent roles to the members of the senior role only
* fix: a user that holds no role was accepted, with an empty task and command, as soon as a role of the policy had parents whose command matched; dosr then went on to authenticate and log an execution that could not run
* fix: a member of the senior role never got the tasks of its parents, they were scored with the actors of the parent role
* fix: a role disabled by the separation of duties plugin still gave the tasks of its parents
* refactor: the role events are only sent for the roles that the user holds, and task_settings_as scores a task with the actors it is held through
* tests: inheritance from the parents, refusal for a user without role, a role excluded by separation of duties
* tests: the policy files read by dosr and the separation of duties plugin
* tests: find_best_exec_settings reads one file in JSON and in CBOR, chooses the best task of a policy directory and skips the files it cannot read, refuses a root or policy file, or its directory, that other users can write, and reports invalid files
* tests: the ssd plugin disables a role whose user also holds a conflicting role, accepts a list or a string, does not loop on roles excluding each other, and refuses an invalid value
* chore: bump to 4.1.0
* chore: 4.0.4 (Not found) was never released, the policy migration goes from 4.0.3 to 4.1.0
* fix: the `**` command pattern of the policy matches any binary
* fix: `**` matched without giving the path to run, so a task with `"commands": ["**"]` was never chosen; the command of the user is now resolved like with the `all` default behavior, a name only in PATH
* chore: resolve_any_command moves to the command matching module, which the fuzz targets include on its own
* tests: `**` runs the command of the user, a name not in PATH is refused, an exact command is still a better match
* chore: globset replaces glob, and wildcard command paths now match
* fix: a command path with a wildcard in the policy never matched: the pattern had to end with the command path, was wrapped in `^...$`, and a match was never marked as matching
* feat: command paths of the policy are globset patterns: `*`, `?` and `[...]` stay within one path component, `**` crosses them, `{a,b}` is an alternative; a command path with a `.` or `..` component never matches a pattern, so `/usr/bin/**` cannot match `/usr/bin/../../tmp/x`
* chore: the landlock plugin expands its paths one component at a time with globset, a missing path is still skipped; `**` only matches one component there, a rule already applies to the whole hierarchy beneath a directory
* chore: dosr and rootasrole-core do not depend on the unmaintained glob crate anymore, only the xtask does; globset and bstr are trusted in cargo-vet like the other crates of their publisher
* chore: the regex crate replaces pcre2
* chore: regexes on command arguments use the regex crate, which matches in linear time: a pattern of the policy cannot make the matching of the user's arguments explode; lookarounds and backreferences are not supported anymore
* chore: the PAM password prompt is recognized by comparing text, without regex, and a username is never used as a pattern
* chore: dosr does not depend on libpcre2 anymore: removed from the Arch package, the xtask installer and the cargo-vet trusted crates; the pcre2 feature is kept as an alias of the regex feature
* chore: the fuzz crate follows the change
* chore: the hashchecker plugin is deprecated
* chore: dosr logs once per run that the plugin is deprecated and removed in 5.0, the file can change between its check and its execution; the IPE LSM (fs-verity or dm-verity) restricts executed files from the kernel
* chore: the plugin keeps working until then
* fix: only the directory the command runs in is checked against the allowed workdirs
* fix: a task forcing its working directory (`"workdir": "/path"` or a fallback) was refused unless the current directory of the user was also allowed, so the short form could never be used
* fix: with -D, the current directory of the user was checked too, although the command runs in the requested directory
* tests: a forced working directory is not checked, the current directory still is when nothing forces another one
* refactor: the allowed working directories are decided once, in rootasrole-core
* refactor: database::workdir merges the workdir options of every level (ResolvedWorkdir) with the same inheritance rule as the env and path lists, and decides whether a directory is allowed; dosr uses it through the WorkdirLevel trait
* fix: a level setting the allowlist or blacklist mode erased the inherited lists and fallback, so a directory removed by an upper level could be allowed again
* chore: the forced working directory and the allowed directories come from the same resolution, they were computed by two different functions
* refactor: the PATH of a command is decided once, in rootasrole-core
* refactor: database::path merges the path options of every level (ResolvedPath) with the same inheritance rule as the env lists, then computes the PATH; dosr and chsr both use it through the PathLevel trait
* fix: in dosr, a level setting the delete mode without an add list emptied the PATH, it now inherits the directories of the upper levels
* fix: in dosr, a keep-safe or keep-unsafe level ignored its own add and sub lists, so its sub list did not remove the directories of the user
* fix: in keep mode, the directories of the policy now come before those of the user, so they are found first
* fix: chsr showed a third resolution, where an inheriting level could not remove a directory already added
* fix: a TZ value with a '..' element after another dot was accepted
* fix: the '..' check looked at the first dot of the value instead of the current one, so a checked TZ such as `Etc.x/../../etc/shadow` was passed to the command; every path element is now compared with '..'
* refactor: the environment of a command is decided once, in rootasrole-core
* refactor: database::env merges the env options of every level (ResolvedEnv) and filters the user's variables; dosr and chsr both use it through the EnvLevel trait, implemented for their own option types
* fix: chsr showed resolved env lists that dosr did not apply: a variable in both the check and delete lists was hidden from both, and a requested behavior was shown even when the policy forbids overriding it
* fix: when the policy allowed overriding the env behavior, a command line filter without -E (such as -r) kept the built-in default behavior and ignored the global, role and task levels
* tests: the filtering rules are tested once, in rootasrole-core
* fix: CBOR commands are resolved in the PATH of their own task
* fix: with CBOR, commands were matched while the policy was read, with the default PATH, because the options of a task and of its role are written after its commands: the PATH of the task was ignored, a command outside of it was allowed
* chore: commands are stored and matched once the options are known, like with JSON; the parse-time matcher (DCommandListDeserializer, DCommandDeserializer), its PATH plumbing, DTaskFinder::final_path and the unused DPathOptions::union, calc_path and default_path are removed
* perf: with a 10000-task policy shaped like RaR-perf's confgen, finding the task takes 20 ms with CBOR and 26 ms with JSON (2.0 and 2.6 µs per task), sudo's testsudoers takes 32 ms (3.1 µs per task)
* tests: JSON and CBOR policies give the same result when the PATH of the task does not contain the command
* fix: a task cannot change how the next tasks are compared
* fix: the best settings were changed while a task was evaluated, before knowing if it was better: a losing task replaced the command to run and the command score, so a worse task could win afterwards
* fix: a task with the same command match as the best one was never compared, the first one won even when it needed more privileges
* fix: a denied command only denied its task when it matched better than the best task so far, and it replaced the best command
* tests: the best task is the same in every order of the tasks, from a policy deserialized like dosr does
* perf: dosr borrows the options of the policy instead of copying them for every task
* perf: BorrowedOptStack keeps references to the global, role and task options, it deep-copied the three of them for every task evaluated; with 500 tasks, finding the best task takes 1.7 ms instead of 6.3 ms
* chore: DLinkedRole and DLinkedTask give references that live as long as the policy, not as long as the link, so the stack can keep them
* chore: the stack has two lifetimes because Opt is invariant (Cow<'a, [Cow<'a, str>]> in DPathOptions)
* fix: commands are resolved in the PATH of their own task
* fix: dosr resolved the commands of a task with the PATH of the global options and of the best task found so far, so a command could be found in a directory that the task's PATH does not contain, or not found in one that it does
* perf: this PATH is computed once per task, it was computed up to three times and copied once more for the best task
* perf: dosr borrows the env list entries instead of copying them
* perf: the env lists computed from the defaults and the policy levels borrow their entries, they were all copied into new strings
* feat: the 4.0.4 migration keeps delete mode levels as strict as they were
* feat: a level with `"default": "delete"` that does not define its keep or check list gets an empty one, since inheriting them would pass more variables than in 4.0.3
* feat: keep mode levels are not changed, inheriting the delete and check lists only removes or checks more variables
* fix: an env list that a level does not define is inherited, even when the level sets its default behavior
* fix: a level switching to keep mode without a delete list passed every variable, it now inherits the delete and check lists of the upper levels and of the defaults
* feat: a defined list replaces the inherited one, only an empty list (`[]`) removes it, in dosr and in rootasrole-core
* docs: SEnvOptions explains the filtering modes and why inheriting undefined lists is the fail-safe choice
* chore: the shell function rule is defined in rootasrole-core
* chore: `is_shell_function` decides that a value starting with `()` is never passed to a command, dosr uses it instead of its own check
* chore: run chsr upgrade when RootAsRole is upgraded
* chore: the Arch post_upgrade, the RH postinst and `cargo xtask configure` upgrade an existing policy, and only warn when it fails
* feat: chsr upgrade saves the policy files migrated from an older version
* feat: `chsr upgrade` writes only the files migrated when they were loaded, and prints each file with its version and the changed values, such as `/options/env/check/0: "LC_.*" -> "LC_*"`
* feat: `chsr upgrade --check` writes nothing and fails when a file must be upgraded
* feat: a file that cannot be upgraded, or a policy file of a directory that cannot be loaded, is reported and makes the command fail
* feat: rootasrole-core keeps the migration applied to each loaded file and the policy files it could not load
* fix: chsr exits with a failure status when a command fails
* fix: errors were only logged and chsr exited with 0, so scripts could not detect a failed command or a usage error
* feat: migrate env regexes of policies saved before 4.0.4 to wildcards
* feat: policy documents are migrated before being deserialized, with the Migration type on the raw document: `LC_.*` becomes `LC_*`, anchors are dropped, other regexes are kept and rejected with an explicit error
* feat: a global delete list equal to the 4.0.3 default is replaced by the new default, a list written by the administrator is kept
* fix: chain unchanged migrations from 4.0.0, a policy saved by an older 4.0.x version failed to load in chsr with "No migration path"
* feat: downgrade migration back to 4.0.3 turns `*` into `.*` and restores the 4.0.3 default delete list
* chore: bump to 4.0.4
* fix: an invalid entry silently dropped the whole env or path list
* fix: lhs_deserialize_envkey and lhs_deserialize turned any error into None, so one invalid entry removed the entire keep/check/delete (or path) list, and chsr saved it that way
* fix: the error is now reported with the invalid entry
feat: environment lists use `*` wildcards instead of regexes
* feat: keep, check and delete entries are variable names where `*` matches any characters, like sudo (`LC_*`, `*_PROXY`)
* fix: dosr silently dropped invalid env entries, a task inheriting one is now denied with an error naming the entry
* fix: chsr and rootasrole-core refuse invalid entries such as regexes (`LC_.*`) with an explicit message
* chore: RAR_ENV_CHECK_LIST and RAR_ENV_DELETE_LIST defaults use `*` (LC_*, LD_*)
* chore: rootasrole-core does not depend on pcre2 anymore, the feature is kept for compatibility; its tests pass without it
* feat: log every granted execution
* feat: user, tty, working directory, role, task, target user, groups, capabilities and command are logged before executing, like sudo
* fix: control characters are escaped so an argument cannot forge another log line
* fix: dosr refuses policy files that other users can write
* fix: the main settings file, the policy file and their directories are refused when writable by others, a warning is logged when others can read them
* fix: the owner is not checked, root is not trusted more than any other account
* fix: xtask and the Arch install script create new policy files with mode 0600
* fix: the Arch install script always exited with an error after copying the default policy (`cp || log && exit 1`)
* fix: environment filtering follows sudo's rules
* fix: in keep mode, the default delete list removes the dynamic loader, shell and interpreter variables (LD_*, BASH_ENV, IFS, PYTHONPATH...), it had only 6 entries
* fix: values starting with `()` (exported shell functions) are never passed
* fix: a variable in both keep and check lists is checked, in keep mode the delete list wins over the check list
* fix: env patterns came from the user's variable name instead of the policy, so LC_.* never matched LC_ALL
* fix: a command name is never resolved in the current directory
* fix: with a task allowing every command, `dosr ls` ran ./ls if it existed, it is now only searched in PATH
* fix: relative PATH entries are ignored when resolving commands
* fix: an explicit command path is not canonicalized anymore, multi-call binaries keep the name they are run with
* chore: allow undocumented unsafe blocks in the remaining test modules
* chore: missed by 5707c19, clippy only lints them with -p rootasrole-core or -p rootasrole-exec
* fix: without pcre2, an env pattern matches nothing instead of everything
* fix: unreachable today since no pattern can be created without pcre2, kept fail-closed for the future
* chore: the comment of is_regex said the opposite of the code
v4.0.3
* chore: bump to 4.0.3
* chore: deprecate privileged file helpers that follow symbolic links
* chore: create_with_privileges, remove_with_privileges and create_dir_all_with_privileges are no longer used
* chore: document every unsafe block with a SAFETY comment
* chore: enable clippy::undocumented_unsafe_blocks, test modules are allowed like in sudo-rs
* chore: SAFETY comments of cutils.rs, securemem.rs and rpassword.rs are taken from sudo-rs
* fix: the monitor process exits with _exit to not run the atexit handlers of the parent
* fix: plugin API created aliasing mutable references on nested notifications
* fix: the hierarchy plugin notifies events while its callback runs, the API is now a RefCell borrowed immutably during notifications
* fix: raise CAP_FOWNER to change file flags whenever the file is not owned by the user
* fix: pre-exec file descriptors and capabilities errors
* fix: mark file descriptors above stderr close-on-exec with close_range, fallback on /proc/self/fd
* fix: an error while handling file descriptors now aborts the execution instead of being ignored
* fix: setting capabilities returns an error instead of panicking in the child
* fix: bind timestamp cookies to the controlling terminal and process start times
* fix: the tty is the controlling terminal, not the device behind stdout
* fix: tty cookies are bound to the session leader and ppid cookies to the parent, both with their start time, so a recycled pid does not match
* fix: without a controlling terminal, tty cookies fall back to the parent process instead of being valid for every process of the user
* fix: cookie times use CLOCK_BOOTTIME, cookies from the future are rejected
* fix: secure timestamp cookie storage
* fix: the timestamp directory and cookie files are created owned by root, opened without following symbolic links and checked before use
* fix: insecure cookie files left by previous versions are replaced
* fix: replace the racy lockfile by a flock on the cookie file
* fix: failing to save a cookie no longer aborts the session
* chore: remove RAR_MAX_LOCKFILE_RETRIES and RAR_LOCKFILE_RETRY_INTERVAL
* fix: remove forgotten debug logging of session I/O to /tmp/dosr-session.log
* chore: SimpleFileLogger creates its file with mode 0600 and never follows symlinks