[PATCH v5] ld: Add --encoded-package-metadata

Benjamin Drung benjamin.drung@canonical.com
Tue Jul 23 23:28:18 GMT 2024


Specifying the compiler flag `-Wl,--package-metadata=<JSON>` will not
work in case the JSON contains a comma, because compiler drivers eat
commas. Example:

```
$ echo "void main() { }" > test.c
$ gcc '-Wl,--package-metadata={"type":"deb","os":"ubuntu"}' test.c
/usr/bin/ld: cannot find "os":"ubuntu"}: No such file or directory
collect2: error: ld returned 1 exit status
```

The quotation marks in the JSON value do not work well with shell nor
make. Specifying the `--package-metadata` linker flag in a `LDFLAGS`
environment variable might loose its quotation marks when it hits the
final compiler call.

So introduce a `--encoded-package-metadata` linker flag that takes a
percent-encoded JSON. Percent-encoding is used because it is a
standard, simple to implement, and does take too many additional
characters.

Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=32003
Bug-Ubutru: https://bugs.launchpad.net/bugs/2071468
Signed-off-by: Benjamin Drung <benjamin.drung@canonical.com>
---
 ld/NEWS                              |  5 +++
 ld/emultempl/elf.em                  | 19 +++++++++++
 ld/ld.texi                           | 15 ++++++++-
 ld/ldlex.h                           |  1 +
 ld/ldmisc.c                          | 49 ++++++++++++++++++++++++++++
 ld/ldmisc.h                          |  1 +
 ld/lexsup.c                          |  2 ++
 ld/testsuite/ld-elf/package-note.exp | 18 ++++++++++
 ld/testsuite/ld-elf/package-note2.rd |  6 ++++
 9 files changed, 115 insertions(+), 1 deletion(-)
 create mode 100644 ld/testsuite/ld-elf/package-note2.rd

diff --git a/ld/NEWS b/ld/NEWS
index c1e42139b8f..a1880ccf1bb 100644
--- a/ld/NEWS
+++ b/ld/NEWS
@@ -14,6 +14,11 @@ Changes in 2.43:
 
 * Add -plugin-save-temps to store plugin intermediate files permanently.
 
+* The ELF linker now supports a new --encoded-package-metadata option that
+  is equivalent to --package-metadata, but takes the JSON payload
+  percent-encoded to ease passing around this option without getting the
+  JSON payload corrupted.
+
 Changes in 2.42:
 
 * Add -z mark-plt/-z nomark-plt options to x86-64 ELF linker to mark PLT
diff --git a/ld/emultempl/elf.em b/ld/emultempl/elf.em
index 863657e12f5..1a893f35416 100644
--- a/ld/emultempl/elf.em
+++ b/ld/emultempl/elf.em
@@ -813,6 +813,7 @@ EOF
 fi
 fragment <<EOF
     {"build-id", optional_argument, NULL, OPTION_BUILD_ID},
+    {"encoded-package-metadata", optional_argument, NULL, OPTION_ENCODED_PACKAGE_METADATA},
     {"package-metadata", optional_argument, NULL, OPTION_PACKAGE_METADATA},
     {"compress-debug-sections", required_argument, NULL, OPTION_COMPRESS_DEBUG},
     {"rosegment", no_argument, NULL, OPTION_ROSEGMENT},
@@ -864,6 +865,24 @@ gld${EMULATION_NAME}_handle_option (int optc)
 	ldelf_emit_note_gnu_build_id = xstrdup (optarg);
       break;
 
+    case OPTION_ENCODED_PACKAGE_METADATA:
+      free ((char *) ldelf_emit_note_fdo_package_metadata);
+      ldelf_emit_note_fdo_package_metadata = NULL;
+      if (optarg != NULL)
+	{
+	  size_t optarg_len = strlen (optarg);
+	  if (optarg_len > 0)
+	    {
+	      char *package_metadata = xmalloc (optarg_len + 1);
+	      int len = percent_decode (optarg, package_metadata);
+	      if (len < 0)
+		einfo (_("%F%P: Failed to decode percent-encoded package"
+		         " metadata \`%s'\n"), optarg);
+	      ldelf_emit_note_fdo_package_metadata = package_metadata;
+	    }
+        }
+      break;
+
     case OPTION_PACKAGE_METADATA:
       free ((char *) ldelf_emit_note_fdo_package_metadata);
       ldelf_emit_note_fdo_package_metadata = NULL;
diff --git a/ld/ld.texi b/ld/ld.texi
index 89e3913317a..28733b1ae8e 100644
--- a/ld/ld.texi
+++ b/ld/ld.texi
@@ -3234,6 +3234,19 @@ string identifying the original linked file does not change.
 Passing @code{none} for @var{style} disables the setting from any
 @code{--build-id} options earlier on the command line.
 
+@kindex --encoded-package-metadata=@var{PERCENT_ENCODED_JSON}
+@item --encoded-package-metadata=@var{PERCENT_ENCODED_JSON}
+Request the creation of a @code{.note.package} ELF note section.  The
+contents of the note are in JSON format, as per the package metadata
+specification.  For more information see:
+https://systemd.io/ELF_PACKAGE_METADATA/
+If the PERCENT_ENCODED_JSON argument is missing/empty then this will
+disable the creation of the metadata note, if one had been enabled by
+an earlier occurrence of the --encoded-package-metadata or
+--package-metadata option. PERCENT_ENCODED_JSON will be percent-decoded.
+If the linker has been built with libjansson, then the JSON string
+will be validated.
+
 @kindex --package-metadata=@var{JSON}
 @item --package-metadata=@var{JSON}
 Request the creation of a @code{.note.package} ELF note section.  The
@@ -3242,7 +3255,7 @@ specification.  For more information see:
 https://systemd.io/ELF_PACKAGE_METADATA/
 If the JSON argument is missing/empty then this will disable the
 creation of the metadata note, if one had been enabled by an earlier
-occurrence of the --package-metadata option.
+occurrence of the --encoded-package-metadata or --package-metadata option.
 If the linker has been built with libjansson, then the JSON string
 will be validated.
 @end table
diff --git a/ld/ldlex.h b/ld/ldlex.h
index defe3fcbbb9..225d95b1e90 100644
--- a/ld/ldlex.h
+++ b/ld/ldlex.h
@@ -284,6 +284,7 @@ enum option_values
   OPTION_EH_FRAME_HDR,
   OPTION_NO_EH_FRAME_HDR,
   OPTION_HASH_STYLE,
+  OPTION_ENCODED_PACKAGE_METADATA,
   OPTION_PACKAGE_METADATA,
   OPTION_AUDIT,
   OPTION_COMPRESS_DEBUG,
diff --git a/ld/ldmisc.c b/ld/ldmisc.c
index 180b24b3448..ff98226afd6 100644
--- a/ld/ldmisc.c
+++ b/ld/ldmisc.c
@@ -770,3 +770,52 @@ ld_abort (const char *file, int line, const char *fn)
   einfo (_("%F%P: please report this bug\n"));
   xexit (1);
 }
+
+/* Decode a hexadecimal character. Return -1 on error. */
+static int
+hexdecode (char c)
+{
+  if ('0' <= c && c <= '9')
+    return c - '0';
+  if ('A' <= c && c <= 'F')
+    return c - 'A' + 10;
+  if ('a' <= c && c <= 'f')
+    return c - 'a' + 10;
+  return -1;
+}
+
+/* Decode a percent-encoded string. dst must be at least the same size as src.
+   It can be converted in place. Returns the lenght of the decoded string
+   (without training null character) or -1 on error. */
+int
+percent_decode (const char *src, char *dst)
+{
+  int length = 0;
+  while (*src != '\0')
+    {
+      char c = *src++;
+      if (c != '%')
+	{
+	  *dst++ = c;
+	  length += 1;
+	  continue;
+	}
+      char next1 = *src++;
+      if (next1 == '%')
+	{
+	  *dst++ = '%';
+	  length += 1;
+	  continue;
+	}
+      int hex1 = hexdecode (next1);
+      if (hex1 == -1)
+	return -1;
+      int hex2 = hexdecode (*src++);
+      if (hex2 == -1)
+	return -1;
+      *dst++ = (char) ((hex1 << 4) + hex2);
+      length += 1;
+    }
+  *dst = '\0';
+  return length;
+}
diff --git a/ld/ldmisc.h b/ld/ldmisc.h
index 20289127c0a..815e8ccb548 100644
--- a/ld/ldmisc.h
+++ b/ld/ldmisc.h
@@ -39,5 +39,6 @@ do { info_assert(__FILE__,__LINE__); } while (0)
 extern void print_spaces (int);
 #define print_space() print_spaces (1)
 extern void print_nl (void);
+extern int percent_decode (const char *, char *);
 
 #endif
diff --git a/ld/lexsup.c b/ld/lexsup.c
index 4aa0124ce2f..60e329a713f 100644
--- a/ld/lexsup.c
+++ b/ld/lexsup.c
@@ -2279,6 +2279,8 @@ elf_static_list_options (FILE *file)
   fprintf (file, _("\
   --build-id[=STYLE]          Generate build ID note\n"));
   fprintf (file, _("\
+  --encoded-package-metadata[=PERCENT_ENCODED_JSON]   Generate package metadata note\n"));
+  fprintf (file, _("\
   --package-metadata[=JSON]   Generate package metadata note\n"));
   fprintf (file, _("\
   --compress-debug-sections=[none|zlib|zlib-gnu|zlib-gabi|zstd]\n\
diff --git a/ld/testsuite/ld-elf/package-note.exp b/ld/testsuite/ld-elf/package-note.exp
index 9f18705bd65..b4feef541b4 100644
--- a/ld/testsuite/ld-elf/package-note.exp
+++ b/ld/testsuite/ld-elf/package-note.exp
@@ -42,4 +42,22 @@ run_ld_link_tests [list \
 	{{readelf {--notes} package-note.rd}} \
 	"package-note.o" \
     ] \
+    [list \
+	"package-note1b.o" \
+	"--encoded-package-metadata=%7B%22foo%22%3A%22bar%22%7D" \
+	"" \
+	"" \
+	{start.s} \
+	{{readelf {--notes} package-note.rd}} \
+	"package-note1b.o" \
+    ] \
+    [list \
+	"package-note2.o" \
+	"--encoded-package-metadata=%7B%22name%22:%22binutils%22%2C%22foo%22%3A%22bar%22%7d" \
+	"" \
+	"" \
+	{start.s} \
+	{{readelf {--notes} package-note2.rd}} \
+	"package-note2.o" \
+    ] \
 ]
diff --git a/ld/testsuite/ld-elf/package-note2.rd b/ld/testsuite/ld-elf/package-note2.rd
new file mode 100644
index 00000000000..c24c37825ad
--- /dev/null
+++ b/ld/testsuite/ld-elf/package-note2.rd
@@ -0,0 +1,6 @@
+#...
+Displaying notes found in: \.note\.package
+\s+Owner\s+Data\s+size\s+Description
+\s+FDO\s+0x00000020\s+(Unknown note type:\s+\(0xcafe1a7e\)|FDO_PACKAGING_METADATA)
+\s+(description data:\s+.*|Packaging Metadata:\s+{"name":"binutils","foo":"bar"})
+#pass
-- 
2.43.0



More information about the Binutils mailing list