diff mbox series

[13/15] modpost: Add support for hashing long symbol names

Message ID 20240617175818.58219-30-samitolvanen@google.com (mailing list archive)
State Handled Elsewhere
Headers show
Series Implement MODVERSIONS for Rust | expand

Commit Message

Sami Tolvanen June 17, 2024, 5:58 p.m. UTC
Rust frequently has symbol names longer than MODULE_NAME_LEN, because
the full namespace path is encoded into the mangled name. Instead of
modpost failing when running into a long name with CONFIG_MODVERSIONS,
store a hash of the name in struct modversion_info.

To avoid breaking userspace tools that parse the __versions array,
include a null-terminated hash function name at the beginning of the
name string, followed by a binary hash. In order to keep .mod.c files
more human-readable, add a comment with the full symbol name before the
array entry. Example output in rust_minimal.mod.c:

  static const struct modversion_info ____versions[]
  __used __section("__versions") = {
        /* _RNvNtNtCs1cdwasc6FUb_6kernel5print14format_strings4INFO */
        { 0x9ec5442f, "sha256\x00\x56\x96\xf4\x27\xdb\x4a\xbf[...]" },
        { 0x1d6595b1, "_RNvNtCs1cdwasc6FUb_6kernel5print11call_printk" },
        { 0x3c642974, "__rust_dealloc" },
        ...
  };

modprobe output for the resulting module:

  $ modprobe --dump-modversions rust_minimal.ko
  0x9ec5442f	sha256
  0x1d6595b1	_RNvNtCs1cdwasc6FUb_6kernel5print11call_printk
  0x3c642974	__rust_dealloc
  ...

While the output is less useful, the tool continues to work and can be
later updated to produce more helpful output for hashed symbols.

Note that this patch adds a generic SHA-256 implementation to modpost
adapted from the Crypto API, but other hash functions may be used in
future if needed.

Suggested-by: Matthew Maurer <mmaurer@google.com>
Signed-off-by: Sami Tolvanen <samitolvanen@google.com>
---
 scripts/mod/Makefile  |   4 +-
 scripts/mod/modpost.c |  20 ++-
 scripts/mod/modpost.h |  20 +++
 scripts/mod/symhash.c | 327 ++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 364 insertions(+), 7 deletions(-)
 create mode 100644 scripts/mod/symhash.c

Comments

Masahiro Yamada June 18, 2024, 4:47 p.m. UTC | #1
On Tue, Jun 18, 2024 at 2:58 AM Sami Tolvanen <samitolvanen@google.com> wrote:
>
> Rust frequently has symbol names longer than MODULE_NAME_LEN, because
> the full namespace path is encoded into the mangled name. Instead of
> modpost failing when running into a long name with CONFIG_MODVERSIONS,
> store a hash of the name in struct modversion_info.
>
> To avoid breaking userspace tools that parse the __versions array,
> include a null-terminated hash function name at the beginning of the
> name string, followed by a binary hash. In order to keep .mod.c files
> more human-readable, add a comment with the full symbol name before the
> array entry. Example output in rust_minimal.mod.c:
>
>   static const struct modversion_info ____versions[]
>   __used __section("__versions") = {
>         /* _RNvNtNtCs1cdwasc6FUb_6kernel5print14format_strings4INFO */
>         { 0x9ec5442f, "sha256\x00\x56\x96\xf4\x27\xdb\x4a\xbf[...]" },
>         { 0x1d6595b1, "_RNvNtCs1cdwasc6FUb_6kernel5print11call_printk" },
>         { 0x3c642974, "__rust_dealloc" },
>         ...
>   };
>
> modprobe output for the resulting module:
>
>   $ modprobe --dump-modversions rust_minimal.ko
>   0x9ec5442f    sha256
>   0x1d6595b1    _RNvNtCs1cdwasc6FUb_6kernel5print11call_printk
>   0x3c642974    __rust_dealloc
>   ...
>
> While the output is less useful, the tool continues to work and can be
> later updated to produce more helpful output for hashed symbols.
>
> Note that this patch adds a generic SHA-256 implementation to modpost
> adapted from the Crypto API, but other hash functions may be used in
> future if needed.
>
> Suggested-by: Matthew Maurer <mmaurer@google.com>
> Signed-off-by: Sami Tolvanen <samitolvanen@google.com>
> ---
>  scripts/mod/Makefile  |   4 +-
>  scripts/mod/modpost.c |  20 ++-
>  scripts/mod/modpost.h |  20 +++
>  scripts/mod/symhash.c | 327 ++++++++++++++++++++++++++++++++++++++++++
>  4 files changed, 364 insertions(+), 7 deletions(-)
>  create mode 100644 scripts/mod/symhash.c
>
> diff --git a/scripts/mod/Makefile b/scripts/mod/Makefile
> index c729bc936bae..ddd59ea9027e 100644
> --- a/scripts/mod/Makefile
> +++ b/scripts/mod/Makefile
> @@ -4,7 +4,7 @@ CFLAGS_REMOVE_empty.o += $(CC_FLAGS_LTO)
>  hostprogs-always-y     += modpost mk_elfconfig
>  always-y               += empty.o
>
> -modpost-objs   := modpost.o file2alias.o sumversion.o symsearch.o
> +modpost-objs   := modpost.o file2alias.o symhash.o sumversion.o symsearch.o
>
>  devicetable-offsets-file := devicetable-offsets.h
>
> @@ -15,7 +15,7 @@ targets += $(devicetable-offsets-file) devicetable-offsets.s
>
>  # dependencies on generated files need to be listed explicitly
>
> -$(obj)/modpost.o $(obj)/file2alias.o $(obj)/sumversion.o $(obj)/symsearch.o: $(obj)/elfconfig.h
> +$(obj)/modpost.o $(obj)/file2alias.o $(obj)/symhash.o $(obj)/sumversion.o $(obj)/symsearch.o: $(obj)/elfconfig.h
>  $(obj)/file2alias.o: $(obj)/$(devicetable-offsets-file)
>
>  quiet_cmd_elfconfig = MKELF   $@
> diff --git a/scripts/mod/modpost.c b/scripts/mod/modpost.c
> index f48d72d22dc2..2631e3e75a5c 100644
> --- a/scripts/mod/modpost.c
> +++ b/scripts/mod/modpost.c
> @@ -1900,7 +1900,10 @@ static void add_exported_symbols(struct buffer *buf, struct module *mod)
>   **/
>  static void add_versions(struct buffer *b, struct module *mod)
>  {
> +       char hash[SYMHASH_STR_LEN];
>         struct symbol *s;
> +       const char *name;
> +       size_t len;
>
>         if (!modversions)
>                 return;
> @@ -1917,13 +1920,20 @@ static void add_versions(struct buffer *b, struct module *mod)
>                                 s->name, mod->name);
>                         continue;
>                 }
> -               if (strlen(s->name) >= MODULE_NAME_LEN) {
> -                       error("too long symbol \"%s\" [%s.ko]\n",
> -                             s->name, mod->name);
> -                       break;
> +               len = strlen(s->name);
> +               /*
> +                * For symbols with a long name, use the hash format, but include
> +                * the full symbol name as a comment to keep the generated files
> +                * human-readable.
> +                */
> +               if (len >= MODULE_NAME_LEN) {
> +                       buf_printf(b, "\t/* %s */\n", s->name);
> +                       name = symhash_str(s->name, len, hash);
> +               } else {
> +                       name = s->name;
>                 }
>                 buf_printf(b, "\t{ %#8x, \"%s\" },\n",
> -                          s->crc, s->name);
> +                          s->crc, name);
>         }
>
>         buf_printf(b, "};\n");
> diff --git a/scripts/mod/modpost.h b/scripts/mod/modpost.h
> index ee43c7950636..cd2eb718936b 100644
> --- a/scripts/mod/modpost.h
> +++ b/scripts/mod/modpost.h
> @@ -183,6 +183,26 @@ void handle_moddevtable(struct module *mod, struct elf_info *info,
>                         Elf_Sym *sym, const char *symname);
>  void add_moddevtable(struct buffer *buf, struct module *mod);
>
> +/* symhash.c */
> +/*
> + * For symbol names longer than MODULE_NAME_LEN, encode a hash of the
> + * symbol name in version information as follows:
> + *
> + * <hash name>\0<binary hash of the symbol name>
> + *
> + * e.g. as a string in .mod.c files:
> + * "sha256\x00\xNN{32}"
> + *
> + * The string is null terminated after the hash name to avoid breaking
> + * userspace tools that parse the __versions table and don't understand
> + * the format.
> + */
> +#define SYMHASH_STR_PREFIX     "sha256\\x00"
> +#define SYMHASH_STR_PREFIX_LEN (sizeof(SYMHASH_STR_PREFIX) - 1)
> +#define SYMHASH_STR_LEN                (SYMHASH_STR_PREFIX_LEN + 4*32 + 1)
> +
> +char *symhash_str(const char *name, size_t len, char hash_str[SYMHASH_STR_LEN]);
> +
>  /* sumversion.c */
>  void get_src_version(const char *modname, char sum[], unsigned sumlen);
>
> diff --git a/scripts/mod/symhash.c b/scripts/mod/symhash.c
> new file mode 100644
> index 000000000000..d0c9cf5f1f6c
> --- /dev/null
> +++ b/scripts/mod/symhash.c
> @@ -0,0 +1,327 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * symhash.c
> + *
> + * Symbol name hashing using a SHA-256 implementation adapted from the
> + * Cryptographic API.
> + */
> +#include <byteswap.h>
> +#include "modpost.h"
> +
> +#if HOST_ELFDATA == ELFDATA2MSB
> +/* Big endian */
> +#define be32_to_cpu(val) (val)
> +#define cpu_to_be32(val) (val)
> +#define cpu_to_be64(val) (val)
> +#else
> +/* Little endian */
> +#define be32_to_cpu(val) bswap_32(val)
> +#define cpu_to_be32(val) bswap_32(val)
> +#define cpu_to_be64(val) bswap_64(val)
> +#endif
> +
> +#define barrier_data(ptr) __asm__ __volatile__("": :"r"(ptr) :"memory")




I know this is a copy-paste of the kernel space code,
but is barrier_data() also necessary for host programs?





> +
> +static inline void memzero_explicit(void *s, size_t count)
> +{
> +       memset(s, 0, count);
> +       barrier_data(s);
> +}
> +



--
Best Regards
Masahiro Yamada
Sami Tolvanen June 18, 2024, 8:07 p.m. UTC | #2
On Wed, Jun 19, 2024 at 01:47:13AM +0900, Masahiro Yamada wrote:
> On Tue, Jun 18, 2024 at 2:58 AM Sami Tolvanen <samitolvanen@google.com> wrote:
> >
> > +#define barrier_data(ptr) __asm__ __volatile__("": :"r"(ptr) :"memory")
> 
> 
> 
> 
> I know this is a copy-paste of the kernel space code,
> but is barrier_data() also necessary for host programs?

I tested this with Clang and it worked fine without the barrier, but
with gcc, the code no longer produces correct values. Presumably this is
a load bearing data barrier.

Sami
diff mbox series

Patch

diff --git a/scripts/mod/Makefile b/scripts/mod/Makefile
index c729bc936bae..ddd59ea9027e 100644
--- a/scripts/mod/Makefile
+++ b/scripts/mod/Makefile
@@ -4,7 +4,7 @@  CFLAGS_REMOVE_empty.o += $(CC_FLAGS_LTO)
 hostprogs-always-y	+= modpost mk_elfconfig
 always-y		+= empty.o
 
-modpost-objs	:= modpost.o file2alias.o sumversion.o symsearch.o
+modpost-objs	:= modpost.o file2alias.o symhash.o sumversion.o symsearch.o
 
 devicetable-offsets-file := devicetable-offsets.h
 
@@ -15,7 +15,7 @@  targets += $(devicetable-offsets-file) devicetable-offsets.s
 
 # dependencies on generated files need to be listed explicitly
 
-$(obj)/modpost.o $(obj)/file2alias.o $(obj)/sumversion.o $(obj)/symsearch.o: $(obj)/elfconfig.h
+$(obj)/modpost.o $(obj)/file2alias.o $(obj)/symhash.o $(obj)/sumversion.o $(obj)/symsearch.o: $(obj)/elfconfig.h
 $(obj)/file2alias.o: $(obj)/$(devicetable-offsets-file)
 
 quiet_cmd_elfconfig = MKELF   $@
diff --git a/scripts/mod/modpost.c b/scripts/mod/modpost.c
index f48d72d22dc2..2631e3e75a5c 100644
--- a/scripts/mod/modpost.c
+++ b/scripts/mod/modpost.c
@@ -1900,7 +1900,10 @@  static void add_exported_symbols(struct buffer *buf, struct module *mod)
  **/
 static void add_versions(struct buffer *b, struct module *mod)
 {
+	char hash[SYMHASH_STR_LEN];
 	struct symbol *s;
+	const char *name;
+	size_t len;
 
 	if (!modversions)
 		return;
@@ -1917,13 +1920,20 @@  static void add_versions(struct buffer *b, struct module *mod)
 				s->name, mod->name);
 			continue;
 		}
-		if (strlen(s->name) >= MODULE_NAME_LEN) {
-			error("too long symbol \"%s\" [%s.ko]\n",
-			      s->name, mod->name);
-			break;
+		len = strlen(s->name);
+		/*
+		 * For symbols with a long name, use the hash format, but include
+		 * the full symbol name as a comment to keep the generated files
+		 * human-readable.
+		 */
+		if (len >= MODULE_NAME_LEN) {
+			buf_printf(b, "\t/* %s */\n", s->name);
+			name = symhash_str(s->name, len, hash);
+		} else {
+			name = s->name;
 		}
 		buf_printf(b, "\t{ %#8x, \"%s\" },\n",
-			   s->crc, s->name);
+			   s->crc, name);
 	}
 
 	buf_printf(b, "};\n");
diff --git a/scripts/mod/modpost.h b/scripts/mod/modpost.h
index ee43c7950636..cd2eb718936b 100644
--- a/scripts/mod/modpost.h
+++ b/scripts/mod/modpost.h
@@ -183,6 +183,26 @@  void handle_moddevtable(struct module *mod, struct elf_info *info,
 			Elf_Sym *sym, const char *symname);
 void add_moddevtable(struct buffer *buf, struct module *mod);
 
+/* symhash.c */
+/*
+ * For symbol names longer than MODULE_NAME_LEN, encode a hash of the
+ * symbol name in version information as follows:
+ *
+ * <hash name>\0<binary hash of the symbol name>
+ *
+ * e.g. as a string in .mod.c files:
+ * "sha256\x00\xNN{32}"
+ *
+ * The string is null terminated after the hash name to avoid breaking
+ * userspace tools that parse the __versions table and don't understand
+ * the format.
+ */
+#define SYMHASH_STR_PREFIX	"sha256\\x00"
+#define SYMHASH_STR_PREFIX_LEN	(sizeof(SYMHASH_STR_PREFIX) - 1)
+#define SYMHASH_STR_LEN		(SYMHASH_STR_PREFIX_LEN + 4*32 + 1)
+
+char *symhash_str(const char *name, size_t len, char hash_str[SYMHASH_STR_LEN]);
+
 /* sumversion.c */
 void get_src_version(const char *modname, char sum[], unsigned sumlen);
 
diff --git a/scripts/mod/symhash.c b/scripts/mod/symhash.c
new file mode 100644
index 000000000000..d0c9cf5f1f6c
--- /dev/null
+++ b/scripts/mod/symhash.c
@@ -0,0 +1,327 @@ 
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * symhash.c
+ *
+ * Symbol name hashing using a SHA-256 implementation adapted from the
+ * Cryptographic API.
+ */
+#include <byteswap.h>
+#include "modpost.h"
+
+#if HOST_ELFDATA == ELFDATA2MSB
+/* Big endian */
+#define be32_to_cpu(val) (val)
+#define cpu_to_be32(val) (val)
+#define cpu_to_be64(val) (val)
+#else
+/* Little endian */
+#define be32_to_cpu(val) bswap_32(val)
+#define cpu_to_be32(val) bswap_32(val)
+#define cpu_to_be64(val) bswap_64(val)
+#endif
+
+#define barrier_data(ptr) __asm__ __volatile__("": :"r"(ptr) :"memory")
+
+static inline void memzero_explicit(void *s, size_t count)
+{
+	memset(s, 0, count);
+	barrier_data(s);
+}
+
+static inline uint32_t ror32(uint32_t word, unsigned int shift)
+{
+	return (word >> (shift & 31)) | (word << ((-shift) & 31));
+}
+
+/*
+ * include/crypto/sha2.h - Common values for SHA-2 algorithms
+ */
+#define SHA256_DIGEST_SIZE      32
+#define SHA256_BLOCK_SIZE       64
+
+#define SHA256_H0	0x6a09e667UL
+#define SHA256_H1	0xbb67ae85UL
+#define SHA256_H2	0x3c6ef372UL
+#define SHA256_H3	0xa54ff53aUL
+#define SHA256_H4	0x510e527fUL
+#define SHA256_H5	0x9b05688cUL
+#define SHA256_H6	0x1f83d9abUL
+#define SHA256_H7	0x5be0cd19UL
+
+struct sha256_state {
+	uint32_t state[SHA256_DIGEST_SIZE / 4];
+	uint64_t count;
+	uint8_t buf[SHA256_BLOCK_SIZE];
+};
+
+static inline void sha256_init(struct sha256_state *sctx)
+{
+	sctx->state[0] = SHA256_H0;
+	sctx->state[1] = SHA256_H1;
+	sctx->state[2] = SHA256_H2;
+	sctx->state[3] = SHA256_H3;
+	sctx->state[4] = SHA256_H4;
+	sctx->state[5] = SHA256_H5;
+	sctx->state[6] = SHA256_H6;
+	sctx->state[7] = SHA256_H7;
+	sctx->count = 0;
+}
+
+/*
+ * include/crypto/sha256_base.h - core logic for SHA-256 implementations
+ *
+ * Copyright (C) 2015 Linaro Ltd <ard.biesheuvel@linaro.org>
+ */
+typedef void (sha256_block_fn)(struct sha256_state *sst, uint8_t const *src,
+			       int blocks);
+
+static inline int lib_sha256_base_do_update(struct sha256_state *sctx,
+					    const uint8_t *data,
+					    unsigned int len,
+					    sha256_block_fn *block_fn)
+{
+	unsigned int partial = sctx->count % SHA256_BLOCK_SIZE;
+
+	sctx->count += len;
+
+	if ((partial + len) >= SHA256_BLOCK_SIZE) {
+		int blocks;
+
+		if (partial) {
+			int p = SHA256_BLOCK_SIZE - partial;
+
+			memcpy(sctx->buf + partial, data, p);
+			data += p;
+			len -= p;
+
+			block_fn(sctx, sctx->buf, 1);
+		}
+
+		blocks = len / SHA256_BLOCK_SIZE;
+		len %= SHA256_BLOCK_SIZE;
+
+		if (blocks) {
+			block_fn(sctx, data, blocks);
+			data += blocks * SHA256_BLOCK_SIZE;
+		}
+		partial = 0;
+	}
+	if (len)
+		memcpy(sctx->buf + partial, data, len);
+
+	return 0;
+}
+
+static inline int lib_sha256_base_do_finalize(struct sha256_state *sctx,
+					      sha256_block_fn *block_fn)
+{
+	const int bit_offset = SHA256_BLOCK_SIZE - sizeof(uint64_t);
+	uint64_t *bits = (uint64_t *)(sctx->buf + bit_offset);
+	unsigned int partial = sctx->count % SHA256_BLOCK_SIZE;
+
+	sctx->buf[partial++] = 0x80;
+	if (partial > bit_offset) {
+		memset(sctx->buf + partial, 0x0, SHA256_BLOCK_SIZE - partial);
+		partial = 0;
+
+		block_fn(sctx, sctx->buf, 1);
+	}
+
+	memset(sctx->buf + partial, 0x0, bit_offset - partial);
+	*bits = cpu_to_be64(sctx->count << 3);
+	block_fn(sctx, sctx->buf, 1);
+
+	return 0;
+}
+
+static inline int lib_sha256_base_finish(struct sha256_state *sctx, uint8_t *out,
+					 unsigned int digest_size)
+{
+	uint32_t *digest = (uint32_t *)out;
+	int i;
+
+	for (i = 0; digest_size > 0; i++, digest_size -= sizeof(uint32_t))
+		*digest++ = cpu_to_be32(sctx->state[i]);
+
+	memzero_explicit(sctx, sizeof(*sctx));
+	return 0;
+}
+
+/*
+ * lib/crypto/sha256.c
+ *
+ * SHA-256, as specified in
+ * http://csrc.nist.gov/groups/STM/cavp/documents/shs/sha256-384-512.pdf
+ *
+ * SHA-256 code by Jean-Luc Cooke <jlcooke@certainkey.com>.
+ *
+ * Copyright (c) Jean-Luc Cooke <jlcooke@certainkey.com>
+ * Copyright (c) Andrew McDonald <andrew@mcdonald.org.uk>
+ * Copyright (c) 2002 James Morris <jmorris@intercode.com.au>
+ * Copyright (c) 2014 Red Hat Inc.
+ */
+static const uint32_t SHA256_K[] = {
+	0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
+	0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
+	0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
+	0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
+	0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
+	0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
+	0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
+	0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
+	0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
+	0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
+	0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
+	0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
+	0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
+	0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
+	0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
+	0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
+};
+
+static inline uint32_t Ch(uint32_t x, uint32_t y, uint32_t z)
+{
+	return z ^ (x & (y ^ z));
+}
+
+static inline uint32_t Maj(uint32_t x, uint32_t y, uint32_t z)
+{
+	return (x & y) | (z & (x | y));
+}
+
+#define e0(x)       (ror32(x, 2) ^ ror32(x, 13) ^ ror32(x, 22))
+#define e1(x)       (ror32(x, 6) ^ ror32(x, 11) ^ ror32(x, 25))
+#define s0(x)       (ror32(x, 7) ^ ror32(x, 18) ^ (x >> 3))
+#define s1(x)       (ror32(x, 17) ^ ror32(x, 19) ^ (x >> 10))
+
+static inline void LOAD_OP(int I, uint32_t *W, const uint8_t *input)
+{
+	W[I] = be32_to_cpu(*((__uint32_t *)input + I));
+}
+
+static inline void BLEND_OP(int I, uint32_t *W)
+{
+	W[I] = s1(W[I-2]) + W[I-7] + s0(W[I-15]) + W[I-16];
+}
+
+#define SHA256_ROUND(i, a, b, c, d, e, f, g, h) do {		\
+	uint32_t t1, t2;						\
+	t1 = h + e1(e) + Ch(e, f, g) + SHA256_K[i] + W[i];	\
+	t2 = e0(a) + Maj(a, b, c);				\
+	d += t1;						\
+	h = t1 + t2;						\
+} while (0)
+
+static void sha256_transform(uint32_t *state, const uint8_t *input, uint32_t *W)
+{
+	uint32_t a, b, c, d, e, f, g, h;
+	int i;
+
+	/* load the input */
+	for (i = 0; i < 16; i += 8) {
+		LOAD_OP(i + 0, W, input);
+		LOAD_OP(i + 1, W, input);
+		LOAD_OP(i + 2, W, input);
+		LOAD_OP(i + 3, W, input);
+		LOAD_OP(i + 4, W, input);
+		LOAD_OP(i + 5, W, input);
+		LOAD_OP(i + 6, W, input);
+		LOAD_OP(i + 7, W, input);
+	}
+
+	/* now blend */
+	for (i = 16; i < 64; i += 8) {
+		BLEND_OP(i + 0, W);
+		BLEND_OP(i + 1, W);
+		BLEND_OP(i + 2, W);
+		BLEND_OP(i + 3, W);
+		BLEND_OP(i + 4, W);
+		BLEND_OP(i + 5, W);
+		BLEND_OP(i + 6, W);
+		BLEND_OP(i + 7, W);
+	}
+
+	/* load the state into our registers */
+	a = state[0];  b = state[1];  c = state[2];  d = state[3];
+	e = state[4];  f = state[5];  g = state[6];  h = state[7];
+
+	/* now iterate */
+	for (i = 0; i < 64; i += 8) {
+		SHA256_ROUND(i + 0, a, b, c, d, e, f, g, h);
+		SHA256_ROUND(i + 1, h, a, b, c, d, e, f, g);
+		SHA256_ROUND(i + 2, g, h, a, b, c, d, e, f);
+		SHA256_ROUND(i + 3, f, g, h, a, b, c, d, e);
+		SHA256_ROUND(i + 4, e, f, g, h, a, b, c, d);
+		SHA256_ROUND(i + 5, d, e, f, g, h, a, b, c);
+		SHA256_ROUND(i + 6, c, d, e, f, g, h, a, b);
+		SHA256_ROUND(i + 7, b, c, d, e, f, g, h, a);
+	}
+
+	state[0] += a; state[1] += b; state[2] += c; state[3] += d;
+	state[4] += e; state[5] += f; state[6] += g; state[7] += h;
+}
+
+static void sha256_transform_blocks(struct sha256_state *sctx,
+				    const uint8_t *input, int blocks)
+{
+	uint32_t W[64];
+
+	do {
+		sha256_transform(sctx->state, input, W);
+		input += SHA256_BLOCK_SIZE;
+	} while (--blocks);
+
+	memzero_explicit(W, sizeof(W));
+}
+
+static void sha256_update(struct sha256_state *sctx, const uint8_t *data, unsigned int len)
+{
+	lib_sha256_base_do_update(sctx, data, len, sha256_transform_blocks);
+}
+
+static void __sha256_final(struct sha256_state *sctx, uint8_t *out, int digest_size)
+{
+	lib_sha256_base_do_finalize(sctx, sha256_transform_blocks);
+	lib_sha256_base_finish(sctx, out, digest_size);
+}
+
+static void sha256_final(struct sha256_state *sctx, uint8_t *out)
+{
+	__sha256_final(sctx, out, 32);
+}
+
+char *symhash_str(const char *name, size_t len, char hash_str[SYMHASH_STR_LEN])
+{
+	static const char hex[] = "0123456789abcdef";
+	uint8_t hash[SHA256_DIGEST_SIZE];
+	struct sha256_state sctx;
+	char *p = hash_str;
+
+	/*
+	 * If the symbol name has an initial dot, dedotify it before hashing to match
+	 * PPC64 behavior in arch/powerpc/kernel/module_64.c.
+	 */
+	if (name[0] == '.') {
+		name++;
+		len--;
+	}
+
+	sha256_init(&sctx);
+	sha256_update(&sctx, (const uint8_t *)name, len);
+	sha256_final(&sctx, hash);
+
+	/* Null-terminated prefix */
+	memcpy(p, SYMHASH_STR_PREFIX, SYMHASH_STR_PREFIX_LEN);
+	p += SYMHASH_STR_PREFIX_LEN;
+
+	/* Binary hash */
+	for (int i = 0; i < SHA256_DIGEST_SIZE; i++) {
+		*p++ = '\\';
+		*p++ = 'x';
+		*p++ = hex[(hash[i] & 0xf0) >> 4];
+		*p++ = hex[hash[i] & 0x0f];
+	}
+
+	hash_str[SYMHASH_STR_LEN - 1] = '\0';
+	return hash_str;
+}