0

Trying to implement the exFAT boot checksum as described in section 3.4 of:

https://learn.microsoft.com/en-us/windows/win32/fileio/exfat-specification

My code does not produce the correct checksums. :(

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>

int main ( int argc, char *argv[] )
{
/* test for filename in parameters */
if ( argc != 2 )
    {
    /* assume argv[0] has the program name */
    printf( "usage: %s filename", argv[0] );
    }
else 
    {
    /* assume argv[1] has the filename to process */
    FILE *filename = fopen( argv[1], "rb" );

    /* check that file exists */
    if ( filename == 0 )
        {
        printf( "Could not open %s\n", argv[1] );
        exit (1);
        }
    else 
        {
        unsigned char cbytes[5632];
        int ibytes = fread(cbytes, 1, sizeof(cbytes), filename);
        if (ibytes != 5632)
            {
            printf( "Can't read 5632 bytes from %s\n", argv[1] );
            exit (1);
            }
        fclose( filename );

        uint32_t chksum=0;

        for (int index = 0; index < 5632; index++)
            {
            if ((index == 106) || (index == 107) || (index == 112))
                { continue; }
            chksum = ((chksum&1) ? 0x80000000 : 0) + (chksum>>1) + cbytes[index];
            }

        printf("%8x\n", chksum);
        }
    }
}

Yes I have examined this past question (the author apparently never could get the correct checksums either).

Calculation of exFAT checksum

Can anyone spot what I have done wrong?

confused
  • 47
  • 5
  • Try `unsigned char cbytes[5632];`, as the MS page does, and the linked question suggests. – user58697 Feb 20 '21 at 02:05
  • It does make a difference (outputs different resuts), but it still doesn't produce the correct values. – confused Feb 20 '21 at 02:27
  • How about presenting the code you're *really* using? The code you've actually presented does not appear to be it, for it misspells type `uint32_t` in a couple of places. – John Bollinger Feb 20 '21 at 02:28
  • 1
    You appear to be assuming 512-byte sectors, but exFAT permits other sector sizes. There is a field in the boot sector that conveys the sector size. – John Bollinger Feb 20 '21 at 02:31
  • That is **exactly** the code I compiled with gcc and used. I will recheck the spelling through. :) Yes, I did assume 512-byte sectors (I have never seen anything else on flash media), and in this case, offset 108 does indeed have 0x09 (512 bytes). – confused Feb 20 '21 at 02:52
  • If I use uint32_t, it fails to compile: ```xftchksm.c:34:9: error: unknown type name 'uint32_t'; did you mean 'u_int32_t'? uint32_t chksum=0; ^~~~~~~~ u_int32_t``` – confused Feb 20 '21 at 02:54
  • Then you should include `stdint.h`. I've no idea where `u_int32_t` is coming from -- it is not part of any C standard. – John Bollinger Feb 20 '21 at 02:55
  • Okay, I did that (include stdint.h), and now the compiler accepts uint32_t (thank you). Still doesn't produce the correct checksums, though. :( – confused Feb 20 '21 at 03:30

1 Answers1

0

Supposing that there were

#include <stdint.h>
typedef uint32_t u_int32_t;

appearing before your main(), there are two primary issues I would see with your code:

  1. As I already remarked in comments, exFAT provides for a variety of sector sizes, and the checksum is over sectors of whatever size is in use on the filesystem. This is why the example code in the MS docs accepts a parameter specifying the sector size. Your code assumes 512-byte sectors, which not only will give you the wrong checksum when a different sector size is in use, but will also lead you to write the wrong amount of checksum data in the wrong location in that case.

  2. Even if the sector size chosen for the particular exFAT filesystem is indeed 512 bytes, you are performing the sum differently from the MS example code in the (likely) event that your implementation's char is signed rather than unsigned. It would be best to simply declare cbytes as an array of unsigned char. Alternatively, with it declared as (signed) char, casting elements to unsigned char or uint8_t would produce an equivalent result -- different in many cases from the result of casting to uint32_t, which isn't necessary anyway when the value is already being interpreted as unsigned.

Other than those two (significant) factors, your computation appears to be equivalent to the MS example code.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
  • Before I continue, I do want to describe myself as a complete C novice (most familiar with perl). Per the suggestion of user58697, I had already changed the declaration of cbyets to unsignd (question now edited). I can't get your typedef statement to work (and don't understand the reason for it if I already have the equivalent). ```xftchksm.c:3:9: error: unknown type name 'uint32_t' typedef uint32_t u_int32_t; ^~~~~~~~ xftchksm.c:3:18: error: conflicting types for 'u_int32_t' typedef uint32_t u_int32_t; ^~~~~~~~~``` – confused Feb 20 '21 at 03:12
  • For your point 1., yes I recognize the exFat provides a variety of different sector sizes. I will worry about that (with an extra paramenter), after I get a "base case" working. For point 2., I have now declared cbytes as unsigned char. You (correctly) pointed out that I don't need to cast to u_int32_t (I get the same results) with cbytes declared as unsigned char. Still can't get correct checksums, though. – confused Feb 20 '21 at 03:20
  • @confused, it is *particularly* concerning that your implementation complains about the typedef, as the diagnostic you report suggests that whatever `u_int32_t` is for you, and whyever it is declared, it does not mean what `uint32_t` means: an unsigned integer type with exactly 32 value bits and no padding bits. I suggest forgetting about the typedef, including `stdint.h`, and simply changing your uses of `u_unit32_t` to standard `uint32_t`. – John Bollinger Feb 20 '21 at 03:34
  • If, in conjunction with changing the type of `cbytes`, that does not give you the correct checksum, then possibly you are checksumming the wrong device (e.g. `/dev/sdb` instead of `/dev/sdb1`, or similar), or perhaps the MS example code is incorrect, but your code matches MS's. – John Bollinger Feb 20 '21 at 03:37
  • However, @confused, you could consider replacing the awkward `((chksum&1) ? 0x80000000 : 0)` with the simpler `(chksum << 31)`. It oughtn't to make a difference if the data types are right, but it will at least be cleaner. – John Bollinger Feb 20 '21 at 03:43
  • I made that change (thank you again), and finally realized that things basically worked as soon as I changed the declaration to unsigned char. I didn't realize it because I basically used the wrong format to print the result (eight hexadecimal digits rather than the hex representation of a 32-bit integer). Thank you so much for your help. – confused Feb 20 '21 at 04:25