plugins/checkpoint: add checkpoint plugin

The checkpoint plugin watches for writes on the given symbol and logs
the written value and the simulation time to a given output file.
Additionally, a SHA1 hash is computed over all memory locations between
given start and stop symbols.

On x86, virtual memory is disabled while computing the checkpoint hash.
This means the checkpoint plugin in checksumming over physical, not
virtual, address ranges! This can result to unexpected behaviour if
virtual memory is not used for identity paging.

To save checkpoint information to a file, use the Checkpoint constructor
with a given checkpoint symbol and add the plugin to the experiment (flow).
To check checkpoints against an existing file, use the constructor without
memory symbol and do not add the plugin to the experiment. Instead, define
a memory listener "manually" and call the check() function. This approach
was taken as the simplest form of cooperation between experiment and
plugins.

For SHA1 calculation, C code from RFC 3174 is used to prevent depending
on another external library. However, this may not be the fastest or
best code for the task.

TEMPORARY HACK/WORKAROUND:
Since dOSEK uses the highest bit (31) of some pointers for parity and
the checksum plugin reads these (stack) pointers to determine checksum
regions, the plugin currently DISCARDS BIT 31 of pointers used as
dynamic region limits.
This will be replaced in the future by a callback mechanism, which lets
the experiment specify the regions to checksum (called at each checkpoint).

Change-Id: I176eccc34b582bbf13e52b6943191dd20258acc5
This commit is contained in:
Florian Lukas
2014-02-26 14:19:22 +01:00
committed by Christian Dietrich
parent 0426970dff
commit bdfacbe605
5 changed files with 822 additions and 0 deletions

View File

@ -0,0 +1,11 @@
set(PLUGIN_NAME checkpoint)
set(MY_PLUGIN_SRCS
Checkpoint.cc
Checkpoint.hpp
sha1.c
)
include_directories(${CMAKE_CURRENT_BINARY_DIR})
## Build library
add_library(fail-${PLUGIN_NAME} ${MY_PLUGIN_SRCS})

View File

@ -0,0 +1,230 @@
#include "Checkpoint.hpp"
#include "sal/Listener.hpp"
#include "sal/Memory.hpp"
#include "sha1.h"
#include <cstring>
#include "sal/bochs/BochsCPU.hpp"
#include "sal/SALConfig.hpp"
using namespace std;
using namespace fail;
bool Checkpoint::run()
{
assert(!m_checking && "FATAL: Checkpoint plugin must not be added to simulation in checking mode");
// log information
m_log << "Checkpoint Logger started." << std::endl;
m_log << "Triggering on: " << m_symbol << std::endl;
m_log << "Writing output to: " << m_file << std::endl;
/*
std::vector<address_range>::const_iterator it = m_check_ranges.begin();
for( ; it != m_check_ranges.end(); ++it) {
m_log << "Checksumming: " <<
<< ((it->first.second) ? "*" : "")
<< "0x" << std::hex << it->first.first
<< " - " <<
<< (it->second.second) ? "*" : ""
<< "0x" << std::hex << it->second.first
<< std::endl;
}
*/
if (!m_ostream.is_open()) {
m_log << "No output file." << std::endl;
return false;
}
// listen for memory writes and save checkpoints
MemWriteListener ev_mem(m_symbol.getAddress());
while (true) {
simulator.addListenerAndResume(&ev_mem);
save_checkpoint(ev_mem.getTriggerInstructionPointer());
}
return true;
}
address_t Checkpoint::resolve_address(const indirectable_address_t &addr) {
if(addr.second) {
const address_t paddr = addr.first;
address_t value = 0;
MemoryManager& mm = simulator.getMemoryManager();
if(mm.isMapped(paddr) && mm.isMapped(paddr+1) && mm.isMapped(paddr+2) && mm.isMapped(paddr+3)) {
simulator.getMemoryManager().getBytes(paddr, 4, &value);
}
// HACK/WORKAROUND for dOSEK, which uses bit 31 for parity!
// This fixes checkpoint ranges for dOSEK, but breaks other systems *if*
// addresses with bit 31 set are used as the limit for checkpoint regions
return value & ~(1<<31);
} else {
return addr.first;
}
}
void Checkpoint::checksum(uint8_t (&Message_Digest)[20])
{
SHA1Context sha;
int err;
// prepare SHA1 hash
err = SHA1Reset(&sha);
assert(err == 0);
MemoryManager& mm = simulator.getMemoryManager();
// disable paging on x86
#ifdef BUILD_X86
const Register *reg_cr0 = simulator.getCPU(0).getRegister(RID_CR0);
uint32_t cr0 = simulator.getCPU(0).getRegisterContent(reg_cr0);
simulator.getCPU(0).setRegisterContent(reg_cr0, cr0 & ~(1<<31));
#endif
// iterate memory regions
std::vector<address_range>::const_iterator it = m_check_ranges.begin();
for( ; it != m_check_ranges.end(); ++it) {
fail::address_t start = resolve_address(it->first);
fail::address_t end = resolve_address(it->second);
if((start == 0) || (end == 0)) {
m_log << std::hex << "invalid checksum range pointer" << std::endl;
continue;
}
//m_log << std::hex << "checksumming 0x" << start << " - 0x" << end << std::endl;
for(fail::address_t addr = start; addr < end; addr++) {
if(mm.isMapped(addr)) {
// read byte
uint8_t data = mm.getByte(addr);
// add to hash
err = SHA1Input(&sha, &data, 1);
} else {
err = SHA1Input(&sha, (uint8_t*) &addr, 4);
}
assert(err == 0);
}
}
// restore paging on x86
#ifdef BUILD_X86
simulator.getCPU(0).setRegisterContent(reg_cr0, cr0);
#endif
// complete hash
err = SHA1Result(&sha, Message_Digest);
assert(err == 0);
}
void Checkpoint::checkpoint(const fail::ElfSymbol symbol,
uint32_t &value,
fail::simtime_t &simtime,
std::string &digest_str)
{
// increment checkpoint count
m_count++;
// timestamp
simtime = simulator.getTimerTicks();
// written value
address_t addr = symbol.getAddress();
MemoryManager& mm = simulator.getMemoryManager();
if(mm.isMapped(addr) && mm.isMapped(addr+1) && mm.isMapped(addr+2) && mm.isMapped(addr+3)) {
mm.getBytes(symbol.getAddress(), symbol.getSize(), &value);
} else {
value = 0xDEADBEEF; // TODO: invalid value?
}
// checksum
uint8_t digest[20];
checksum(digest);
// checksum to string
std::stringstream s;
s.fill('0');
for ( size_t i = 0 ; i < 20 ; ++i )
s << std::setw(2) << std::hex <<(unsigned short)digest[i];
digest_str = s.str();
}
void Checkpoint::save_checkpoint(fail::address_t ip)
{
uint32_t value;
fail::simtime_t simtime;
std::string digest;
// get checkpoint info
checkpoint(m_symbol, value, simtime, digest);
// log checkpoint
m_log << std::dec << "Checkpoint " << m_count << " @ " << simtime << std::endl;
// write checkpoint
if (m_ostream.is_open()) {
m_ostream << std::hex << ip << "\t" << std::dec << simtime << "\t" << value << "\t" << digest << std::endl;
} else {
m_log << "Output error" << std::endl;
}
}
Checkpoint::check_result Checkpoint::check(const fail::ElfSymbol symbol, fail::address_t ip)
{
uint32_t value;
fail::simtime_t simtime;
std::string digest;
address_t golden_ip;
uint32_t golden_timestamp;
uint32_t golden_value;
char golden_digest_hex[41];
assert(m_checking && "FATAL: Checkpoint plugin cannot check in tracing mode");
// get checkpoint info
checkpoint(symbol, value, simtime, digest);
// check with log
if (!m_istream.is_open()) {
m_log << "Input file not open!" << std::endl;
return INVALID;
}
if (m_istream.eof()) {
m_log << "Checkpoint after last golden checkpoint!" << std::endl;
return INVALID;
}
// read golden values
m_istream >> std::hex >> golden_ip;
m_istream >> std::dec >> golden_timestamp;
m_istream >> std::dec >> golden_value;
m_istream.width(41);
m_istream >> golden_digest_hex;
std::string golden_digest = golden_digest_hex;
//bool same_timestamp = simtime == golden_timestamp);
bool same_ip = ip == golden_ip;
bool same_value = value == golden_value;
bool same_digest = digest == golden_digest;
if (!same_ip || !same_value || !same_digest) {
// log
m_log << "GOLDEN:" << std::hex << golden_ip << "\t" << std::dec << golden_timestamp << "\t" << golden_value << "\t" << golden_digest << std::endl;
m_log << "TEST: " << std::hex << ip << "\t" << std::dec << simtime << "\t" << value << "\t" << digest << std::endl;
}
if(!same_value) return DIFFERENT_VALUE;
if(!same_ip) return DIFFERENT_IP;
if(!same_digest) return DIFFERENT_DIGEST;
return IDENTICAL;
}

View File

@ -0,0 +1,117 @@
#ifndef __Checkpoint_HPP__
#define __Checkpoint_HPP__
#include <string>
#include <vector>
#include <utility>
#include <unistd.h>
#include "efw/ExperimentFlow.hpp"
#include "config/FailConfig.hpp"
#include "util/Logger.hpp"
#include <fstream>
#include "util/ElfReader.hpp"
/**
* @class Checkpoint
* @brief Listens to a memory location and outputs instruction pointer, written value,
* timestamp and SHA1 hash of memory regions to file on each write access from SUT
*/
class Checkpoint : public fail::ExperimentFlow
{
public:
typedef std::pair<fail::address_t,bool> indirectable_address_t; //!< fixed address or pointer to address
typedef std::pair<indirectable_address_t,indirectable_address_t> address_range; //!< contiguous memory region
typedef std::vector<address_range> range_vector; //!< vector of memory regions
enum check_result {
IDENTICAL,
DIFFERENT_IP,
DIFFERENT_VALUE,
DIFFERENT_DIGEST,
INVALID
};
private:
const fail::ElfSymbol m_symbol; //!< target memory symbol triggering checkpoints
const range_vector m_check_ranges; //!< address ranges to checksum
const bool m_checking; //!< checking mode (when false: tracing mode)
std::string m_file; //!< the input/output filename
fail::Logger m_log; //!< debug output
std::ofstream m_ostream; //!< outputfile stream
std::ifstream m_istream; //!< inputfile stream
unsigned m_count;
public:
/**
* Construct Checkpoint Logger in tracing (output) mode.
*
* @param symbol The global memory address the plugin listens to
* @param check_ranges Address ranges which are included in the saved checksum
* @param outputfile The path to the file to write the checkpoints
*/
Checkpoint(const fail::ElfSymbol & symbol,
const std::vector<address_range> check_ranges,
const std::string& outputfile) :
m_symbol(symbol), m_check_ranges(check_ranges), m_checking(false),
m_file(outputfile) , m_log("CPLogger", false), m_count(0)
{
m_ostream.open(m_file.c_str() );
if (!m_ostream.is_open()) {
m_log << "Could not open " << m_file.c_str() << " for writing." << std::endl;
}
}
/**
* Construct Checkpoint Logger in checking (input) mode.
*
* @param symbol The global memory address the plugin listens to
* @param check_ranges Address ranges which are compared to the saved checksum
* @param inputfile The path to the file to read in checkpoints
*/
Checkpoint(const std::vector<address_range> check_ranges,
const std::string& inputfile) :
m_check_ranges(check_ranges), m_checking(true), m_file(inputfile),
m_log("CPLogger", false), m_count(0)
{
m_istream.open(m_file.c_str() );
if (!m_istream.is_open()) {
m_log << "Could not open " << m_file.c_str() << " for reading." << std::endl;
}
}
//! How many checkpoints have been triggered so far
unsigned getCount() const {
return m_count;
}
//! Start plugin control flow for tracing mode. Do not call in checking mode!
bool run();
/**
* Perform a check against saved checkpoints. Call this method in experiment when
* a MemWriteBreakpoint on symbol triggers.
*
* @param symbol (memory) symbol which triggered checkpoint
* @param ip instruction pointer which triggered checkpoint
* @return check result
*/
check_result check(const fail::ElfSymbol symbol, fail::address_t ip);
private:
//! calulate checksum over memory regions
void checksum(uint8_t (&Message_Digest)[20]);
//! extract checkpoint information from simulation
void checkpoint(const fail::ElfSymbol symbol,
uint32_t &value,
fail::simtime_t &simtime,
std::string &digest_str);
//! save checkpoint to file
void save_checkpoint(fail::address_t ip);
//! get value of indirectable_address_t
fail::address_t resolve_address(const indirectable_address_t &addr);
};
#endif // __Checkpoint_HPP__

View File

@ -0,0 +1,392 @@
/*
* sha1.c
*
* Copyright (C) The Internet Society (2001). All Rights Reserved.
* This file is taken from RFC3174.
*
* Description:
* This file implements the Secure Hashing Algorithm 1 as
* defined in FIPS PUB 180-1 published April 17, 1995.
*
* The SHA-1, produces a 160-bit message digest for a given
* data stream. It should take about 2**n steps to find a
* message with the same digest as a given message and
* 2**(n/2) to find any two messages with the same digest,
* when n is the digest size in bits. Therefore, this
* algorithm can serve as a means of providing a
* "fingerprint" for a message.
*
* Portability Issues:
* SHA-1 is defined in terms of 32-bit "words". This code
* uses <stdint.h> (included via "sha1.h" to define 32 and 8
* bit unsigned integer types. If your C compiler does not
* support 32 bit unsigned integers, this code is not
* appropriate.
*
* Caveats:
* SHA-1 is designed to work with messages less than 2^64 bits
* long. Although SHA-1 allows a message digest to be generated
* for messages of any number of bits less than 2^64, this
* implementation only works with messages with a length that is
* a multiple of the size of an 8-bit character.
*
*/
#include "sha1.h"
/*
* Define the SHA1 circular left shift macro
*/
#define SHA1CircularShift(bits,word) \
(((word) << (bits)) | ((word) >> (32-(bits))))
/* Local Function Prototyptes */
void SHA1PadMessage(SHA1Context *);
void SHA1ProcessMessageBlock(SHA1Context *);
/*
* SHA1Reset
*
* Description:
* This function will initialize the SHA1Context in preparation
* for computing a new SHA1 message digest.
*
* Parameters:
* context: [in/out]
* The context to reset.
*
* Returns:
* sha Error Code.
*
*/
int SHA1Reset(SHA1Context *context)
{
if (!context)
{
return shaNull;
}
context->Length_Low = 0;
context->Length_High = 0;
context->Message_Block_Index = 0;
context->Intermediate_Hash[0] = 0x67452301;
context->Intermediate_Hash[1] = 0xEFCDAB89;
context->Intermediate_Hash[2] = 0x98BADCFE;
context->Intermediate_Hash[3] = 0x10325476;
context->Intermediate_Hash[4] = 0xC3D2E1F0;
context->Computed = 0;
context->Corrupted = 0;
return shaSuccess;
}
/*
* SHA1Result
*
* Description:
* This function will return the 160-bit message digest into the
* Message_Digest array provided by the caller.
* NOTE: The first octet of hash is stored in the 0th element,
* the last octet of hash in the 19th element.
*
* Parameters:
* context: [in/out]
* The context to use to calculate the SHA-1 hash.
* Message_Digest: [out]
* Where the digest is returned.
*
* Returns:
* sha Error Code.
*
*/
int SHA1Result( SHA1Context *context,
uint8_t Message_Digest[SHA1HashSize])
{
int i;
if (!context || !Message_Digest)
{
return shaNull;
}
if (context->Corrupted)
{
return context->Corrupted;
}
if (!context->Computed)
{
SHA1PadMessage(context);
for(i=0; i<64; ++i)
{
/* message may be sensitive, clear it out */
context->Message_Block[i] = 0;
}
context->Length_Low = 0; /* and clear length */
context->Length_High = 0;
context->Computed = 1;
}
for(i = 0; i < SHA1HashSize; ++i)
{
Message_Digest[i] = context->Intermediate_Hash[i>>2]
>> 8 * ( 3 - ( i & 0x03 ) );
}
return shaSuccess;
}
/*
* SHA1Input
*
* Description:
* This function accepts an array of octets as the next portion
* of the message.
*
* Parameters:
* context: [in/out]
* The SHA context to update
* message_array: [in]
* An array of characters representing the next portion of
* the message.
* length: [in]
* The length of the message in message_array
*
* Returns:
* sha Error Code.
*
*/
int SHA1Input( SHA1Context *context,
const uint8_t *message_array,
unsigned length)
{
if (!length)
{
return shaSuccess;
}
if (!context || !message_array)
{
return shaNull;
}
if (context->Computed)
{
context->Corrupted = shaStateError;
return shaStateError;
}
if (context->Corrupted)
{
return context->Corrupted;
}
while(length-- && !context->Corrupted)
{
context->Message_Block[context->Message_Block_Index++] =
(*message_array & 0xFF);
context->Length_Low += 8;
if (context->Length_Low == 0)
{
context->Length_High++;
if (context->Length_High == 0)
{
/* Message is too long */
context->Corrupted = 1;
}
}
if (context->Message_Block_Index == 64)
{
SHA1ProcessMessageBlock(context);
}
message_array++;
}
return shaSuccess;
}
/*
* SHA1ProcessMessageBlock
*
* Description:
* This function will process the next 512 bits of the message
* stored in the Message_Block array.
*
* Parameters:
* None.
*
* Returns:
* Nothing.
*
* Comments:
* Many of the variable names in this code, especially the
* single character names, were used because those were the
* names used in the publication.
*
*
*/
void SHA1ProcessMessageBlock(SHA1Context *context)
{
const uint32_t K[] = { /* Constants defined in SHA-1 */
0x5A827999,
0x6ED9EBA1,
0x8F1BBCDC,
0xCA62C1D6
};
int t; /* Loop counter */
uint32_t temp; /* Temporary word value */
uint32_t W[80]; /* Word sequence */
uint32_t A, B, C, D, E; /* Word buffers */
/*
* Initialize the first 16 words in the array W
*/
for(t = 0; t < 16; t++)
{
W[t] = context->Message_Block[t * 4] << 24;
W[t] |= context->Message_Block[t * 4 + 1] << 16;
W[t] |= context->Message_Block[t * 4 + 2] << 8;
W[t] |= context->Message_Block[t * 4 + 3];
}
for(t = 16; t < 80; t++)
{
W[t] = SHA1CircularShift(1,W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16]);
}
A = context->Intermediate_Hash[0];
B = context->Intermediate_Hash[1];
C = context->Intermediate_Hash[2];
D = context->Intermediate_Hash[3];
E = context->Intermediate_Hash[4];
for(t = 0; t < 20; t++)
{
temp = SHA1CircularShift(5,A) +
((B & C) | ((~B) & D)) + E + W[t] + K[0];
E = D;
D = C;
C = SHA1CircularShift(30,B);
B = A;
A = temp;
}
for(t = 20; t < 40; t++)
{
temp = SHA1CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[1];
E = D;
D = C;
C = SHA1CircularShift(30,B);
B = A;
A = temp;
}
for(t = 40; t < 60; t++)
{
temp = SHA1CircularShift(5,A) +
((B & C) | (B & D) | (C & D)) + E + W[t] + K[2];
E = D;
D = C;
C = SHA1CircularShift(30,B);
B = A;
A = temp;
}
for(t = 60; t < 80; t++)
{
temp = SHA1CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[3];
E = D;
D = C;
C = SHA1CircularShift(30,B);
B = A;
A = temp;
}
context->Intermediate_Hash[0] += A;
context->Intermediate_Hash[1] += B;
context->Intermediate_Hash[2] += C;
context->Intermediate_Hash[3] += D;
context->Intermediate_Hash[4] += E;
context->Message_Block_Index = 0;
}
/*
* SHA1PadMessage
*
* Description:
* According to the standard, the message must be padded to an even
* 512 bits. The first padding bit must be a '1'. The last 64
* bits represent the length of the original message. All bits in
* between should be 0. This function will pad the message
* according to those rules by filling the Message_Block array
* accordingly. It will also call the ProcessMessageBlock function
* provided appropriately. When it returns, it can be assumed that
* the message digest has been computed.
*
* Parameters:
* context: [in/out]
* The context to pad
* ProcessMessageBlock: [in]
* The appropriate SHA*ProcessMessageBlock function
* Returns:
* Nothing.
*
*/
void SHA1PadMessage(SHA1Context *context)
{
/*
* Check to see if the current message block is too small to hold
* the initial padding bits and length. If so, we will pad the
* block, process it, and then continue padding into a second
* block.
*/
if (context->Message_Block_Index > 55)
{
context->Message_Block[context->Message_Block_Index++] = 0x80;
while(context->Message_Block_Index < 64)
{
context->Message_Block[context->Message_Block_Index++] = 0;
}
SHA1ProcessMessageBlock(context);
while(context->Message_Block_Index < 56)
{
context->Message_Block[context->Message_Block_Index++] = 0;
}
}
else
{
context->Message_Block[context->Message_Block_Index++] = 0x80;
while(context->Message_Block_Index < 56)
{
context->Message_Block[context->Message_Block_Index++] = 0;
}
}
/*
* Store the message length as the last 8 octets
*/
context->Message_Block[56] = context->Length_High >> 24;
context->Message_Block[57] = context->Length_High >> 16;
context->Message_Block[58] = context->Length_High >> 8;
context->Message_Block[59] = context->Length_High;
context->Message_Block[60] = context->Length_Low >> 24;
context->Message_Block[61] = context->Length_Low >> 16;
context->Message_Block[62] = context->Length_Low >> 8;
context->Message_Block[63] = context->Length_Low;
SHA1ProcessMessageBlock(context);
}

View File

@ -0,0 +1,72 @@
/*
* sha1.h
*
* Description:
* This is the header file for code which implements the Secure
* Hashing Algorithm 1 as defined in FIPS PUB 180-1 published
* April 17, 1995.
*
* Many of the variable names in this code, especially the
* single character names, were used because those were the names
* used in the publication.
*
* Please read the file sha1.c for more information.
*
*/
#ifndef _SHA1_H_
#define _SHA1_H_
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#ifndef _SHA_enum_
#define _SHA_enum_
enum
{
shaSuccess = 0,
shaNull, /* Null pointer parameter */
shaInputTooLong, /* input data too long */
shaStateError /* called Input after Result */
};
#endif
#define SHA1HashSize 20
/*
* This structure will hold context information for the SHA-1
* hashing operation
*/
typedef struct SHA1Context
{
uint32_t Intermediate_Hash[SHA1HashSize/4]; /* Message Digest */
uint32_t Length_Low; /* Message length in bits */
uint32_t Length_High; /* Message length in bits */
/* Index into message block array */
int_least16_t Message_Block_Index;
uint8_t Message_Block[64]; /* 512-bit message blocks */
int Computed; /* Is the digest computed? */
int Corrupted; /* Is the message digest corrupted? */
} SHA1Context;
/*
* Function Prototypes
*/
int SHA1Reset( SHA1Context *);
int SHA1Input( SHA1Context *,
const uint8_t *,
unsigned int);
int SHA1Result( SHA1Context *,
uint8_t Message_Digest[SHA1HashSize]);
#ifdef __cplusplus
}
#endif
#endif