public inbox for cygwin-apps-cvs@sourceware.org
help / color / mirror / Atom feed
* [rebase - The rebase tool, core of the automatic rebase facility during postinstall] branch master, updated. bb79f3cdb48f90948ccc41621f77f32ab59495cb
@ 2023-08-07 19:16 Corinna Vinschen
  0 siblings, 0 replies; only message in thread
From: Corinna Vinschen @ 2023-08-07 19:16 UTC (permalink / raw)
  To: cygwin-apps-cvs




https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/rebase.git;h=bb79f3cdb48f90948ccc41621f77f32ab59495cb

commit bb79f3cdb48f90948ccc41621f77f32ab59495cb
Author: Christian Franke <christian.franke@t-online.de>
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 <christian.franke@t-online.de>

https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/rebase.git;h=cdd3b966d77f68edbb7f1fba001930a41457df8e

commit cdd3b966d77f68edbb7f1fba001930a41457df8e
Author: Christian Franke <christian.franke@t-online.de>
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 <christian.franke@t-online.de>


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 <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+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 <stdio.h>
+#include <string.h>
+
+// Optionally check result using native imagehlp.dll function.
+#if STANDALONE > 1
+#include <windows.h>
+#include <imagehlp.h>
+#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 <windows.h>
 
+#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);
 }
 


^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2023-08-07 19:16 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-08-07 19:16 [rebase - The rebase tool, core of the automatic rebase facility during postinstall] branch master, updated. bb79f3cdb48f90948ccc41621f77f32ab59495cb Corinna Vinschen

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).