1

I have the following task: Given source code A, modify it into source code B such that it can optionally enable fault injection. When the fault injection is disabled, it shall output the exact same binary as source code A. When fault injection is enabled, it can of course output some other binary. We want the exact same binary when fault injection is disabled to be certain it does not affect the production code. An example in code follows:

Source code A:

if (!allowedEntry) {
    printf("error: Cannot enter\n");
    return FAIL;
}

Source code B: (my changes)

#if defined(ENABLE_FAULT_INJECTION)
    #define IMPORT_FAULT_INJECTION(name) extern bool name
#else
    #define IMPORT_FAULT_INJECTION(name) bool name = false
#endif

// Fault injection variable is set/unset from external library
IMPORT_FAULT_INJECTION(injectNoAllowedEntry);
if (!allowedEntry || injectNoAllowedEntry) {
    printf("error: Cannot enter\n");
    return FAIL;
}

With optimization, the compiler will easily optimize source code A and B to produce similar outputs when ENABLE_FAULT_INJECTION is undefined. I have a script that verifies it by inspecting the resulting assembly output.

However, the problem: The resulting .bin file produced is different. I have at least seen that the compile-time is embedded into the resulting .bin file, which is a difference that does not stem from source code. My two questions are:

  1. Is there any way to disable the embedding of the compile-time into the .bin file? Or any way to override it with my own time? (I realize I could mitigate this issue by compiling the binaries at the same time, but that seems like a brittle solution to me.)

  2. Are there any other subtle differences I need to be aware of that do not stem from source code? And any ways to mitigate these?

9
  • Does adding const make a difference? GCC might worry that your boolean is accessed externally elsewhere. Commented Jul 4 at 9:26
  • @LukeSharkey The problem is not with the C source code. I have already verified the source code in both cases produce the exact same assembly code. Commented Jul 4 at 9:27
  • The compile timestamp is useful inside the bin file to prevent someone switching an old version or tweaked copy in intentionally or accidentally. Any tool checking the bin files needs to ignore the compiler generated timestamps. How different are they in practice? And is the datestamp in a recognisable fixed location? Things could get messy if the binary is losslessly compressed and the datestamp is at start of file. Commented Jul 4 at 9:28
  • @MartinBrown Good point. I am only compiling the binaries for knowing if they are the same and throwing them away after, so the timestamp will not be a problem here. But I see the usefulness in general. Commented Jul 4 at 9:31
  • 3
    You want reproducible builds, eg reproducible-builds.org/docs/source-date-epoch Commented Jul 4 at 11:53

2 Answers 2

2

I found the problem was with within the project code itself. (I'm not the maintainer of the code base, and it is quite large.)

One of the differences was a git commit hash that was integrated into the binary. When I was verifying that the binaries were equal, I was compiling the code on different commits. Therefore the git commit has was different.

The other difference was the timestamp. This was embedded in the binary because a third-party library was using the `__TIME__` macro.

So if others run into the same issue, looking into similar things might be the solution :)

Sign up to request clarification or add additional context in comments.

Comments

-1

Not an answer to the question as such but a suggested possible alternative method for fault injection that is too long to write as a comment. I think it is cleaner. YMMV

My suggested alternative approach to the fault injection code is as follows:

#if defined(ENABLE_FAULT_INJECTION)
  #include <FaultInjection.h>
#else
   #define INJECTFAULT(var, name)
#endif
...

#if defined(ENABLE_FAULT_INJECTION)
    #define INJECT_FAULT(var, name) var = name(var) // so the routine knows value of var
#endif

// Fault injection variable is set/unset from external library
INJECTFAULT(allowedEntry, injectNoAllowedEntry); // now alters live variable
if (!allowedEntry) {
    printf("error: Cannot enter\n");
    return FAIL;
}

The advantage being that the fault injection done this way affects all other future references to the affected variable that has been corrupted by fault injection without having to make any other code changes. So if the expected code fails to handle it something else hopefully will. Otherwise it is inevitable that the one conditional test where you have forgotten to add || injectNoAllowedEntry will be the latent fault.

This method also makes it possible to handle edge cases for numeric values as well with the expected valid range accepted displayed in the codebase with something along the lines of:

  #define INJECTNUMERICFAULT(var, minval, maxval, name) var = name(var, minval, maxval)

Handy for checking out fence post errors which are still too common in new code unfortunately.

FWIW I think simplest answer for the OP may well be to ignore the CRC check and date fields in the comparison. Possibly verify the CRC against the date(s) if you can figure it out.

5 Comments

Thank you for elaborating. This indeed seems like a more robust approach.
About your comment on the CRC check and dates. Are you sure it is actually a CRC checksum? When I mentioned checksum I was just guessing. I have not been able to find any documentation on either the timestamp or possible checksum - could you link to it if you have it available?
IDK but I'd be inclined to corrupt a single bit or byte of a string literal inside the bin file and see what error message results when you try to execute it to find out.
What's this var = name(var) syntax? godbolt.org/z/qYh5zco9x
@MargaretBloom I thought it was obvious from the context (but evidently it is not). The macro arguments are INJECTFAULT(variable_to_corrupt, function_to_injectfault). It is expected that bool injectNoAllowedEntry(bool b) is defined in FaultInjection.h and implemented in the appropriate external file just like the original code snippet. The syntax var = name(var) gives the fault injection logic knowledge of the present state of the variable in the program that it is supposed to inject a fault into.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.