Skip to content

Conversation

HW42
Copy link
Contributor

@HW42 HW42 commented Oct 14, 2025

See included README for details.

Issue: QubesOS/qubes-issues#2695

Copy link
Member

@marmarek marmarek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've looked at the PAM module implementation for now, but not much at the PAM configs. On the first sight looks like there is quite a bit of duplication in the PAM configs, but that's probably unavoidable to make it work with any authselect profile...

all: qubes-admin-authzd pam_qubes_admin_authz.so

qubes-admin-authzd: qubes-admin-authzd.c qubes-admin-authz-common.h
gcc -O2 -Wall -Wextra -Werror -fPIC -pie $< -o $@
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Include standard CFLAGS var too (both debian and fedora add preferred hardening options here)

gcc -O2 -Wall -Wextra -Werror -fPIC -pie $< -o $@

pam_qubes_admin_authz.so: pam_qubes_admin_authz.c qubes-admin-authz-common.h
gcc -O2 -Wall -Wextra -Werror -fPIC -pie -shared $< -o $@ -lpam
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and here too

dom0. For example:

```
qubes.AuthorizeInVMAdminAccess * * @default target=dom0 default_target=dom0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"ask" is missing

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. I'm wondering if we should use "VM" in the service name or change it to "qube"?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder about even simpler name like qubes.AuthorizeAdminAccess? But it might be confusing where that admin is (whether it gives dom0 admin access for example)... But OTOH that concern should be covered by the custom prompt already, right? (at least for the "ask" case)

@marmarta any opinion? Past related discussion starting at this comment

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think AuthorizeAdminAccess is ok, within context of the custom prompt.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As in, if we got a prompt that just had the service name I'm with @HW42 , but if there's a custom prompt, it's fine to omit it. If we do keep it, lets make it InQube, because from what I understand in can apply to things like remote qubes which might not be vms..

Comment on lines +34 to +36
The mode can be set via /usr/local/etc/qubes/admin-authzd.conf for per-qube
setting or more common via /etc/qubes/admin-authzd.conf in a template (setting
in /usr/local has precendce).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe make it configurable via qvm-service (too?) so it's more compatible with disposable qubes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. Since services are boolean we need 3. Any preferences regarding naming?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe root-access-deny, root-access-prompt and root-access-allow?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whonix would like for this to be configurable in Qube Manager, via a tri-select dropdown with a default setting. Is qvm-service the best tool for this job, or would something like qvm-prefs or qvm-features work better here? (I would assume a property settable via qvm-prefs and readable via qubesdb would be best.)

Comment on lines +58 to +62
if (!(strcmp(service, "su") == 0 ||
strcmp(service, "su-l") == 0 ||
strcmp(service, "sudo") == 0 ||
strcmp(service, "sudo-i") == 0 ||
strcmp(service, "polkit-1") == 0)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this necessary, instead of just adding it to only relevant PAM configuration files? If it's necessary, better have the list in the module parameter, so it can be adjusted without rebuilding (for example to use it for other services too, like doas or login).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think yes, given how Debian and Fedora structure their PAM configurations. They have common stacks (like /etc/pam.d/common-auth) and those are then included by the service specific configs (like /etc/pam.d/su). Since the later are owned by those packages they are not so easy to update, unlike the former, for with their is authselect in Fedora and pam-auth-update in Debian.

My original plan was to configure it like this:

auth [default=1 success=ignore] pam_succeed_if.so use_uid user ingroup qubes service in su:su-l:sudo:sudo-i:polkit-1
auth sufficient                 pam_qubes_admin_authz.so

This works under Debian but the PAM stack under Fedora is broken because how of how it interacts with the relative jump (in default=1) works:

jump over the next N modules in the stack. Note that N equal to 0 is not allowed, it would be treated as ignore in such case. The side effect depends on the PAM function call: for pam_authenticate, pam_acct_mgmt, pam_chauthtok, and pam_open_session it is ignore; for pam_setcred and pam_close_session it is one of ignore, ok, or bad depending on the module's return value.

This means that for non matching requests pam_setcred get a failed state for non-matching requests and this is propagated through. This even breaks login on the console!

This can be worked around by negating the logic:

auth [default=ignore success=1] pam_succeed_if.so use_uid user ingroup qubes service in su:su-l:sudo:sudo-i:polkit-1
auth [default=1]                pam_permit.so
auth sufficient                 pam_qubes_admin_authz.so

But I disliked this relatively complex jumping around.

But making it configurable via a module parameter is easy. In the end you will need to re-build the package anyway. But it would be easier for experimenting or other unusual usage of the module.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

log to stderr instead of stdout?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tend to use stdout if it's the only and main output of a program, like in this case, but fine with me. Will update it together with the other changes.

return 1;
}

execlp("qrexec-client-vm", "qrexec-client-vm", "@default", "qubes.AuthorizeInVMAdminAccess", NULL);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just an idea: add PAM service name as the call argument? This way you could for example allow polkit but not sudo. Or do you think it it's not really useful?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure about a use case, since you get full admin access in all cases. But if you think it's useful, I would not object.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not really sure. It could be useful if there are cases where it allows some limited root access, but even for polkit it would allow pkexec (right?) so a full shell anyway... So, maybe not that useful?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes currently the polkit config allows full access. But yes, in the future their might be an PAM using program that doesn't give full access.

sudo has an pam_service option, so you might build something based on that. Since polkit is scriptable there probably too, but I didn't looked into that.

@HW42 HW42 force-pushed the simon/admin-authz branch from 56960e0 to e7b9577 Compare October 14, 2025 18:53
@HW42 HW42 force-pushed the simon/admin-authz branch from e7b9577 to b7decf6 Compare October 14, 2025 18:58
@HW42
Copy link
Contributor Author

HW42 commented Oct 14, 2025

[...] but not much at the PAM configs. On the first sight looks like there is quite a bit of duplication in the PAM configs, but that's probably unavoidable to make it work with any authselect profile...

Those are direct copies of the Fedora configs, just with our line added. And yes AFAIU that's how you are supposed to use authselect: You can only define a full profile not a small addition like with pam-auth-config.

Copy link
Contributor

@DemiMarie DemiMarie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly looks good and I like the mechanism. The main caveat is that privilege escalation via various Xen devices is likely still possible. This is because libvchan does not have kernel support.

Comment on lines +129 to +145
if (read_ret < 0) {
pam_syslog(pamh, LOG_ERR, "failed to read from socket: %i", errno);
rc = PAM_SYSTEM_ERR;
goto ret;
}

// Since the other side is trusted this isn't strictly necessary. But it's
// probably still nicer to ensure that we don't put unexpected bytes into
// the log.
for (size_t i = 0; i < sizeof(res) - 1; i += 1) {
if (res[i] == '\0') {
break;
}
if (res[i] < 0x20 || res[i] > 0x7e) {
res[i] = '.';
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe check that the other side didn’t send a NUL byte in the string?

# keeps working on upgrade. (But still allow the user to override the preset,
# if they really want.

if [[ -e /run/systemd/system ]]; then
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[[ is not part of POSIX and isn't needed here.

@marmarek
Copy link
Member

The main caveat is that privilege escalation via various Xen devices is likely still possible.

Offtopic for this PR...

Copy link
Contributor

@ArrayBolt3 ArrayBolt3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple of notes and a few typo fixes. I didn't review the code thoroughly, but I skimmed part of it and read the documentation page.

Comment on lines +29 to +39
# If systemd is running try starting qubes-admin-authzd even if the user
# blocked the normal start by dh (above) via policy-rc.d. Not running it will
# break passwordless auth, so this is most likely not what the user wanted. But
# if they really want to they still can mask the service.

if [[ -e /run/systemd/system ]] &&
! systemctl is-active --quiet qubes-admin-authzd.service &&
systemctl is-enabled --quiet qubes-admin-authzd.service
then
systemctl start qubes-admin-authzd.service || true
fi
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a concrete scenario where policy-rc.d blocks startup of something incorrectly? I'm a bit worried about assuming the user's intentions here, since Kicksecure's build system uses a "deny everything" policy-rc.d to prevent everything from starting, because of the fallout it can have on the rest of the build. Bypassing this mechanism seems dangerous at best. If a user does end up blocked, they can always open a DispVM console and log in as root, or use qvm-run -u root ....

- `allow`: Always allow admin access.
- `deny`: Always deny admin access.
- `qrexec`: Make a qrexec call and use it's result to allow/deny. The main
usage is to have a trivial qrexec service in dom0 and use the qrexex policy
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
usage is to have a trivial qrexec service in dom0 and use the qrexex policy
usage is to have a trivial qrexec service in dom0 and use the qrexec policy

setting or more common via /etc/qubes/admin-authzd.conf in a template (setting
in /usr/local has precendce).

There config file have a very simpley syntax. The first line needs to contain
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
There config file have a very simpley syntax. The first line needs to contain
There config file have a very simple syntax. The first line needs to contain

Comment on lines +34 to +36
The mode can be set via /usr/local/etc/qubes/admin-authzd.conf for per-qube
setting or more common via /etc/qubes/admin-authzd.conf in a template (setting
in /usr/local has precendce).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whonix would like for this to be configurable in Qube Manager, via a tri-select dropdown with a default setting. Is qvm-service the best tool for this job, or would something like qvm-prefs or qvm-features work better here? (I would assume a property settable via qvm-prefs and readable via qubesdb would be best.)

} else if (strcmp(argv[i], "quiet") == 0) {
quiet = true;
} else {
pam_syslog(pamh, LOG_ERR, "unkown option: %s", argv[i]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pam_syslog(pamh, LOG_ERR, "unkown option: %s", argv[i]);
pam_syslog(pamh, LOG_ERR, "unknown option: %s", argv[i]);

@adrelanos
Copy link
Member

@DemiMarie

The main caveat is that privilege escalation via various Xen devices is likely still possible. This is because libvchan does not have kernel support.

Related:

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

Successfully merging this pull request may close these issues.

6 participants