1
+ /// Container Virtualization Test
2
+ ///
3
+ /// It is very difficult to detect from within a container whether we are running
4
+ /// under virtualization. Am I Isolated relies on heuristics to detect virtualization.
5
+ /// This is an incremental process, we expect this test to sometimes fail, particularly
6
+ /// in systems like Kata Containers which have no easy way to detect.
7
+ ///
8
+ /// There is a simple fallback mechanism that can give a "maybe" result (represented as a failure.)
9
+ /// We read /proc/uptime to attempt to get the system uptime. If the uptime is less than 60 seconds,
10
+ /// we assume that the container might be running under virtualization. This makes the assumption
11
+ /// that container lifetime is closely related to VM lifetime. In some VM systems, this might not
12
+ /// be true. In microVMs, like Edera Protect or firecracker, this should be true if the container
13
+ /// is running inside a single Linux kernel boot.
1
14
use anyhow:: Result ;
2
15
3
- use crate :: { util:: read_file_as_space_separated_lines, Test , TestCategory , TestResult } ;
16
+ use crate :: {
17
+ util:: { is_running_gvisor, read_file_as_space_separated_lines} ,
18
+ Test , TestCategory , TestResult ,
19
+ } ;
4
20
5
- pub struct VirtualizedTest { }
21
+ const KNOWN_VIRT_RUNTIMES : & [ & ' static str ] = & [ "edera" ] ;
22
+
23
+ #[ derive( Default , Debug , PartialEq , Eq ) ]
24
+ pub enum VirtualizationEnabled {
25
+ DefinitelyPresent ( String ) ,
26
+ MaybePresent ,
27
+ #[ default]
28
+ NotPresent ,
29
+ }
6
30
7
31
#[ derive( Default ) ]
8
32
pub struct VirtualizedResult {
9
- pub visible : bool ,
10
- pub uptime : u64 ,
33
+ pub enabled : VirtualizationEnabled ,
34
+ }
35
+
36
+ pub struct VirtualizedTest ;
37
+
38
+ impl VirtualizedTest {
39
+ pub fn check_definite_runtime_env ( & self ) -> Option < String > {
40
+ let container_runtime = std:: env:: var ( "container" ) . unwrap_or_default ( ) ;
41
+ KNOWN_VIRT_RUNTIMES
42
+ . iter ( )
43
+ . find ( |runtime| * * runtime == container_runtime. as_str ( ) )
44
+ . map ( |runtime| runtime. to_string ( ) )
45
+ }
46
+
47
+ pub fn check_definite_gvisor ( & self ) -> Option < String > {
48
+ if is_running_gvisor ( ) {
49
+ Some ( "gvisor" . to_string ( ) )
50
+ } else {
51
+ None
52
+ }
53
+ }
54
+
55
+ pub fn check_maybe_present ( & self ) -> bool {
56
+ let Ok ( lines) = read_file_as_space_separated_lines ( "/proc/uptime" ) else {
57
+ return false ;
58
+ } ;
59
+
60
+ if lines. is_empty ( ) {
61
+ return false ;
62
+ }
63
+
64
+ let line = & lines[ 0 ] ;
65
+ if line. len ( ) != 2 {
66
+ return false ;
67
+ }
68
+
69
+ let Ok ( uptime) = line[ 0 ] . parse :: < f64 > ( ) else {
70
+ return false ;
71
+ } ;
72
+
73
+ if uptime < 60.0 {
74
+ return true ;
75
+ }
76
+ false
77
+ }
11
78
}
12
79
13
80
impl Test for VirtualizedTest {
@@ -16,22 +83,18 @@ impl Test for VirtualizedTest {
16
83
}
17
84
18
85
fn run ( & self ) -> Result < Box < dyn TestResult > , ( ) > {
19
- let mut result = VirtualizedResult {
20
- visible : false ,
21
- uptime : 0 ,
86
+ let enabled = if let Some ( definite_runtime) = self
87
+ . check_definite_runtime_env ( )
88
+ . or ( self . check_definite_gvisor ( ) )
89
+ {
90
+ VirtualizationEnabled :: DefinitelyPresent ( definite_runtime)
91
+ } else if self . check_maybe_present ( ) {
92
+ VirtualizationEnabled :: MaybePresent
93
+ } else {
94
+ VirtualizationEnabled :: NotPresent
22
95
} ;
23
- if let Ok ( lines) = read_file_as_space_separated_lines ( "/proc/uptime" ) {
24
- if !lines. is_empty ( ) {
25
- let line = & lines[ 0 ] ;
26
- if line. len ( ) == 2 {
27
- if let Ok ( uptime) = line[ 0 ] . parse :: < f64 > ( ) {
28
- result. visible = true ;
29
- result. uptime = uptime as u64 ;
30
- }
31
- }
32
- }
33
- }
34
- Ok ( Box :: new ( result) )
96
+
97
+ Ok ( Box :: new ( VirtualizedResult { enabled } ) )
35
98
}
36
99
37
100
fn category ( & self ) -> crate :: TestCategory {
@@ -41,29 +104,22 @@ impl Test for VirtualizedTest {
41
104
42
105
impl TestResult for VirtualizedResult {
43
106
fn success ( & self ) -> bool {
44
- ! self . visible || self . uptime <= 10
107
+ matches ! ( self . enabled , VirtualizationEnabled :: DefinitelyPresent ( _ ) )
45
108
}
46
109
47
110
fn explain ( & self ) -> String {
48
- if self . success ( ) {
49
- return "separate kernel used for each container" . to_string ( ) ;
111
+ match & self . enabled {
112
+ VirtualizationEnabled :: DefinitelyPresent ( runtime) => format ! ( "virtualization runtime '{}' found" , runtime) ,
113
+ VirtualizationEnabled :: MaybePresent => "signs of virtualization detected, but couldn't definitively determine a virtualization method" . to_string ( ) ,
114
+ VirtualizationEnabled :: NotPresent => "virtualization not detected" . to_string ( ) ,
50
115
}
51
- "without virtualization, the kernel state is shared, opening escape attacks" . to_string ( )
52
116
}
53
117
54
118
fn fault_code ( & self ) -> String {
55
119
"AII2280" . to_string ( )
56
120
}
57
121
58
122
fn as_string ( & self ) -> String {
59
- if !self . visible {
60
- return "result not reliable" . to_string ( ) ;
61
- }
62
-
63
- if self . uptime <= 10 {
64
- return "virtualization in use" . to_string ( ) ;
65
- }
66
-
67
- "virtualization not in use" . to_string ( )
123
+ self . explain ( )
68
124
}
69
125
}
0 commit comments