Path: blob/main/crypto/openssl/doc/designs/quic-design/quic-fault-injector.md
34877 views
QUIC Fault Injector
The OpenSSL QUIC implementation receives QUIC packets from the network layer and processes them accordingly. It will need to behave appropriately in the event of a misbehaving peer, i.e. one which is sending protocol elements (e.g. datagrams, packets, frames, etc) that are not in accordance with the specifications or OpenSSL's expectations.
The QUIC Fault Injector is a component within the OpenSSL test framework that can be used to simulate misbehaving peers and confirm that OpenSSL QUIC implementation behaves in the expected manner in the event of such misbehaviour.
Typically an individual test will inject one particular misbehaviour (i.e. a fault) into an otherwise normal QUIC connection. Therefore the fault injector will have to be capable of creating fully normal QUIC protocol elements, but also offer the flexibility for a test to modify those normal protocol elements as required for the specific test circumstances. The OpenSSL QUIC implementation in libssl does not offer the capability to send faults since it is designed to be RFC compliant.
The QUIC Fault Injector will be external to libssl (it will be in the test framework) but it will reuse the standards compliant QUIC implementation in libssl and will make use of 3 integration points to inject faults. 2 of these integration points will use new callbacks added to libssl. The final integration point does not require any changes to libssl to work.
QUIC Integration Points
TLS Handshake
Fault Injector based tests may need to inject faults directly into the TLS handshake data (i.e. the contents of CRYPTO frames). However such faults may need to be done in handshake messages that would normally be encrypted. Additionally the contents of handshake messages are hashed and each peer confirms that the other peer has the same calculated hash value as part of the "Finished" message exchange - so any modifications would be rejected and the handshake would fail.
An example test might be to confirm that an OpenSSL QUIC client behaves correctly in the case that the server provides incorrectly formatted transport parameters. These transport parameters are sent from the server in the EncryptedExtensions message. That message is encrypted and so cannot be modified by a "man-in-the-middle".
To support this integration point two new callbacks will be introduced to libssl that enables modification of handshake data prior to it being encrypted and hashed. These callbacks will be internal only (i.e. not part of the public API) and so only usable by the Fault Injector.
The new libssl callbacks will be as follows:
The two callbacks are set via a single internal function call ossl_statem_set_mutator
. The mutator callback mutate_handshake_cb
will be called after each handshake message has been constructed and is ready to send, but before it has been passed through the handshake hashing code. It will be passed a pointer to the constructed handshake message in msgin
along with its associated length in inlen
. The mutator will construct a replacement handshake message (typically by copying the input message and modifying it) and store it in a newly allocated buffer. A pointer to the new buffer will be passed back in *msgout
and its length will be stored in *outlen
. Optionally the mutator can choose to not mutate by simply creating a new buffer with a copy of the data in it. A return value of 1 indicates that the callback completed successfully. A return value of 0 indicates a fatal error.
Once libssl has finished using the mutated buffer it will call the finish_mutate_handshake_cb
callback which can then release the buffer and perform any other cleanup as required.
QUIC Pre-Encryption Packets
QUIC Packets are the primary mechanism for exchanging protocol data within QUIC. Multiple packets may be held within a single datagram, and each packet may itself contain multiple frames. A packet gets protected via an AEAD encryption algorithm prior to it being sent. Fault Injector based tests may need to inject faults into these packets prior to them being encrypted.
An example test might insert an unrecognised frame type into a QUIC packet to confirm that an OpenSSL QUIC client handles it appropriately (e.g. by raising a protocol error).
The above functionality will be supported by the following two new callbacks which will provide the ability to mutate packets before they are encrypted and sent. As for the TLS callbacks these will be internal only and not part of the public API.
A single new function call will set both callbacks. The mutatecb
callback will be invoked after each packet has been constructed but before protection has been applied to it. The header for the packet will be pointed to by hdrin
and the payload will be in an iovec array pointed to by iovecin
and containing numin
iovecs. The mutatecb
callback is expected to allocate a new header structure and return it in *hdrout
and a new set of iovecs to be stored in *iovecout
. The number of iovecs need not be the same as the input. The number of iovecs in the output array is stored in *numout
. Optionally the callback can choose to not mutate by simply creating new iovecs/headers with a copy of the data in it. A return value of 1 indicates that the callback completed successfully. A return value of 0 indicates a fatal error.
Once the OpenSSL QUIC implementation has finished using the mutated buffers the finishmutatecb
callback is called. This is expected to free any resources and buffers that were allocated as part of the mutatecb
call.
QUIC Datagrams
Encrypted QUIC packets are sent in datagrams. There may be more than one QUIC packet in a single datagram. Fault Injector based tests may need to inject faults directly into these datagrams.
An example test might modify an encrypted packet to confirm that the AEAD decryption process rejects it.
In order to provide this functionality the QUIC Fault Injector will insert itself as a man-in-the-middle between the client and server. A BIO_s_dgram_pair() will be used with one of the pair being used on the client end and the other being associated with the Fault Injector. Similarly a second BIO_s_dgram_pair() will be created with one used on the server and other used with the Fault Injector.
With this setup the Fault Injector will act as a proxy and simply pass datagrams sent from the client on to the server, and vice versa. Where a test requires a modification to be made, that will occur prior to the datagram being sent on.
This will all be implemented using public BIO APIs without requiring any additional internal libssl callbacks.
Fault Injector API
The Fault Injector will utilise the callbacks described above in order to supply a more test friendly API to test authors.
This API will primarily take the form of a set of event listener callbacks. A test will be able to "listen" for a specific event occurring and be informed about it when it does. Examples of events might include:
An EncryptedExtensions handshake message being sent
An ACK frame being sent
A Datagram being sent
Each listener will be provided with additional data about the specific event. For example a listener that is listening for an EncryptedExtensions message will be provided with the parsed contents of that message in an easy to use structure. Additional helper functions will be provided to make changes to the message (such as to resize it).
Initially listeners will only be able to listen for events on the server side. This is because, in MVP, it will be the client side that is under test - so the faults need to be injected into protocol elements sent from the server. Post MVP this will be extended in order to be able to test the server. It may be that we need to do this during MVP in order to be able to observe protocol elements sent from the client without modifying them (i.e. in order to confirm that the client is behaving as we expect). This will be added if required as we develop the tests.
It is expected that the Fault Injector API will expand over time as new listeners and helper functions are added to support specific test scenarios. The initial API will provide a basic set of listeners and helper functions in order to provide the basis for future work.
The following outlines an illustrative set of functions that will initially be provided. A number of TODO(QUIC TESTING)
comments are inserted to explain how we might expand the API over time:
Example Tests
This section provides some example tests to illustrate how the Fault Injector might be used to create tests.
Unknown Frame Test
An example test showing a server sending a frame of an unknown type to the client:
No Transport Parameters test
An example test showing the case where a server does not supply any transport parameters in the TLS handshake: