Repository navigation
Conversation
* 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 Co-Authored-By: Claude
* chore: missed by 5707c19, clippy only lints them with -p rootasrole-core or -p rootasrole-exec Co-Authored-By: Claude
* 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 Co-Authored-By: Claude
* 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 Co-Authored-By: Claude
* 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`) Co-Authored-By: Claude
* 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 Co-Authored-By: Claude
* 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 Co-Authored-By: Claude
* 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 Co-Authored-By: Claude
Co-Authored-By: Claude
* 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 Co-Authored-By: Claude
* fix: errors were only logged and chsr exited with 0, so scripts could not detect a failed command or a usage error Co-Authored-By: Claude
* 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 Co-Authored-By: Claude
* chore: the Arch post_upgrade, the RH postinst and `cargo xtask configure` upgrade an existing policy, and only warn when it fails Co-Authored-By: Claude
* chore: `is_shell_function` decides that a value starting with `()` is never passed to a command, dosr uses it instead of its own check Co-Authored-By: Claude
… 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 Co-Authored-By: Claude
…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 Co-Authored-By: Claude
* perf: the env lists computed from the defaults and the policy levels borrow their entries, they were all copied into new strings Co-Authored-By: Claude
* 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 Co-Authored-By: Claude
…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) Co-Authored-By: Claude
* 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 Co-Authored-By: Claude
* 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 Co-Authored-By: Claude
…-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 Co-Authored-By: Claude
* 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 '..' Co-Authored-By: Claude
* 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 Co-Authored-By: Claude
…role-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 Co-Authored-By: Claude
…lowed 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 Co-Authored-By: Claude
* 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 Co-Authored-By: Claude
* 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 Co-Authored-By: Claude
* 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
Co-Authored-By: Claude
* 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 Co-Authored-By: Claude
* chore: 4.0.4 (Not found) was never released, the policy migration goes from 4.0.3 to 4.1.0 Co-Authored-By: Claude
* 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 Co-Authored-By: Claude
…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 Co-Authored-By: Claude
…ng 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 Co-Authored-By: Claude
* 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 Co-Authored-By: Claude
* 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 Co-Authored-By: Claude
Co-Authored-By: Claude
* 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 Co-Authored-By: Claude
* chore: needed to fuzz dosr internals in-process, no behaviour change Co-Authored-By: Claude
* 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 Co-Authored-By: Claude
* 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 Co-Authored-By: Claude
* chore: dosr is a library, so that the fuzz targets run its parsers in-process, with coverage and in parallel * tests: fuzz targets for passwords, command matching, the command line, the environment, the policy decision, /proc stat and policy files Co-Authored-By: Claude
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #107 +/- ##
==========================================
+ Coverage 68.50% 73.02% +4.52%
==========================================
Files 55 60 +5
Lines 6426 6487 +61
==========================================
+ Hits 4402 4737 +335
+ Misses 2024 1750 -274
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This is massive bugfix, and fixes also a lot of security issues. This version and the previous one are the result of co-working with Claude. This is has to be done I was completely blind about these. But then I have to compare it more or less like all other project that have thousands of bugfix from Mythos, but with Opus 5.5.
First of all, I did some mistakes about choosing a implementation for env vars management that were inevitably insecure at some point. Claude made me a proof of it.
The workdir feature weren't correctly located in the exec order. I thought that position would be a feature, but was a problem, and Claude gave me a proof.
The PATH management struggled inside the hierarchy of the configuration.
There was a logic bug in the task matching making in some cases winning a task with more privileges than the other.
TZ check algorithm is stricter than before, and so better.
RootAsRole denies policy file with other permission enabled.
RootAsRole now logs successful commands into syslogs, and also preventing forging indesirable chars into this syslog.
Several bugs with PATH management, we could have some relative path that could become "legitimate".
**is now really working as a wildcard for all binaries. Use with care ofc.Hierarchy of roles had logic bugs.
After seeing some blocking point on pcre2 on debian CI, the dependency is now dropped in favour of regex from BurntSushi, and for trust reasons, RootAsRole switched from glob lib to globset from burntsushi. This is thanks to my last cargo vet iteration.
For making this new version transparent for current policies, RootAsRole firstly uses my 2 years old implementation of Migration type... I finally have a usage of this feature lol.
And it also refactored code that was duplicate since the optimisation update.
And there many more small bugfixes. Described below from an extract of the
git logresult. You'll see that the git log is incredibly easy to read, for me at least, but is mostly Claude text.dosr echo '"a"'ranecho a; a leftover of the pest grammar that parsed a joined command line, the shell already removed its quoting before dosr runs-pwithout a prompt is an error instead of a panicunimplemented!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**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 thealldefault behavior, a name only in PATH**command pattern of the policy matches any binary^...$, and a match was never marked as matching"workdir": "/path"or a fallback) was refused unless the current directory of the user was also allowed, sothe short form could never be usedEtc.x/../../etc/shadowwas passed to the command; every path element is now compared with '..'LC_.*) with an explicit messagecp || log && exit 1)()(exported shell functions) are never passeddosr lsran ./ls if it existed, it is now only searched in PATH