@@ -1104,3 +1104,116 @@ func TestUpdateVMMetadata_Isolated(t *testing.T) {
1104
1104
t .Logf ("stdout output from task %q: %s" , containerName , stdout )
1105
1105
assert .Equalf (t , "45" , stdout , "container %q did not emit expected stdout" , containerName )
1106
1106
}
1107
+
1108
+ // TestRandomness validates that there is a reasonable amount of entropy available to the VM and thus
1109
+ // randomness available to containers (test reads about 2.5MB from /dev/random w/ an overall test
1110
+ // timeout of 60 seconds). It also validates that the quality of the randomness passes the rngtest
1111
+ // utility's suite.
1112
+ func TestRandomness_Isolated (t * testing.T ) {
1113
+ prepareIntegTest (t )
1114
+
1115
+ ctx , cancel := context .WithTimeout (namespaces .WithNamespace (context .Background (), defaultNamespace ), 60 * time .Second )
1116
+ defer cancel ()
1117
+
1118
+ client , err := containerd .New (containerdSockPath , containerd .WithDefaultRuntime (firecrackerRuntime ))
1119
+ require .NoError (t , err , "unable to create client to containerd service at %s, is containerd running?" , containerdSockPath )
1120
+ defer client .Close ()
1121
+
1122
+ image , err := alpineImage (ctx , client , defaultSnapshotterName ())
1123
+ require .NoError (t , err , "failed to get alpine image" )
1124
+ containerName := "test-entropy"
1125
+
1126
+ const blockCount = 1024
1127
+ ddContainer , err := client .NewContainer (ctx ,
1128
+ containerName ,
1129
+ containerd .WithSnapshotter (defaultSnapshotterName ()),
1130
+ containerd .WithNewSnapshot ("test-entropy-snapshot" , image ),
1131
+ containerd .WithNewSpec (
1132
+ oci .WithDefaultUnixDevices ,
1133
+ // Use blocksize of 2500 as rngtest consumes data in blocks of 2500 bytes.
1134
+ oci .WithProcessArgs ("/bin/dd" , "iflag=fullblock" , "if=/dev/random" , "of=/dev/stdout" , "bs=2500" ,
1135
+ fmt .Sprintf ("count=%d" , blockCount )),
1136
+ ),
1137
+ )
1138
+ require .NoError (t , err , "failed to create container %s" , containerName )
1139
+
1140
+ // rngtest is a utility to "check the randomness of data using FIPS 140-2 tests", installed as part of
1141
+ // the container image this test is running in. We pipe the output from "dd if=/dev/random" to rngtest
1142
+ // to validate the quality of the randomness inside the VM.
1143
+ // TODO It would be conceptually simpler to just run rngtest inside the container in the VM, but
1144
+ // doing so would require some updates to our test infrastructure to support custom-built container
1145
+ // images running in VMs (right now it's only feasible to use publicly available container images).
1146
+ // Right now, it's instead run as a subprocess of this test outside the VM.
1147
+ var rngtestStdout bytes.Buffer
1148
+ var rngtestStderr bytes.Buffer
1149
+ rngtestCmd := exec .CommandContext (ctx , "rngtest" ,
1150
+ // we set this to 1 less than the number of blocks read by dd above to account for the fact that
1151
+ // the first 32 bits read by rngtest are not used for the tests themselves
1152
+ fmt .Sprintf ("--blockcount=%d" , blockCount - 1 ),
1153
+ )
1154
+ rngtestCmd .Stdout = & rngtestStdout
1155
+ rngtestCmd .Stderr = & rngtestStderr
1156
+ rngtestStdin , err := rngtestCmd .StdinPipe ()
1157
+ require .NoError (t , err , "failed to get pipe to rngtest command's stdin" )
1158
+
1159
+ ddStdout := rngtestStdin
1160
+ var ddStderr bytes.Buffer
1161
+
1162
+ task , err := ddContainer .NewTask (ctx , cio .NewCreator (cio .WithStreams (nil , ddStdout , & ddStderr )))
1163
+ require .NoError (t , err , "failed to create task for dd container" )
1164
+
1165
+ exitCh , err := task .Wait (ctx )
1166
+ require .NoError (t , err , "failed to wait on task for dd container" )
1167
+
1168
+ err = task .Start (ctx )
1169
+ require .NoError (t , err , "failed to start task for dd container" )
1170
+
1171
+ err = rngtestCmd .Start ()
1172
+ require .NoError (t , err , "failed to start rngtest" )
1173
+
1174
+ select {
1175
+ case exitStatus := <- exitCh :
1176
+ assert .NoError (t , exitStatus .Error (), "failed to retrieve exitStatus" )
1177
+ assert .EqualValues (t , 0 , exitStatus .ExitCode ())
1178
+
1179
+ status , err := task .Delete (ctx )
1180
+ assert .NoErrorf (t , err , "failed to delete dd task after exit" )
1181
+ if status != nil {
1182
+ assert .NoError (t , status .Error ())
1183
+ }
1184
+
1185
+ t .Logf ("stderr output from dd:\n %s" , ddStderr .String ())
1186
+ case <- ctx .Done ():
1187
+ require .Fail (t , "context cancelled" ,
1188
+ "context cancelled while waiting for dd container to exit (is it blocked on reading /dev/random?), err: %v" , ctx .Err ())
1189
+ }
1190
+
1191
+ err = rngtestCmd .Wait ()
1192
+ t .Logf ("stdout output from rngtest:\n %s" , rngtestStdout .String ())
1193
+ t .Logf ("stderr output from rngtest:\n %s" , rngtestStderr .String ())
1194
+ if err != nil {
1195
+ // rngtest will exit non-zero if any blocks fail its randomness tests.
1196
+ // Trials showed an approximate false-negative rate of 27/32863 blocks,
1197
+ // so testing on 1023 blocks gives a ~36% chance of there being a single
1198
+ // false-negative. The chance of there being 5 or more drops down to
1199
+ // about 0.1%, which is an acceptable flakiness rate, so we assert
1200
+ // that there are no more than 4 failed blocks.
1201
+ // Even though we have a failure tolerance, the test still provides some
1202
+ // value in that we can be aware if a change to the rootfs results in a
1203
+ // regression.
1204
+ require .EqualValues (t , 1 , rngtestCmd .ProcessState .ExitCode ())
1205
+ const failureTolerance = 4
1206
+
1207
+ for _ , outputLine := range strings .Split (rngtestStderr .String (), "\n " ) {
1208
+ var failureCount int
1209
+ _ , err := fmt .Sscanf (strings .TrimSpace (outputLine ), "rngtest: FIPS 140-2 failures: %d" , & failureCount )
1210
+ if err == nil {
1211
+ if failureCount > failureTolerance {
1212
+ require .Failf (t , "too many d block test failures from rngtest" ,
1213
+ "%d failures is greater than tolerance of up to %d failures" , failureCount , failureTolerance )
1214
+ }
1215
+ break
1216
+ }
1217
+ }
1218
+ }
1219
+ }
0 commit comments