Skip to content

feat: Interactive User Confirmation for Privilege Escalation#64

Merged
Vladush merged 8 commits into
masterfrom
feature/uac-style-prompt
Jun 11, 2026
Merged

feat: Interactive User Confirmation for Privilege Escalation#64
Vladush merged 8 commits into
masterfrom
feature/uac-style-prompt

Conversation

@Vladush

@Vladush Vladush commented Jun 10, 2026

Copy link
Copy Markdown
Owner

Description

This PR adds an interactive user confirmation prompt (require_confirmation) for privilege escalation, preventing silent exploitation by background scripts.

Behavior Change:
By default, require_confirmation is now set to true. This means LinuxCamPAM will no longer silently authenticate for all services upon human presence detection. Instead, for privilege escalation services (like sudo or su), users will be interactively prompted to press Enter to explicitly confirm authentication.
A default whitelist (confirmation_exempt_services) containing gdm-password, login, sddm, swaylock, kscreenlocker, etc., ensures that screen lockers and display managers continue to authenticate silently as before.

It also applies modern C++17 refactorings (such as std::exchange, UniqueFd, and std::filesystem) and brings hardware module unit test coverage up to 100%. Relevant security and configuration documentation have been updated accordingly.

Closes #62

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update

How Has This Been Tested?

Please describe the tests that you ran to verify your changes.

  • Verified require_confirmation logic via pam_tester to ensure interactive prompt blocks background tasks while authenticating interactive tasks when configured.
  • Executed full unit test suite including newly added test_hardware_manager.cpp, test_ite8353_parser.cpp, test_presence_tripwire.cpp, test_scoped_worker.cpp, and test_sensor_factory.cpp.
  • Achieved 100% test coverage on HardwareManager, Ite8353Parser, PresenceTripwire, ScopedWorker, and SensorFactory.

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

Vladush added 5 commits June 10, 2026 21:15
…tion

Adds require_confirmation to pam_config to prevent silent privilege escalation by background scripts. Requires interactive user confirmation (e.g. <Enter>) to proceed with facial authentication for non-exempt services.
Modernizes codebase by using std::exchange, UniqueFd for RAII, and std::filesystem. Extends unit tests to 100% coverage for hardware modules including HardwareManager, Ite8353Parser, PresenceTripwire, ScopedWorker, and SensorFactory.
…ion instructions for interactive confirmation

Documents the new require_confirmation configuration option, updates security analysis with new threat vectors regarding silent privilege escalation, and updates README with usage instructions for the interactive confirmation prompt.
@Vladush

Vladush commented Jun 10, 2026

Copy link
Copy Markdown
Owner Author

Hey @techhotspot,
Would you like to join this PR as reviewer?

@techhotspot

Copy link
Copy Markdown
Collaborator

Sure, want to add me?

@Vladush

Vladush commented Jun 10, 2026

Copy link
Copy Markdown
Owner Author

you have to accept the collaboration invite for that first. I've sent it just as I've asked you to join.

…kground sensor initialization, and memory leak fixes
@Vladush Vladush requested a review from techhotspot June 10, 2026 20:23

@techhotspot techhotspot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

pam_set_item return value ignored
src/pam/pam_linuxcampam.cpp

pam_set_item can fail. If it does you still return PAM_IGNORE, the downstream module never sees the token, and the user gets a silent failure or double-prompt.

if (pam_set_item(pamh, PAM_AUTHTOK, resp_pam[0].resp) != PAM_SUCCESS) {
    syslog(LOG_ERR, "Failed to relay password token to PAM stack");
    return PAM_AUTH_ERR;
}
return PAM_IGNORE;

Unknown service bypasses confirmation
src/pam/pam_linuxcampam.cpp

If pam_get_item(PAM_SERVICE) fails or returns null, the entire confirmation block is skipped and face auth proceeds silently — which is exactly what this feature is trying to prevent. Should default to requiring confirmation and only skip it for positively-identified exempt services.

bool needs_confirmation = true;
const void *service_ptr = nullptr;
if (pam_get_item(pamh, PAM_SERVICE, &service_ptr) == PAM_SUCCESS && service_ptr != nullptr) {
    // check exempt list, set needs_confirmation = false if matched
} else {
    syslog(LOG_WARNING, "PAM_SERVICE unavailable; applying confirmation prompt as fallback");
}
if (needs_confirmation) { ... }

Quoted value silently breaks service matching
src/pam/pam_config.hpp

The commented example in config.ini wraps the value in quotes. If a user copies it verbatim, split() yields "gdm-password with a leading quote and nothing matches. welcome_message already handles this — same fix needed here.

if (auto ces_opt = get_value("confirmation_exempt_services")) {
    std::string ces_str = *ces_opt;
    if (ces_str.size() >= 2 && ces_str.front() == '"' && ces_str.back() == '"')
        ces_str = ces_str.substr(1, ces_str.size() - 2);
    config.confirmation_exempt_services = split(ces_str, ',');
}

Fixed temp path causes parallel test races
tests/test_hardware_manager.cpp

Two processes in CI will collide on the hardcoded path.

temp_dir_ = fs::temp_directory_path() / ("linuxcampam_hw_test_" + std::to_string(getpid()));

@techhotspot techhotspot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

pam_set_item return value ignored
src/pam/pam_linuxcampam.cpp — inside pam_sm_authenticate, password fallback block

pam_set_item can fail. If it does you still return PAM_IGNORE, the downstream module never sees the token, and the user gets a silent failure or double-prompt.

Fix:

if (pam_set_item(pamh, PAM_AUTHTOK, resp_pam[0].resp) != PAM_SUCCESS) {
    syslog(LOG_ERR, "Failed to relay password token to PAM stack");
    return PAM_AUTH_ERR;
}
return PAM_IGNORE;

Unknown service bypasses confirmation
src/pam/pam_linuxcampam.cpp — inside pam_sm_authenticate, require_confirmation block

If pam_get_item(PAM_SERVICE) fails or returns null, the entire confirmation block is skipped and face auth proceeds silently — which is exactly what this feature is trying to prevent. Should default to requiring confirmation and only skip it for positively-identified exempt services.

Fix:

bool needs_confirmation = true;
const void *service_ptr = nullptr;
if (pam_get_item(pamh, PAM_SERVICE, &service_ptr) == PAM_SUCCESS && service_ptr != nullptr) {
    // check exempt list, set needs_confirmation = false if matched
} else {
    syslog(LOG_WARNING, "PAM_SERVICE unavailable; applying confirmation prompt as fallback");
}
if (needs_confirmation) { ... }

Quoted value silently breaks service matching
src/pam/pam_config.hppresolve_pam_config, confirmation_exempt_services parsing

The commented example in config.ini wraps the value in quotes. If a user copies it verbatim, split() yields "gdm-password with a leading quote and nothing matches. welcome_message already handles this — same fix needed here.

Fix:

if (auto ces_opt = get_value("confirmation_exempt_services")) {
    std::string ces_str = *ces_opt;
    if (ces_str.size() >= 2 && ces_str.front() == '"' && ces_str.back() == '"')
        ces_str = ces_str.substr(1, ces_str.size() - 2);
    config.confirmation_exempt_services = split(ces_str, ',');
}

Fixed temp path causes parallel test races
tests/test_hardware_manager.cppHardwareManagerTest::SetUp

Two processes in CI will collide on the hardcoded path.

Fix:

temp_dir_ = fs::temp_directory_path() / ("linuxcampam_hw_test_" + std::to_string(getpid()));

@techhotspot

Copy link
Copy Markdown
Collaborator

Sorry, didnt mean to post that twice

…logic, and prevent temporary directory collisions in tests
@Vladush

Vladush commented Jun 11, 2026

Copy link
Copy Markdown
Owner Author

Hey @techhotspot,
Thanks for the thorough review!
I've addressed all the points:
Added the missing check for the pam_set_item return value.
Defaulted to requiring confirmation if PAM_SERVICE is unavailable.
Added logic to strip wrapping quotes from confirmation_exempt_services before splitting.
Appended getpid() to the test temp directory to prevent parallel race conditions.

@Vladush Vladush requested a review from techhotspot June 11, 2026 08:23
@Vladush

Vladush commented Jun 11, 2026

Copy link
Copy Markdown
Owner Author

@techhotspot Thank you for CR!

@Vladush Vladush merged commit cb1d9d2 into master Jun 11, 2026
10 checks passed
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.

Confirmation Popup Before Granting Elevation

2 participants