windows_thread_ambient_sys/state.rs
1// Copyright (c) Mike Grier.
2
3//! The composite: a thread's ambient state, captured as one value.
4//!
5//! [`AmbientState`] holds every aspect together, so a caller carries one value
6//! to a worker rather than remembering which pieces it collected. Its field list
7//! is contract surface: it is exhaustively enumerated, and a silently added
8//! field would be a silent semantic change.
9//!
10//! # Capture fails on the calling thread, never later
11//!
12//! Capture is synchronous and happens where the caller can still act on the
13//! result. A context that cannot be captured is an **admission** failure, not a
14//! deferred one -- a worker discovering it later has no way to report it to
15//! anyone who can do anything about it, and by then the caller has usually moved
16//! on. The error names the aspect that failed, because "capture failed" is not
17//! actionable when three aspects could have caused it.
18//!
19//! # Declared aspects are not captured
20//!
21//! [`Declared`] values are supplied by the caller and read from nothing, so they
22//! are attached with [`AmbientState::with_declared`] rather than collected. That
23//! separation is why the capture set names only capturable aspects.
24//!
25//! # One capture can serve many workers at once
26//!
27//! [`AmbientState`] is both `Send` and `Sync`, so a single capture may be shared
28//! through an `Arc` and applied concurrently on any number of workers. That is
29//! the shape a traversal or scan engine actually has: capture once at
30//! submission, then run it on every worker for the length of the job. Each
31//! application installs and restores on its own thread and observes nothing of
32//! the others.
33//!
34//! Sharing is also the cheap option. Capture duplicates a kernel token object,
35//! so re-capturing per unit of work re-pays for a snapshot the caller already
36//! holds.
37//!
38//! # Granularity is the caller's choice, and it costs something
39//!
40//! Applying once around a batch of operations and applying once per operation
41//! are both expressible, and the crate deliberately does not choose. Each
42//! application is a `SetThreadToken` plus a call for every other aspect in play,
43//! so a worker that opens a thousand files pays that a thousand times if it
44//! applies per open.
45//!
46//! Prefer the widest window the aspects allow -- but note that the *narrowest*
47//! window is sometimes the correct one for a reason unrelated to cost:
48//! [crates/windows-file-enumeration-sys](../../windows-file-enumeration-sys/DESIGN-NOTES.md)
49//! deliberately impersonates only around its directory open, because every later
50//! query uses the resulting handle and needs no token at all. Holding a token
51//! longer than the work requires is a security decision, not just a performance
52//! one.
53//!
54//! # The blast radius of fail-fast restoration
55//!
56//! A failure to restore impersonation panics. **That is this crate's decision,
57//! not one inherited from a dependency**: a shared worker returned to a pool
58//! under an unknown identity is a process-wide security failure, and no error
59//! return could make a caller notice in time.
60//! [`windows_impersonation_token_sys`] is used because its behaviour already
61//! satisfies that requirement; if it stopped doing so, the dependency would be
62//! wrong and this guarantee would stay.
63//!
64//! The consequence is worth stating plainly for anyone running many impersonated
65//! workers. A panic inside a thread-pool callback **aborts the process** -- the
66//! pool has no caller to unwind to -- so a restore failure on one worker of
67//! sixty-four is not one failed operation, it is the whole process. This is the
68//! intended trade, and a consumer that cannot accept it should not be applying
69//! impersonation on threads it does not own.
70//!
71//! # Example
72//!
73//! ```
74//! use windows_thread_ambient_sys::declared::MemoryPriority;
75//! use windows_thread_ambient_sys::{AmbientState, CaptureSet, Declared};
76//!
77//! // Collected from this thread, right now, where a failure is still ours.
78//! let state = AmbientState::capture(CaptureSet::DEFAULT)?
79//! // Stated rather than read: nothing was collected for this.
80//! .with_declared(Declared::none().with_memory_priority(MemoryPriority::Low));
81//!
82//! // What was asked for is recoverable afterwards, which is what keeps an
83//! // omission distinguishable from an aspect that was captured and empty.
84//! assert_eq!(state.captured_set(), CaptureSet::DEFAULT);
85//! assert!(state.impersonation().was_captured());
86//! assert!(!state.transaction().was_captured());
87//!
88//! // Applying installs every aspect in a fixed order and releases in exact
89//! // reverse. An uncaptured aspect is skipped, leaving the running thread's own
90//! // value alone.
91//! let applied = state.with_applied(|| "work")?;
92//! assert_eq!(*applied.value(), "work");
93//! assert!(applied.restore().is_clean());
94//! # Ok::<(), Box<dyn std::error::Error>>(())
95//! ```
96
97use std::fmt;
98
99use windows_impersonation_token_sys::{
100 ApplyError as ImpersonationApplyError, CaptureError as ImpersonationCaptureError,
101 ImpersonationToken,
102};
103
104use crate::capture_set::{CapturableAspect, CaptureSet};
105use crate::captured::Captured;
106use crate::declared::{Declared, DeclaredError};
107use crate::error_mode::{
108 ApplyError as ErrorModeApplyError, RestoreError as ErrorModeRestoreError, ThreadErrorMode,
109 UnsupportedBits,
110};
111use crate::transaction::{TransactionContext, TransactionError};
112use crate::{impersonation, transaction};
113/// Which aspect failed to capture, and why.
114#[derive(Debug)]
115#[non_exhaustive]
116pub enum CaptureFailure {
117 /// The impersonation context could not be captured.
118 Impersonation(ImpersonationCaptureError),
119 /// The thread error mode reported a value this crate cannot represent.
120 ErrorMode(UnsupportedBits),
121 /// The current transaction could not be captured.
122 Transaction(TransactionError),
123}
124
125/// A composite capture failed.
126///
127/// The failing aspect is **derived** from the failure rather than stored beside
128/// it, so the two cannot disagree.
129#[derive(Debug)]
130pub struct CaptureError {
131 failure: CaptureFailure,
132}
133
134impl CaptureError {
135 /// Which aspect failed.
136 #[must_use]
137 pub const fn aspect(&self) -> CapturableAspect {
138 match self.failure {
139 CaptureFailure::Impersonation(_) => CapturableAspect::Impersonation,
140 CaptureFailure::ErrorMode(_) => CapturableAspect::ErrorMode,
141 CaptureFailure::Transaction(_) => CapturableAspect::Transaction,
142 }
143 }
144
145 /// The underlying failure.
146 #[must_use]
147 pub const fn failure(&self) -> &CaptureFailure {
148 &self.failure
149 }
150
151 /// The underlying Win32 code, if the failing aspect reported one.
152 #[must_use]
153 pub fn raw_os_error(&self) -> Option<i32> {
154 match &self.failure {
155 CaptureFailure::Impersonation(error) => error.raw_os_error(),
156 CaptureFailure::ErrorMode(_) => None,
157 CaptureFailure::Transaction(error) => error.raw_os_error(),
158 }
159 }
160}
161
162impl fmt::Display for CaptureError {
163 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
164 write!(f, "capturing the {} aspect failed: ", self.aspect())?;
165 match &self.failure {
166 CaptureFailure::Impersonation(error) => write!(f, "{error}"),
167 CaptureFailure::ErrorMode(error) => write!(f, "{error}"),
168 CaptureFailure::Transaction(error) => write!(f, "{error}"),
169 }
170 }
171}
172
173impl std::error::Error for CaptureError {
174 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
175 match &self.failure {
176 CaptureFailure::Impersonation(error) => Some(error),
177 CaptureFailure::ErrorMode(error) => Some(error),
178 CaptureFailure::Transaction(error) => Some(error),
179 }
180 }
181}
182
183/// A thread's ambient state, captured and declared, ready to travel.
184///
185/// The field list is exhaustive on purpose; see the module documentation.
186#[derive(Debug)]
187#[must_use = "an ambient state that is never applied captured a context for nothing"]
188pub struct AmbientState {
189 impersonation: Captured<ImpersonationToken>,
190 error_mode: Captured<ThreadErrorMode>,
191 transaction: Captured<TransactionContext>,
192 declared: Declared,
193}
194
195impl AmbientState {
196 /// Capture the aspects `set` names from the calling thread.
197 ///
198 /// Aspects outside `set` are [`Captured::NotCaptured`], which leaves the
199 /// target thread's own value alone when the state is later applied -- a
200 /// different thing from an aspect that was captured and found empty.
201 ///
202 /// Declared aspects are not touched here; attach them with
203 /// [`with_declared`](Self::with_declared).
204 ///
205 /// # Errors
206 ///
207 /// Returns [`CaptureError`], naming the aspect that failed. Any aspect
208 /// captured before the failure is released rather than leaked, so a failed
209 /// capture holds nothing.
210 pub fn capture(set: CaptureSet) -> Result<Self, CaptureError> {
211 // Order follows `CapturableAspect::EVERY` so the sequence is the one the
212 // set reports, rather than an incidental one.
213 let impersonation = if set.contains(CaptureSet::IMPERSONATION) {
214 impersonation::capture().map_err(|error| CaptureError {
215 failure: CaptureFailure::Impersonation(error),
216 })?
217 } else {
218 Captured::NotCaptured
219 };
220
221 let error_mode = if set.contains(CaptureSet::ERROR_MODE) {
222 Captured::Present(ThreadErrorMode::capture().map_err(|error| CaptureError {
223 failure: CaptureFailure::ErrorMode(error),
224 })?)
225 } else {
226 Captured::NotCaptured
227 };
228
229 let transaction = if set.contains(CaptureSet::TRANSACTION) {
230 transaction::capture().map_err(|error| CaptureError {
231 failure: CaptureFailure::Transaction(error),
232 })?
233 } else {
234 Captured::NotCaptured
235 };
236
237 Ok(Self {
238 impersonation,
239 error_mode,
240 transaction,
241 declared: Declared::none(),
242 })
243 }
244
245 /// Attach declared aspects, replacing any already attached.
246 pub fn with_declared(mut self, declared: Declared) -> Self {
247 self.declared = declared;
248 self
249 }
250
251 /// What was actually collected.
252 ///
253 /// **Derived** from the aspects themselves rather than recorded separately,
254 /// so it cannot disagree with what the state holds.
255 #[must_use]
256 pub fn captured_set(&self) -> CaptureSet {
257 let mut set = CaptureSet::NONE;
258 if self.impersonation.was_captured() {
259 set = set.union(CaptureSet::IMPERSONATION);
260 }
261 if self.error_mode.was_captured() {
262 set = set.union(CaptureSet::ERROR_MODE);
263 }
264 if self.transaction.was_captured() {
265 set = set.union(CaptureSet::TRANSACTION);
266 }
267 set
268 }
269
270 /// The captured impersonation context.
271 #[must_use]
272 pub const fn impersonation(&self) -> &Captured<ImpersonationToken> {
273 &self.impersonation
274 }
275
276 /// The captured thread error mode.
277 #[must_use]
278 pub const fn error_mode(&self) -> &Captured<ThreadErrorMode> {
279 &self.error_mode
280 }
281
282 /// The captured transaction.
283 #[must_use]
284 pub const fn transaction(&self) -> &Captured<TransactionContext> {
285 &self.transaction
286 }
287
288 /// The declared aspects.
289 #[must_use]
290 pub const fn declared(&self) -> &Declared {
291 &self.declared
292 }
293
294 /// Run `operation` with this state installed on the calling thread.
295 ///
296 /// # Order
297 ///
298 /// Guards are applied outermost-first and released in **exact reverse**, so
299 /// the thread passes back through each intermediate state:
300 ///
301 /// 1. thread error mode -- outermost, so hard-error suppression is already
302 /// in force while everything else is being applied;
303 /// 2. declared aspects (background mode, memory priority, redirection);
304 /// 3. TxF transaction;
305 /// 4. impersonation -- innermost, because its window is the narrowest and
306 /// its restoration is the one that must not be delayed.
307 ///
308 /// Applying a subset stays expressible: an aspect that is
309 /// [`Captured::NotCaptured`] or unspecified is skipped entirely, leaving the
310 /// running thread's own value alone.
311 ///
312 /// # Overriding rather than transplanting the error mode
313 ///
314 /// This applies the error mode it *captured*. A consumer that wants to
315 /// impose its own -- forcing the dialog-suppressing bits on a shared worker,
316 /// say -- should leave [`CaptureSet::ERROR_MODE`] out of its capture set and
317 /// wrap this call in its own [`ThreadErrorMode::apply`] guard, which then
318 /// sits outermost, exactly where the order above puts it. Capturing *and*
319 /// overriding would install the captured value inside the override.
320 ///
321 /// # Errors
322 ///
323 /// Returns [`ApplyError`] if an aspect could not be installed, in which case
324 /// `operation` did not run and every already-installed aspect is released
325 /// first.
326 ///
327 /// A failure to **restore** is different, and does not fail the call: the
328 /// operation ran and its value is kept, with the failures reported through
329 /// [`Applied::restore`]. Discarding a successful operation's value because a
330 /// priority could not be put back would lose more than it protects.
331 ///
332 /// # Panics
333 ///
334 /// **Panics if the impersonation context cannot be restored, and that is
335 /// this crate's guarantee rather than a detail of its dependencies.**
336 /// Returning a shared worker to a pool under an unknown identity is a
337 /// process-wide security failure: every later task on that thread would run
338 /// as whoever the failed restore left behind, and no caller could detect it
339 /// from a returned error. Failing fast is the only response that cannot be
340 /// ignored, which is a different order of hazard from the other aspects
341 /// here, and they are reported rather than fatal.
342 ///
343 /// [`windows_impersonation_token_sys`] is used because its behaviour
344 /// already satisfies that guarantee. If it ever stopped doing so, the
345 /// dependency would be wrong and this contract would not change -- an
346 /// earlier version of this note described the semantics as *inherited* from
347 /// that crate and "not chosen here", which left a security property of this
348 /// public API resting on someone else's implementation detail.
349 pub fn with_applied<F, T>(&self, operation: F) -> Result<Applied<T>, ApplyError>
350 where
351 F: FnOnce() -> T,
352 {
353 // 1. Error mode, outermost.
354 let error_mode_guard = match self.error_mode.present() {
355 Some(mode) => Some(mode.apply().map_err(|error| ApplyError {
356 failure: ApplyFailure::ErrorMode(error),
357 })?),
358 None => None,
359 };
360
361 // 2. Declared aspects.
362 let declared_guard = match self.declared.install() {
363 Ok(guard) => guard,
364 Err(error) => {
365 drop(error_mode_guard);
366 return Err(ApplyError {
367 failure: ApplyFailure::Declared(error),
368 });
369 }
370 };
371
372 // 3. Transaction.
373 let transaction_guard = match transaction::install(&self.transaction) {
374 Ok(guard) => guard,
375 Err(error) => {
376 drop(declared_guard);
377 drop(error_mode_guard);
378 return Err(ApplyError {
379 failure: ApplyFailure::Transaction(error),
380 });
381 }
382 };
383
384 // 4. Impersonation, innermost, and closure-scoped by its own crate.
385 let outcome = impersonation::with_applied(&self.impersonation, operation);
386 let value = match outcome {
387 Ok(value) => value,
388 Err(error) => {
389 drop(transaction_guard);
390 drop(declared_guard);
391 drop(error_mode_guard);
392 return Err(ApplyError {
393 failure: ApplyFailure::Impersonation(error),
394 });
395 }
396 };
397
398 // Release in exact reverse. Every release is attempted even after one
399 // fails, because stopping early leaves more of the thread contaminated.
400 //
401 // These are separate statements rather than a struct literal on purpose:
402 // the order below *is* the release order, and burying it in field
403 // initialisers would make a later reader's harmless-looking field
404 // reordering silently reorder the releases.
405 let transaction = transaction_guard.release().err();
406 let declared = declared_guard.release().err();
407 let error_mode = match error_mode_guard {
408 Some(guard) => guard.release().err(),
409 None => None,
410 };
411
412 Ok(Applied {
413 value,
414 restore: RestoreReport {
415 error_mode,
416 declared,
417 transaction,
418 },
419 })
420 }
421}
422
423/// What an operation produced, and whether the thread was put back.
424#[derive(Debug)]
425#[must_use = "ignoring the restore report discards evidence that the thread is contaminated"]
426pub struct Applied<T> {
427 value: T,
428 restore: RestoreReport,
429}
430
431impl<T> Applied<T> {
432 /// The operation's value.
433 pub const fn value(&self) -> &T {
434 &self.value
435 }
436
437 /// Take the value, deliberately ignoring the restore report.
438 pub fn into_value(self) -> T {
439 self.value
440 }
441
442 /// Which aspects failed to restore, if any.
443 pub const fn restore(&self) -> &RestoreReport {
444 &self.restore
445 }
446
447 /// Take the value only if the thread was restored cleanly.
448 ///
449 /// # Errors
450 ///
451 /// Returns the report when any aspect failed to restore. The value is
452 /// dropped in that case, so a caller that needs both should use
453 /// [`value`](Self::value) and [`restore`](Self::restore) instead.
454 pub fn into_clean_value(self) -> Result<T, RestoreReport> {
455 if self.restore.is_clean() {
456 Ok(self.value)
457 } else {
458 Err(self.restore)
459 }
460 }
461}
462
463/// Which aspects could not be restored after an operation.
464///
465/// Exhaustively enumerated rather than a list, so a reader can see every aspect
466/// that can appear without running anything. Impersonation is absent by
467/// construction: its restore failure is fatal, so it never reaches a report.
468#[derive(Debug, Default)]
469pub struct RestoreReport {
470 error_mode: Option<ErrorModeRestoreError>,
471 declared: Option<DeclaredError>,
472 transaction: Option<TransactionError>,
473}
474
475impl RestoreReport {
476 /// Whether every aspect was restored.
477 #[must_use]
478 pub const fn is_clean(&self) -> bool {
479 self.error_mode.is_none() && self.declared.is_none() && self.transaction.is_none()
480 }
481
482 /// The thread error mode's restore failure, if any.
483 #[must_use]
484 pub const fn error_mode(&self) -> Option<&ErrorModeRestoreError> {
485 self.error_mode.as_ref()
486 }
487
488 /// The declared aspects' restore failure, if any.
489 #[must_use]
490 pub const fn declared(&self) -> Option<&DeclaredError> {
491 self.declared.as_ref()
492 }
493
494 /// The transaction's restore failure, if any.
495 #[must_use]
496 pub const fn transaction(&self) -> Option<&TransactionError> {
497 self.transaction.as_ref()
498 }
499}
500
501impl fmt::Display for RestoreReport {
502 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
503 if self.is_clean() {
504 return f.write_str("the thread was restored cleanly");
505 }
506 f.write_str("the thread is contaminated:")?;
507 if let Some(error) = &self.error_mode {
508 write!(f, " error mode: {error};")?;
509 }
510 if let Some(error) = &self.declared {
511 write!(f, " declared: {error};")?;
512 }
513 if let Some(error) = &self.transaction {
514 write!(f, " transaction: {error};")?;
515 }
516 Ok(())
517 }
518}
519
520impl std::error::Error for RestoreReport {}
521
522/// Which aspect could not be installed, and why.
523#[derive(Debug)]
524#[non_exhaustive]
525pub enum ApplyFailure {
526 /// The thread error mode could not be installed.
527 ErrorMode(ErrorModeApplyError),
528 /// A declared aspect could not be installed.
529 Declared(DeclaredError),
530 /// The transaction could not be installed.
531 Transaction(TransactionError),
532 /// The impersonation context could not be applied.
533 Impersonation(ImpersonationApplyError),
534}
535
536/// Applying a composite state failed, so the operation did not run.
537#[derive(Debug)]
538pub struct ApplyError {
539 failure: ApplyFailure,
540}
541
542impl ApplyError {
543 /// The underlying failure, whose variant names the aspect.
544 #[must_use]
545 pub const fn failure(&self) -> &ApplyFailure {
546 &self.failure
547 }
548
549 /// The underlying Win32 code, if the failing aspect reported one.
550 #[must_use]
551 pub fn raw_os_error(&self) -> Option<i32> {
552 match &self.failure {
553 ApplyFailure::ErrorMode(error) => error.raw_os_error(),
554 ApplyFailure::Declared(error) => error.raw_os_error(),
555 ApplyFailure::Transaction(error) => error.raw_os_error(),
556 ApplyFailure::Impersonation(error) => error.raw_os_error(),
557 }
558 }
559}
560
561impl fmt::Display for ApplyError {
562 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
563 f.write_str("applying the ambient state failed: ")?;
564 match &self.failure {
565 ApplyFailure::ErrorMode(error) => write!(f, "{error}"),
566 ApplyFailure::Declared(error) => write!(f, "{error}"),
567 ApplyFailure::Transaction(error) => write!(f, "{error}"),
568 ApplyFailure::Impersonation(error) => write!(f, "{error}"),
569 }
570 }
571}
572
573impl std::error::Error for ApplyError {
574 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
575 match &self.failure {
576 ApplyFailure::ErrorMode(error) => Some(error),
577 ApplyFailure::Declared(error) => Some(error),
578 ApplyFailure::Transaction(error) => Some(error),
579 ApplyFailure::Impersonation(error) => Some(error),
580 }
581 }
582}
583
584#[cfg(test)]
585mod tests;