From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: by sourceware.org (Postfix, from userid 2155) id 573D43858005; Mon, 7 Aug 2023 19:16:53 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 573D43858005 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=sourceware.org; s=default; t=1691435813; bh=fERGsKUjR/FTJ5/CfYSYgG/2HvIAiuSLwTxIois6PP0=; h=To:Subject:Date:From:From; b=ldHao7TnDk5Nvg8ye824K7NYzTEKyCClZdMTG7x6zCCfLL/g0rNR1gPA/4tIjT33r TUaO/O6JGOgbOMnhoHhJMEO4h+5yqPXwybH2S6OQ4oA6yB5Sl/HdT3Hgsh/XkwYZiR k17l+/Zl9fP3rRsNN36tJ3VLc4rES1PHm66ODcXs= To: cygwin-apps-cvs@sourceware.org Subject: [rebase - The rebase tool, core of the automatic rebase facility during postinstall] branch master, updated. bb79f3cdb48f90948ccc41621f77f32ab59495cb X-Git-Refname: refs/heads/master X-Git-Reftype: branch X-Git-Oldrev: 73c83a42d2efafd99fbee28b037fc28e75c5268b X-Git-Newrev: bb79f3cdb48f90948ccc41621f77f32ab59495cb Message-Id: <20230807191653.573D43858005@sourceware.org> Date: Mon, 7 Aug 2023 19:16:53 +0000 (GMT) From: Corinna Vinschen List-Id: https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/rebase.git;h=bb79f3cdb48f90948ccc41621f77f32ab59495cb commit bb79f3cdb48f90948ccc41621f77f32ab59495cb Author: Christian Franke Date: Mon Aug 7 15:52:14 2023 +0200 peflags: Add -k, --checksum option This allows to fix the file checksum in the PE header. An invalid checksum may break reproducible builds or may increase the risk of false positive malware detections. The checksum calculation is done by a new self-contained module 'pechecksum.c' which could also be built as a stand-alone tool or later added to rebase. Signed-off-by: Christian Franke https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/rebase.git;h=cdd3b966d77f68edbb7f1fba001930a41457df8e commit cdd3b966d77f68edbb7f1fba001930a41457df8e Author: Christian Franke Date: Mon Aug 7 14:02:12 2023 +0200 peflags: Add -p, --timestamp option This allows to set the header timestamp to 0 or some other fixed value (SOURCE_DATE_EPOCH) to support reproducible builds. Signed-off-by: Christian Franke Diff: --- Makefile.in | 6 +- pechecksum.c | 195 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ pechecksum.h | 25 ++++++++ peflags.c | 156 ++++++++++++++++++++++++++++++++++++++++++++--- 4 files changed, 371 insertions(+), 11 deletions(-) diff --git a/Makefile.in b/Makefile.in index 34c468417e71..46df1d54a8e9 100644 --- a/Makefile.in +++ b/Makefile.in @@ -80,7 +80,7 @@ REBASE_LIBS = $(LIBIMAGEHELPER) REBASE_DUMP_OBJS = rebase-dump.$(O) rebase-db.$(O) $(LIBOBJS) REBASE_DUMP_LIBS = -PEFLAGS_OBJS = peflags.$(O) $(LIBOBJS) +PEFLAGS_OBJS = peflags.$(O) pechecksum.$(O) $(LIBOBJS) PEFLAGS_LIBS = SRC_DISTFILES = configure.ac configure Makefile.in \ @@ -111,7 +111,9 @@ rebase-dump.$(O):: rebase-dump.c rebase-db.h Makefile peflags$(EXEEXT): $(PEFLAGS_OBJS) $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(PEFLAGS_OBJS) -peflags.$(O):: peflags.c Makefile +peflags.$(O):: peflags.c pechecksum.h Makefile + +pechecksum.$(O):: pechecksum.c pechecksum.h Makefile getopt.h: getopt.h_ cp $^ $@ diff --git a/pechecksum.c b/pechecksum.c new file mode 100644 index 000000000000..86951381f206 --- /dev/null +++ b/pechecksum.c @@ -0,0 +1,195 @@ +/* + * PE32 checksum + * + * Copyright (C) 2023 Christian Franke + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "pechecksum.h" + +#include +#include +#include + +static inline unsigned get_word(const unsigned char * p) +{ + return p[0] | (p[1] << 8); +} + +static inline unsigned get_dword(const unsigned char * p) +{ + return p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24); +} + +static inline void put_dword(unsigned char * p, unsigned val) +{ + p[0] = (unsigned char) val; + p[1] = (unsigned char)(val >> 8); + p[2] = (unsigned char)(val >> 16); + p[3] = (unsigned char)(val >> 24); +} + +unsigned pe32_checksum(int fd, int set, unsigned * old_checksum) +{ + // Read headers. + const unsigned bufsiz = 4096; + unsigned char buf[bufsiz]; + if (lseek(fd, 0, SEEK_SET) != 0) + return 0; + int nr = read(fd, buf, bufsiz); + if (nr < 0) + return 0; + if (nr <= 0x200) { + errno = EINVAL; + return 0; + } + + // IMAGE_DOS_HEADER.pe_magic == "MZ" ? + if (get_word(buf) != 0x5a4d) { + errno = EINVAL; + return 0; + } + // pehdr_offs = IMAGE_DOS_HEADER.lfa_new + unsigned pehdr_offs = get_dword(buf + 0x3c); + if (!(0x40 <= pehdr_offs && pehdr_offs <= nr - 0x100)) { + errno = EINVAL; + return 0; + } + // IMAGE_NT_HEADERS.Signature == "PE" ? + if (get_word(buf + pehdr_offs) != 0x4550) { + errno = EINVAL; + return 0; + } + // old_sum = IMAGE_OPTIONAL_HEADER(32|64).CheckSum + unsigned sum_offs = pehdr_offs + 0x58; + unsigned old_sum = get_dword(buf + sum_offs); + if (old_checksum) + *old_checksum = old_sum; + + // Clear old checksum because it is included below. + put_dword(buf + sum_offs, 0); + + // Calc new checksum. + unsigned sum = 0, size = 0; + int i = 0; + for (;;) { + sum += get_word(buf + i); + sum = (sum + (sum >> 16)) & 0xffff; + + if ((size += 2) >= 0x40000000) { + // 1GiB, assume something is wrong. + errno = EINVAL; + return 0; + } + if ((i += 2) < nr - 1) + continue; // ... with next 2 bytes. + + // Assume that there are no short reads. + if (i < nr) + break; // Last byte. + i = 0; + if ((nr = read(fd, buf, bufsiz)) < 0) + return 0; + if (nr < 2) + break; // Last byte or EOF. + // Continue with next block. + } + + // Handle last byte of file with uneven size. + if (i < nr) { + sum += buf[i]; + sum = (sum + (sum >> 16)) & 0xffff; + size++; + } + + // Add filesize to use some of the upper 16 bits. + sum += size; + + // Fix the checksum if requested and required. + if (set && old_sum != sum) { + put_dword(buf, sum); + if (lseek(fd, sum_offs, SEEK_SET) == -1) + return 0; + if (write(fd, buf, 4) == -1) + return 0; + } + + return sum; +} + +#if STANDALONE +// +// Test program +// +#include +#include + +// Optionally check result using native imagehlp.dll function. +#if STANDALONE > 1 +#include +#include +#endif + +int main(int argc, char ** argv) +{ + int i = 1, set = 0; + if (i < argc && !strcmp(argv[i], "-s")) { + set = 1; + i++; + } + if (i >= argc) { + printf("Usage: %s [-s] FILE...\n", argv[0]); + return 1; + } + + int status = 0; + for ( ; i < argc; i++) { + const char * name = argv[i]; + + int fd = open(name, (set ? O_RDWR : O_RDONLY) | O_BINARY); + if (fd == -1) { + perror(name); + status |= 0x1; + continue; + } + + unsigned osum = 0; + unsigned sum = pe32_checksum(fd, set, &osum); + int err = errno; + close(fd); + + if (sum == 0) { + fprintf(stderr, "%s: Failed, errno=%d\n", name, err); + status |= 0x1; + continue; + } + if (osum == sum) { + printf("%s: 0x%08x (OK)\n", name, osum); + } + else { + printf("%s: 0x%08x -> 0x%08x (%s)\n", name, osum, sum, + (set ? "Fixed" : "*Not fixed*")); + if (!set) + status |= 0x2; + } + +#if STANDALONE > 1 + DWORD osum2 = 0, sum2 = 0; + DWORD rc = MapFileAndCheckSumA(name, &osum2, &sum2); + if (rc != CHECKSUM_SUCCESS) { + fprintf(stderr, "%s: MapFileAndCheckSumA() failed with error %u\n", name, rc); + status |= 0x1; + } + else if (!(osum2 == (set ? sum : osum) && sum2 == sum)) { + fprintf(stderr, "%s: MapFileAndCheckSumA() results differ: 0x%08x -> 0x%08x\n", + name, osum2, sum2); + status |= 0x4; + } +#endif + } + + return status; +} + +#endif // STANDALONE diff --git a/pechecksum.h b/pechecksum.h new file mode 100644 index 000000000000..bf27a84ada46 --- /dev/null +++ b/pechecksum.h @@ -0,0 +1,25 @@ +/* + * PE32 checksum + * + * Copyright (C) 2023 Christian Franke + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef PE_CHECKSUM_H +#define PE_CHECKSUM_H + +/* + * Calculate PE32 checksum of an executable file and optionally fix it. + * + * fd: File descriptor of file opened R/O (!set) or R/W (set) in binary mode. + * set: If nonzero, the correct checksum is written to the file if required. + * old_checksum: If non-NULL, the previous checksum is returned here. + * + * return value: The calculated checksum or 0 on error (the checksum itself + * is never 0). On error, errno is preserved from failed I/O or set to EINVAL + * if the file format is invalid. + */ +unsigned pe32_checksum(int fd, int set, unsigned * old_checksum); + +#endif diff --git a/peflags.c b/peflags.c index 93eaa0b81577..f215704dff31 100644 --- a/peflags.c +++ b/peflags.c @@ -44,6 +44,8 @@ #include +#include "pechecksum.h" + #if defined(__MSYS__) /* MSYS has no strtoull */ unsigned long long strtoull(const char *, char **, int); @@ -66,6 +68,8 @@ static WORD coff_characteristics_show; static WORD pe_characteristics_set; static WORD pe_characteristics_clr; static WORD pe_characteristics_show; +static short checksum_show; +static short checksum_update; /* 1 = update after changes, 2 = fix always */ typedef struct { @@ -132,6 +136,7 @@ enum { SIZEOF_HEAP_RESERVE, SIZEOF_HEAP_COMMIT, SIZEOF_CYGWIN_HEAP, + SIZEOF_TIMESTAMP, NUM_SIZEOF_VALUES /* Keep at the end */ }; @@ -153,7 +158,7 @@ typedef struct { ULONG offset32; } sizeof_values_t; -sizeof_values_t sizeof_vals[5] = { +static sizeof_values_t sizeof_vals[NUM_SIZEOF_VALUES] = { { 0, "stack reserve size" , "bytes", 0, FALSE, offsetof (IMAGE_NT_HEADERS64, OptionalHeader.SizeOfStackReserve), offsetof (IMAGE_NT_HEADERS32, OptionalHeader.SizeOfStackReserve), @@ -173,6 +178,10 @@ sizeof_values_t sizeof_vals[5] = { { 0, "initial Cygwin heap size", "MB", 0, TRUE, offsetof (IMAGE_NT_HEADERS64, OptionalHeader.LoaderFlags), offsetof (IMAGE_NT_HEADERS32, OptionalHeader.LoaderFlags), + }, + { 0, "file header timestamp", "seconds", 0, TRUE, + offsetof (IMAGE_NT_HEADERS64, FileHeader.TimeDateStamp), + offsetof (IMAGE_NT_HEADERS32, FileHeader.TimeDateStamp), } }; @@ -193,11 +202,13 @@ static struct option long_options[] = { {"wstrim", optional_argument, NULL, 'w'}, {"bigaddr", optional_argument, NULL, 'l'}, {"sepdbg", optional_argument, NULL, 'S'}, + {"checksum", optional_argument, NULL, 'k'}, {"stack-reserve",optional_argument, NULL, 'x'}, {"stack-commit", optional_argument, NULL, 'X'}, {"heap-reserve", optional_argument, NULL, 'y'}, {"heap-commit", optional_argument, NULL, 'Y'}, {"cygwin-heap", optional_argument, NULL, 'z'}, + {"timestamp", optional_argument, NULL, 'p'}, {"filelist", no_argument, NULL, 'T'}, {"verbose", no_argument, NULL, 'v'}, {"help", no_argument, NULL, 'h'}, @@ -205,7 +216,7 @@ static struct option long_options[] = { {NULL, no_argument, NULL, 0} }; static const char *short_options - = "d::e::c::f::n::i::s::b::W::t::w::l::S::x::X::y::Y::z::T:vhV"; + = "d::e::c::f::n::i::s::b::W::t::w::l::S::k::x::X::y::Y::z::p::T:vhV"; static void short_usage (FILE *f); static void help (FILE *f); @@ -228,6 +239,7 @@ static void display_flags (const char *field_name, const symbolic_flags_t *syms, WORD new_flag_value); static char *symbolic_flags (const symbolic_flags_t *syms, long show, long value); static void append_and_decorate (char **str, int is_set, const char *name, int len); +static void do_checksum (const char *pathname, int indent, int changed, ULONG hdr_checksum); static void *xmalloc (size_t num); #define XMALLOC(type, num) ((type *) xmalloc ((num) * sizeof(type))) static void handle_coff_flag_option (const char *option_name, @@ -323,6 +335,14 @@ do_mark (const char *pathname) WORD new_coff_characteristics; WORD old_pe_characteristics; WORD new_pe_characteristics; + int indent; + union + { + IMAGE_NT_HEADERS32 ntheader32; + IMAGE_NT_HEADERS64 ntheader64; + } old_ntheader; + int changed; + ULONG hdr_checksum; /* Skip if file does not exist */ if (access (pathname, F_OK) == -1) @@ -351,6 +371,12 @@ do_mark (const char *pathname) return 0; } + /* Save old contents for modification check below. */ + if (pep->is_64bit) + old_ntheader.ntheader64 = *pep->ntheader64; + else + old_ntheader.ntheader32 = *pep->ntheader32; + get_characteristics (pep, &old_coff_characteristics, &old_pe_characteristics); @@ -428,13 +454,13 @@ do_mark (const char *pathname) } /* Display characteristics. */ + indent = 0; if (verbose || !mark_any || coff_characteristics_show || pe_characteristics_show || handle_any_sizeof != DONT_HANDLE) { - BOOL printed_characteristic = FALSE; int i; printf ("%s: ", pathname); @@ -453,27 +479,54 @@ do_mark (const char *pathname) old_pe_characteristics, new_pe_characteristics); puts (""); - printed_characteristic = TRUE; + indent = (int) strlen (pathname) + 2; } for (i = 0; i < NUM_SIZEOF_VALUES; ++i) { if (sizeof_vals[i].handle != DONT_HANDLE) { - printf ("%*s%-24s: %" PRIu64 " (0x%" PRIx64 ") %s\n", - printed_characteristic ? (int) strlen (pathname) + 2 - : 0, "", + char extra[32] = ""; + if (verbose && i == SIZEOF_TIMESTAMP) + { + time_t t = (time_t)sizeof_vals[i].value; + const struct tm * tp = gmtime (&t); + if (tp) + strftime (extra, sizeof(extra), " [%Y-%m-%d %H:%M:%S UTC]", tp); + } + + printf ("%*s%-24s: %" PRIu64 " (0x%" PRIx64 ") %s%s\n", + indent, "", sizeof_vals[i].name, (uint64_t) sizeof_vals[i].value, (uint64_t) sizeof_vals[i].value, - sizeof_vals[i].unit); - printed_characteristic = TRUE; + sizeof_vals[i].unit, extra); + if (!indent) + indent = (int) strlen (pathname) + 2; } } + + } + + /* Any actual changes? */ + if (pep->is_64bit) + { + changed = memcmp(&old_ntheader.ntheader64, pep->ntheader64, + sizeof(old_ntheader.ntheader64)); + hdr_checksum = pep->ntheader64->OptionalHeader.CheckSum; + } + else + { + changed = memcmp(&old_ntheader.ntheader32, pep->ntheader64, + sizeof(old_ntheader.ntheader32)); + hdr_checksum = pep->ntheader32->OptionalHeader.CheckSum; } pe_close (pep); + /* Checksum calculation requires re-open as a regular file. */ + if (checksum_show || checksum_update) + do_checksum (pathname, indent, changed, hdr_checksum); return 0; } @@ -569,6 +622,51 @@ append_and_decorate (char **str, int is_set, const char *name, int len) } } +static void +do_checksum (const char *pathname, int indent, int changed, ULONG hdr_checksum) +{ + const char name[] = "PE file checksum"; + if (checksum_show || (checksum_update && changed) || checksum_update > 1) + { + int fd; + unsigned new_checksum, old_checksum; + if ((fd = open (pathname, (checksum_update ? O_RDWR : O_RDONLY) | O_BINARY)) == -1) + { + fprintf (stderr, "%s: Unable to reopen, errno=%d\n", pathname, errno); + return; + } + new_checksum = pe32_checksum (fd, checksum_update, &old_checksum); + close(fd); + if (!new_checksum) + { + fprintf (stderr, "%s: Checksum calculation failed, errno=%d\n", + pathname, errno); + return; + } + if (old_checksum != hdr_checksum) + { + fprintf (stderr, "%s: Internal error: Re-read checksum does not match: " + "0x%08x != 0x%08x\n", pathname, old_checksum, hdr_checksum); + return; + } + + if (new_checksum == old_checksum) + printf ("%*s%-24s: 0x%08x (%sverified)\n", indent, "", name, + old_checksum, (checksum_update ? "unchanged, " : "")); + else if (checksum_update) + printf ("%*s%-24s: 0x%08x (updated from 0x%08x)\n", indent, "", name, + new_checksum, old_checksum); + else + printf ("%*s%-24s: 0x%08x (*needs update to 0x%08x*)\n", indent, "", name, + old_checksum, new_checksum); + } + else /* (!checksum_show && checksum_update == 1 && !changed) */ + { + printf ("%*s%-24s: 0x%08x (unchanged, not verified)\n", indent, "", name, + hdr_checksum); + } +} + static void * xmalloc (size_t num) { @@ -664,6 +762,31 @@ handle_coff_flag_option (const char *option_name, } } +static void +handle_checksum_option (const char *option_name, + const char *option_arg) +{ + int bool_value; + if (!option_arg) + { + checksum_show = 1; + if (handle_any_sizeof == DONT_HANDLE) + handle_any_sizeof = DO_READ; + } + else + { + if (string_to_bool (option_arg, &bool_value) != 0) + { + fprintf (stderr, "Invalid argument for %s: %s\n", + option_name, option_arg); + short_usage (stderr); + exit (1); + } + checksum_update = (bool_value ? 2 : 1); + handle_any_sizeof = DO_WRITE; + } +} + void parse_args (int argc, char *argv[]) { @@ -762,6 +885,10 @@ parse_args (int argc, char *argv[]) optarg, IMAGE_FILE_DEBUG_STRIPPED); break; + case 'k': + handle_checksum_option (long_options[option_index].name, + optarg); + break; case 'x': handle_num_option (long_options[option_index].name, optarg, @@ -787,6 +914,11 @@ parse_args (int argc, char *argv[]) optarg, SIZEOF_CYGWIN_HEAP); break; + case 'p': + handle_num_option (long_options[option_index].name, + optarg, + SIZEOF_TIMESTAMP); + break; case 'T': file_list = optarg; break; @@ -1102,6 +1234,9 @@ help (FILE *f) " than 2 GB.\n" " -S, --sepdbg [BOOL] Debugging information was removed and stored\n" " separately in another file.\n" +" -k, --checksum [BOOL] Displays (no argument), updates after changes\n" +" only (false) or always fixes (true) the file\n" +" checksum in the PE header.\n" " -x, --stack-reserve [NUM] Reserved stack size of the process in bytes.\n" " -X, --stack-commit [NUM] Initial commited portion of the process stack\n" " in bytes.\n" @@ -1119,6 +1254,7 @@ help (FILE *f) " Useful values are between 4 and 2048. If 0,\n" " Cygwin uses the default heap size of 384 Megs.\n" " Has no meaning for non-Cygwin applications.\n" +" -p, --timestamp [NUM] Timestamp in file header (seconds since epoch).\n" " -T, --filelist FILE Indicate that FILE contains a list\n" " of PE files to process\n" " -v, --verbose Display diagnostic information\n" @@ -1136,6 +1272,8 @@ help (FILE *f) " --cygwin-heap, --cygwin-heap=512, etc\n" "For flag values, to set a value, and display the results symbolic, repeat the\n" "option: --tsaware=true --tsaware -d0 -d\n" +"The time of last modification of each file is preserved unless the checksum\n" +"has been updated.\n" "\n", f); }