@@ -361,4 +361,160 @@ pub mod linux {
361361
362362 Ok ( ( ) )
363363 }
364+
365+ #[ cfg( test) ]
366+ mod tests {
367+ use super :: MappingHeader ;
368+ use anyhow:: ensure;
369+ use std:: {
370+ fs:: File ,
371+ io:: { BufRead , BufReader } ,
372+ sync:: atomic:: { AtomicU64 , Ordering } ,
373+ } ;
374+
375+ /// Parses the start address from a /proc/self/maps line
376+ fn parse_mapping_start ( line : & str ) -> Option < usize > {
377+ usize:: from_str_radix ( line. split ( '-' ) . next ( ) ?, 16 ) . ok ( )
378+ }
379+
380+ /// Parses start and end addresses from a /proc/self/maps line
381+ fn parse_mapping_range ( line : & str ) -> Option < ( usize , usize ) > {
382+ let mut parts = line. split_whitespace ( ) ;
383+ let range = parts. next ( ) ?;
384+ let mut addrs = range. split ( '-' ) ;
385+ let start = usize:: from_str_radix ( addrs. next ( ) ?, 16 ) . ok ( ) ?;
386+ let end = usize:: from_str_radix ( addrs. next ( ) ?, 16 ) . ok ( ) ?;
387+
388+ Some ( ( start, end) )
389+ }
390+
391+ /// Checks if a /proc/self/maps line is a potential OTEL_CTX mapping
392+ ///
393+ /// Accepts both read-only (" r--p ") and read-write (" rw-p "/" rw-s ") permissions
394+ /// for backwards compatibility (old writers used read-only, new writers use read-write).
395+ /// memfd mappings use MAP_SHARED so they show as " rw-s ".
396+ ///
397+ /// Also accepts both 1-page (new) and 2-page (old) mapping sizes.
398+ fn is_otel_mapping_candidate ( line : & str ) -> bool {
399+ // Accept read-write private (anon), and read-write shared (memfd)
400+ if !( line. contains ( " rw-p " ) || line. contains ( " rw-s " ) ) {
401+ return false ;
402+ }
403+
404+ // Check that the size matches
405+ parse_mapping_range ( line)
406+ . map ( |( start, end) | end. saturating_sub ( start) == super :: mapping_size ( ) )
407+ . unwrap_or ( false )
408+ }
409+
410+ /// Checks if a mapping line refers to the OTEL_CTX mapping by name
411+ ///
412+ /// Handles both anonymous naming (`[anon:OTEL_CTX]`) and memfd naming
413+ /// (`/memfd:OTEL_CTX` which may have ` (deleted)` suffix).
414+ fn is_named_otel_mapping ( line : & str ) -> bool {
415+ let trimmed = line. trim_end ( ) ;
416+ trimmed. ends_with ( "[anon:OTEL_CTX]" )
417+ || trimmed. contains ( "/memfd:OTEL_CTX" )
418+ || trimmed. contains ( "memfd:OTEL_CTX" )
419+ }
420+
421+ /// Checks if a mapping line refers to the OTEL_CTX memfd mapping
422+ fn is_memfd_otel_mapping ( line : & str ) -> bool {
423+ line. contains ( "/memfd:OTEL_CTX" )
424+ }
425+
426+ /// Reads the signature from a memory address to verify it's an OTEL_CTX mapping. This also
427+ /// establish proper synchronization/memory ordering through atomics since the reader is
428+ /// the same process in this test setup.
429+ fn verify_signature_at ( addr : usize ) -> bool {
430+ let ptr: * mut u64 = std:: ptr:: with_exposed_provenance_mut ( addr) ;
431+ // Safety: We're reading from our own process memory at an address
432+ // we found in /proc/self/maps. This should be safe as long as the
433+ // mapping exists and has read permissions.
434+ //
435+ // The atomic alignment constraints are checked during publication.
436+ let signature = unsafe { AtomicU64 :: from_ptr ( ptr) . load ( Ordering :: Acquire ) } ;
437+ & signature. to_ne_bytes ( ) == super :: SIGNATURE
438+ }
439+
440+ /// Find the OTEL_CTX mapping in /proc/self/maps
441+ fn find_otel_mapping ( ) -> anyhow:: Result < usize > {
442+ let file = File :: open ( "/proc/self/maps" ) ?;
443+ let reader = BufReader :: new ( file) ;
444+
445+ for line in reader. lines ( ) {
446+ let line = line?;
447+
448+ if !is_otel_mapping_candidate ( & line) {
449+ continue ;
450+ }
451+
452+ if is_named_otel_mapping ( & line) {
453+ if let Some ( addr) = parse_mapping_start ( & line) {
454+ return Ok ( addr) ;
455+ }
456+ }
457+
458+ if is_memfd_otel_mapping ( & line) {
459+ if let Some ( addr) = parse_mapping_start ( & line) {
460+ return Ok ( addr) ;
461+ }
462+ }
463+
464+ // For unnamed mappings, verify by reading the signature
465+ if let Some ( addr) = parse_mapping_start ( & line) {
466+ if verify_signature_at ( addr) {
467+ return Ok ( addr) ;
468+ }
469+ }
470+ }
471+
472+ Err ( anyhow:: anyhow!(
473+ "couldn't find the mapping of the process context"
474+ ) )
475+ }
476+
477+ /// Read the process context from the current process.
478+ ///
479+ /// This searches `/proc/self/maps` for an OTEL_CTX mapping and decodes its contents.
480+ pub fn read_process_context ( ) -> anyhow:: Result < MappingHeader > {
481+ let mapping_addr = find_otel_mapping ( ) ?;
482+ let header_ptr = mapping_addr as * const MappingHeader ;
483+
484+ // Note: verifying the signature ensures proper synchronization
485+ ensure ! (
486+ verify_signature_at( mapping_addr) ,
487+ "verification of the signature failed"
488+ ) ;
489+
490+ // Safety: we found this address in /proc/self/maps and verified the signature
491+ Ok ( unsafe { std:: ptr:: read ( header_ptr) } )
492+ }
493+
494+ #[ test]
495+ fn publish_then_read_context ( ) {
496+ let payload = "example process context payload" ;
497+
498+ super :: publish ( payload. as_bytes ( ) . to_vec ( ) )
499+ . expect ( "couldn't publish the process context" ) ;
500+ let header = read_process_context ( ) . expect ( "couldn't read back the process contex" ) ;
501+ // Safety: the published context must have put valid bytes of size payload_size in the
502+ // context if the signature check succeded.
503+ let read_payload = unsafe {
504+ std:: slice:: from_raw_parts ( header. payload_ptr , header. payload_size as usize )
505+ } ;
506+
507+ assert ! ( header. signature == * super :: SIGNATURE , "wrong signature" ) ;
508+ assert ! (
509+ header. version == super :: PROCESS_CTX_VERSION ,
510+ "wrong context version"
511+ ) ;
512+ assert ! (
513+ header. payload_size == payload. len( ) as u32 ,
514+ "wrong payload size"
515+ ) ;
516+ assert ! ( header. published_at_ns > 0 , "published_at_ns is zero" ) ;
517+ assert ! ( read_payload == payload. as_bytes( ) , "payload mismatch" ) ;
518+ }
519+ }
364520}
0 commit comments