public inbox for cygwin-apps@cygwin.com
 help / color / mirror / Atom feed
* [PATCH rebase 2/2] peflags: Add -k, --checksum option
@ 2023-08-07 14:26 Christian Franke
  2023-08-07 19:16 ` Corinna Vinschen
  0 siblings, 1 reply; 2+ messages in thread
From: Christian Franke @ 2023-08-07 14:26 UTC (permalink / raw)
  To: cygwin-apps

[-- Attachment #1: Type: text/plain, Size: 129 bytes --]

This patch is on top of the --timestamp patch. Could not be applied to 
current HEAD without conflicts.

-- 
Regards,
Christian


[-- Attachment #2: 0001-peflags-Add-k-checksum-option.patch --]
[-- Type: text/plain, Size: 15398 bytes --]

From 9ecaf86bff5d229bf5b2a1ba1ff4674526fc1b68 Mon Sep 17 00:00:00 2001
From: Christian Franke <christian.franke@t-online.de>
Date: Mon, 7 Aug 2023 15:52:14 +0200
Subject: [PATCH] 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>
---
 Makefile.in  |   6 +-
 pechecksum.c | 195 +++++++++++++++++++++++++++++++++++++++++++++++++++
 pechecksum.h |  25 +++++++
 peflags.c    | 129 ++++++++++++++++++++++++++++++++--
 4 files changed, 347 insertions(+), 8 deletions(-)
 create mode 100644 pechecksum.c
 create mode 100644 pechecksum.h

diff --git a/Makefile.in b/Makefile.in
index 34c4684..46df1d5 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 0000000..8695138
--- /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 0000000..bf27a84
--- /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 f4b1812..09aca07 100644
--- a/peflags.c
+++ b/peflags.c
@@ -43,6 +43,8 @@
 
 #include <windows.h>
 
+#include "pechecksum.h"
+
 #if defined(__MSYS__)
 /* MSYS has no strtoull */
 unsigned long long strtoull(const char *, char **, int);
@@ -65,6 +67,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
 {
@@ -197,6 +201,7 @@ 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'},
@@ -210,7 +215,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::p::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);
@@ -233,6 +238,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,
@@ -328,6 +334,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)
@@ -356,6 +370,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);
@@ -433,13 +453,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);
@@ -458,7 +478,7 @@ 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)
@@ -475,19 +495,37 @@ do_mark (const char *pathname)
 		}
 
 	      printf ("%*s%-24s: %" PRIu64 " (0x%" PRIx64 ") %s%s\n",
-		      printed_characteristic ? (int) strlen (pathname) + 2
-					     : 0, "",
+		      indent, "",
 		      sizeof_vals[i].name,
 		      (uint64_t) sizeof_vals[i].value,
 		      (uint64_t) sizeof_vals[i].value,
 		      sizeof_vals[i].unit, extra);
-	      printed_characteristic = TRUE;
+	      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;
 }
 
@@ -583,6 +621,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)
 {
@@ -678,6 +761,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[])
 {
@@ -776,6 +884,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,
@@ -1121,6 +1233,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"
@@ -1156,6 +1271,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);
 }
 
-- 
2.39.0


^ permalink raw reply	[flat|nested] 2+ messages in thread

* Re: [PATCH rebase 2/2] peflags: Add -k, --checksum option
  2023-08-07 14:26 [PATCH rebase 2/2] peflags: Add -k, --checksum option Christian Franke
@ 2023-08-07 19:16 ` Corinna Vinschen
  0 siblings, 0 replies; 2+ messages in thread
From: Corinna Vinschen @ 2023-08-07 19:16 UTC (permalink / raw)
  To: Christian Franke; +Cc: cygwin-apps

On Aug  7 16:26, Christian Franke via Cygwin-apps wrote:
> This patch is on top of the --timestamp patch. Could not be applied to
> current HEAD without conflicts.
> 
> -- 
> Regards,
> Christian
> 

> From 9ecaf86bff5d229bf5b2a1ba1ff4674526fc1b68 Mon Sep 17 00:00:00 2001
> From: Christian Franke <christian.franke@t-online.de>
> Date: Mon, 7 Aug 2023 15:52:14 +0200
> Subject: [PATCH] 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>
> ---
>  Makefile.in  |   6 +-
>  pechecksum.c | 195 +++++++++++++++++++++++++++++++++++++++++++++++++++
>  pechecksum.h |  25 +++++++
>  peflags.c    | 129 ++++++++++++++++++++++++++++++++--
>  4 files changed, 347 insertions(+), 8 deletions(-)
>  create mode 100644 pechecksum.c
>  create mode 100644 pechecksum.h

Pushed.


Thanks,
Corinna


^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2023-08-07 19:16 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-08-07 14:26 [PATCH rebase 2/2] peflags: Add -k, --checksum option Christian Franke
2023-08-07 19:16 ` 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).