Before you start#
Review D-Bus messages, credentials, and PAM and NSS. The examples inspect polkit release 124 at commit 82f0924dc0eb23b9df68e88dbaf9e07c81940a5a and the separately pinned D-Bus daemon 1.14.10. They are historical source snapshots, not a tested host policy or compatibility matrix.
Learning goals#
Distinguish bus delivery, polkit action decisions, an authentication agent and service-side validation. Track an asynchronous check through cancellation and reference cleanup. Explain why caller identity, target state and a delayed authorization result must be considered separately.
Source walkthrough#
- ClientBus
request protected method
- BusService
deliver if bus policy permits
- ServiceAuthority
check action and subject
- AuthorityService
Return. decision or error
- ServiceService
revalidate target and caller
- ServiceClient
Return. reply if request remains valid
Time runs downward. Dashed arrows show returns.
Read the text version
- Client → Bus: request protected method
- Bus → Service: deliver if bus policy permits
- Service → Authority: check action and subject
- Authority → Service (return): decision or error
- Service → Service: revalidate target and caller
- Service → Client (return): reply if request remains valid
A system service may receive a D-Bus method call that requests a privileged operation. A message bus decides whether its configured policy permits connection, name ownership and message delivery. The service must validate its method arguments and object state and can ask polkit for an authorization decision about a named action and subject. An authentication agent can help a user satisfy a challenge if interaction is allowed and an appropriate agent is available. These are separate components, and an answer at one boundary is not automatically an answer at another.
The following exact excerpt from polkit 124 src/polkitbackend/polkitbackendauthority.c, polkit_backend_authority_check_authorization, is LGPL-2.0-or-later under the file notice. The surrounding fallback branch is omitted:
else
{
klass->check_authorization (authority, caller, subject, action_id, details, flags, cancellable, callback, user_data);
}
klass selects the backend authority class, and this branch dispatches through its authorization callback. The arguments distinguish the request's caller and subject, action identifier, details, flags, cancellation object, completion callback and callback data. The operation is asynchronous under this interface: a caller must retain or reference the necessary state until completion or cancellation according to the API. The preceding branch reports an unsupported-operation error if the backend lacks the required method; it does not silently grant access. This is an interface dispatch, not a complete account of every policy rule or agent interaction.
In src/polkitbackend/polkitbackendauthority.c, polkit 124, the file is LGPL-2.0-or-later under its notice; this is the missing-method if that precedes the dispatch above (surrounding code omitted):
if (klass->check_authorization == NULL)
{
GSimpleAsyncResult *simple;
g_warning ("check_authorization is not implemented (it is not optional)");
simple = g_simple_async_result_new_error (G_OBJECT (authority),
callback,
user_data,
POLKIT_ERROR,
POLKIT_ERROR_NOT_SUPPORTED,
"Operation not supported (bug in backend)");
g_simple_async_result_complete (simple);
g_object_unref (simple);
}
The branch logs a warning and completes the result with POLKIT_ERROR_NOT_SUPPORTED instead of calling the missing method. The excerpt does not show where a backend registers check_authorization, so it does not establish that any installed authority provides it.
A second exact excerpt from the same pinned file, check_auth_data_free, shows a selected cleanup path; its LGPL-2.0-or-later notice applies. Other fields and deallocation statements are omitted:
if (data->cancellable != NULL)
g_object_unref (data->cancellable);
The CheckAuthData structure holds a cancellable reference alongside an invocation, caller and subject references. Cleanup tests whether the cancellable exists and releases the reference. The surrounding function also releases other owned references and allocated strings. An asynchronous callback that races cancellation must not use state after its ownership has ended; conversely, merely asking to cancel does not guarantee that an already completed operation's external effects are undone. The exact completion and cancellation behavior is determined by the API and service using the result.
Four decisions on one request#
Imagine client C sends ChangeSetting to service S. First, the bus authenticates the connection under its transport rules and applies configured delivery policy. A denied bus message may never reach S. A permitted message merely reaches the service; it is not a grant to perform the requested operation. Bus eavesdropping or monitoring policy controls observation of messages and is not the same thing as authenticating the person requesting an operation. The D-Bus 1.14.10 bus/dispatch.c contains selected security-policy checks before dispatch; it is a different implementation from the polkit authority.
Second, S validates the method signature, target object and argument ranges. A validly delivered message can still contain an invalid path, unsupported value or stale object identifier. Third, if S's design requires it, S requests a polkit decision for a named action and appropriate subject. A decision can indicate authorization, denial or a challenge according to the API and policy. Fourth, S applies its own post-decision checks and performs or rejects the operation. Polkit does not automatically validate an arbitrary service object's state, perform the requested write, or make the service's application logic race-free.
The subject can describe a D-Bus system-bus name or a process-related identity depending on the API. A unique bus name is scoped to a connection; it can disappear on disconnect. A numeric PID can be reused. The polkit subject API supports a pidfd plus UID where the operating system supports pidfds; otherwise it documents PID, UID and process start time. The pkcheck manual warns that abbreviated PID forms can race, so callers should use the strongest identity form available. UID, process, session and bus name answer different questions. The caller making a polkit check can also differ from the subject whose authorization is evaluated; do not substitute the service's privileged UID for the unprivileged requesting client's identity.
- Unique bus name
- Scoped to one bus connection and gone when that connection disconnects.
- Numeric PID
- Can be reused after the process exits, so it is weak as a long-lived identity.
- pidfd plus UID
- The stronger subject form where the operating system supports pidfds.
- PID, UID and start time
- The documented fallback when pidfds are unavailable.
An action's policy can distinguish active and inactive sessions, local and remote subjects, and identities eligible to authenticate. These conditions depend on session and authority state and are not properties that a client can safely assert in an untrusted message. An agent is a participant that communicates authentication requests to a user and returns evidence through its protocol; it is not the bus daemon and is not equivalent to the protected service. An interactive challenge is possible only when the caller allows interaction and policy, agent availability and context permit it. The allow-interaction flag requests an attempt; it does not guarantee a prompt or approval, and the asynchronous check may take a long time. Absence of an agent, cancellation, timeout or policy denial are valid outcomes.
Delayed authorization and a race#
Consider a synthetic request: client :1.42 asks S to change object /demo/item, and S starts an authorization check for action org.example.change. At T0 S parses the request and obtains the calling connection identity. At T1 the authority reports that interaction is needed; an eligible agent may prompt under the configured policy. At T2 the client disconnects, and the object is removed or replaced by another actor. At T3 an asynchronous completion arrives. Even if the authority's decision was affirmative for the earlier subject and action, S's object pointer, connection and target state cannot be assumed unchanged.
- T0 parse
The service parses the request and records the caller connection identity.
- T1 pending
The authority reports that interaction is needed; the service keeps the references it still needs.
- T2 disconnect
The client disconnects, and the target object is removed or replaced by another actor.
- T3 completion
An affirmative result arrives; the service revalidates its target under its own contract before acting.
A well-designed service retains the references needed for the pending request, handles cancellation and disconnect, and rechecks the specific object and invariants it intends to modify before applying a delayed result. If its contract requires the original caller still to be connected, it rejects or abandons the operation when that condition no longer holds. Whether a particular upstream service does exactly this must be established from that service's own code; it is not a universal guarantee supplied by polkit. This scenario is a teaching model, not an executed polkit check.
A temporary authorization can be scoped by the authority's policy, subject, action and lifetime. It should not be described as a permanent system-wide permission or a capability to modify any target. A cached positive decision can become irrelevant if the target object, operation details, caller connection or policy changes. A service must define which facts are bound to the check and which facts require revalidation. A time-of-check/time-of-use race is the gap between evaluating conditions and acting on an object whose relevant state can change.
The GCancellable in the polkit source represents cancellation coordination, not ownership of every service object. A callback can be scheduled around the same time a caller cancels or disconnects. The service must obey its asynchronous API contract, avoid double replies, release references once, and not dereference callback data after it has been freed. Cancellation may prevent a pending challenge or request from continuing, but it cannot be treated as a transactional rollback of arbitrary work that a protected service already performed.
Authentication, authority and evidence#
Authentication can establish that an interaction satisfied a configured identity check; authorization decides whether a subject may perform an action under policy. PAM can participate in an authentication mechanism, but a successful PAM step is not a universal permission grant. A polkit agent's prompt, a bus connection's credentials and the application's resource validation are separate evidence. Do not collect passwords, run pkexec, invoke a real authorization agent or query the host system bus to practice this distinction.
A process can have Linux capabilities, an effective UID and namespace context that affect what the kernel permits. Polkit is not a replacement for those kernel checks. A privileged service can use its own authority after it decides to act, so incorrect subject identification or missing object validation can become a serious application bug. Conversely, a polkit approval cannot make an operation succeed if the service lacks the kernel or filesystem authority required to perform it. Record the service, action, caller identity mechanism, target, policy and kernel result separately when diagnosing an authorized test environment.
A system-bus policy can restrict who may send a method call or own a name; the defaults depend on bus configuration. It can also restrict monitoring capabilities under its configuration. Those are routing and observation boundaries, not a claim that a method's arguments are safe or that the human user authenticated. Some services may allow a broad set of clients to contact them and perform their own fine-grained checks; others reject earlier. Policy and implementation vary. A source-only chapter cannot tell you what an installed host's rules allow, and this exercise deliberately avoids inspecting that host state.
Cancellation and state ownership#
The pinned authority's CheckAuthData stores references to the invocation, caller, subject and cancellable, together with a cancellation identifier. Its cleanup function releases those references and frees the identifier and structure. In the cancellation method, the authority constructs a key from the request sender and supplied cancellation identifier before looking up pending state. If there is no matching pending entry, it returns an error; otherwise it calls g_cancellable_cancel and responds to the cancellation request. This is a specific implementation path, not evidence that arbitrary services automatically cancel their own operations when any client disconnects.
In the same pinned file, src/polkitbackend/polkitbackendauthority.c, check_auth_cb under the same LGPL-2.0-or-later notice (surrounding code omitted):
if (data->cancellation_id != NULL)
g_hash_table_remove (data->server->cancellation_id_to_check_auth_data, data->cancellation_id);
if (error != NULL)
{
g_dbus_method_invocation_return_gerror (data->invocation, error);
g_error_free (error);
}
else
{
g_dbus_method_invocation_return_value (data->invocation,
g_variant_new ("(@(bba{ss}))",
polkit_authorization_result_to_gvariant (result))); /* A floating value */
g_object_unref (result);
The callback removes the cancellation identifier from the server's table before it replies, then returns either the error or the result on the invocation. The excerpt does not show where that entry is added, and it does not show every path that could reply first.
- Dispatch
A missing check_authorization method completes with NOT_SUPPORTED instead of granting access.
- Pending
CheckAuthData holds the invocation, caller, subject and cancellable references while the check runs.
- Completion
check_auth_cb removes the cancellation entry, then returns the error or the result.
- Cleanup
check_auth_data_free releases each owned reference and frees the cancellation identifier.
A service that starts an asynchronous check should describe who owns each object at four points: before the call, while the check is pending, inside completion, and after cancellation. The invocation might need a reference while the check is pending. The target might be represented by a stable object reference or an identifier that must be looked up again. A callback must determine whether a response has already been sent and whether it still owns the right to use its state. If cancellation and completion can both schedule work, the implementation must arrange a single terminal transition under its own event-loop and locking rules. Merely storing a Boolean named authorized does not establish any of those lifetimes.
Suppose the target is a removable device and the service receives an affirmative decision after that device has been removed. Revalidating that the original object still exists and is the object intended by the request is different from making a fresh authorization decision for a new object with the same pathname. A path or numeric identifier can be reused. The service's actual interface determines whether it rejects the request, repeats the check or binds authorization to a stable object; none of those alternatives should be presented as an automatic polkit guarantee. The synthetic case illustrates a design question that requires a concrete service source path and test before asserting its real behavior.
An authorization challenge can also be unavailable without being a password failure. The request may disallow user interaction, the subject may have no eligible agent, or the caller may disconnect before a response. A temporary authorization, where provided, has a policy-defined scope and lifetime; it is not an inherited Linux capability for all descendants. An investigator should record the action, subject representation, flags, result and relevant service error without collecting a secret or assuming the user's visible name uniquely identifies the requesting process.
Practice#
This pure local model uses invented names and states. It sends no messages, contacts no bus or agent, and does not authorize a real operation:
python3 - <<'PY'
def decide(delivered, authorized, caller_alive, target_same):
return delivered and authorized and caller_alive and target_same
print('ordinary:', decide(True, True, True, True))
print('caller disconnected:', decide(True, True, False, True))
print('target changed:', decide(True, True, True, False))
print('bus denied:', decide(False, True, True, True))
PY
Expected output is True, then three False values. The function is not polkit or a D-Bus policy engine; it only makes the chosen model's revalidation assumptions visible. A real service may have a different contract and must be checked independently. If Python is unavailable, evaluate the same four rows by hand. No network, host policy, authentication prompt or privileged method is involved.
As a source-only alternative, draw C, bus, S, authority and agent; label T0–T3 and identify the reference held for each pending callback. Add the disconnected-caller and replaced-target cases. State which branch in the quoted backend dispatch handles a missing implementation and which cleanup releases the cancellable. Mark the result as a synthetic service design, not a runtime test or a claim about every polkit client.
Knowledge check#
- Why can a bus permit a method call while the service still rejects it?
- What do the
callerandsubjectparameters distinguish in the quoted backend dispatch? - Why is a numeric PID alone a weak long-lived process identity?
- What race occurs if a target changes while an interactive authorization is pending?
- What does unrefing
data->cancellableestablish, and what does it not establish about already performed work?
Answers#
- Bus delivery policy and service-side validation or authorization are separate. A validly routed call can contain invalid arguments, name a stale object or fail the service's authorization check.
- They represent potentially different roles in the authorization request: the entity making the check and the subject being evaluated. The service must choose them according to the API and must not substitute its own authority for the client's identity.
- PIDs can be reused after process exit. A process subject needs the API's additional identity context, such as start time where applicable, and must be checked against its lifecycle.
- An affirmative result for an earlier request can arrive after the original caller disconnected or the target object changed. The service must define and enforce its own reference, cancellation and revalidation policy before acting.
- It releases one owned reference in the selected cleanup function. It does not prove that an asynchronous callback could not already have run or undo arbitrary effects that another component already performed.
Checkpoint#
Submit the five-component request diagram and four-row decision table, plus either the private model output or the supplied source-only analysis. Ready means separating bus policy, subject identity, agent challenge and service validation, and describing the delayed-result race. Extend the source-only analysis by identifying a noninteractive denial case, then explain how an available agent and a caller disconnect would change the pending-request reasoning. Do not exercise a real authorization path.