Imagine a scenario where your application that’s running with elevated privileges like Local System, needs to perform actions on behalf of specific users in a Windows environment. This could range from backing up EFS-encrypted files to accessing user-specific registry settings or network resources. The challenge lies in the inherent restrictions of Windows, which prevent processes running under Local System from directly accessing user-level resources. In this post, we'll explore how we tackled this problem using Golang, focusing on Windows impersonation, goroutine management, and the creation of a versatile "User Task Processor."
Problem Analysis
We aimed to develop a robust solution that could handle various user-level operations in a Windows environment. The core issue stemmed from the fact that processes running under the Local System account lack the necessary privileges to access user-specific resources.
One obvious way to solve this problem was to spawn a new process that runs with the user’s privilege, enabling it to access user-level resources. However, this approach had several drawbacks:
Multi-user backup requirement: In Windows machines, our backup process needs to perform backups of multiple user accounts on a single device. The backup process should be able to back up user data (except in special cases where user privilege is required) even when the user is not logged into the machine. Additionally, running a user-mode process imposes certain restrictions on accessing system resources, which can further complicate the backup process.
Increased system footprint: Launching an additional process increases the memory and CPU usage, which we wanted to avoid for a lightweight client.
Inter-process communication (IPC) complexity: Securely transferring sensitive data between processes is challenging.
Data transfer overhead: Since filesystem data must be securely passed through the chosen IPC method, this could introduce performance bottlenecks.
The Solution
A more efficient solution was to leverage the same Local System-level process and dynamically acquire the necessary user privileges through impersonation. By carefully analyzing the Windows API for impersonation, we identified that certain process threads could be seamlessly impersonated with user-level privileges. This approach allowed our backup process to securely access user-level resources without spawning additional processes, minimizing complexity and resource usage.