From: Andreas Gruenbacher <agruen@suse.de>
Subject: SUSE/external support flag in modules
Patch-mainline: Never, SLES feature
References: bsc#148091 bsc#974406

Upon module load, check if a module is supported, and set the
N (TAINT_NO_SUPPORT) or X (TAINT_EXTERNAL_SUPPORT) tail flags
for unsupported or externally suported modules.

Function get_next_line() was removed by mainline commit 75893572d453
("modpost: remove get_next_text() and make {grab,release_}file static"),
restore it.

Contributions by:
Michal Marek, Michal Suchanek, Jeff Mahoney

Signed-off-by: Andreas Gruenbacher <agruen@suse.de>
---
 .../admin-guide/kernel-parameters.txt         |   8 +
 Documentation/admin-guide/sysctl/kernel.rst   |  41 ++---
 Documentation/admin-guide/tainted-kernels.rst |  48 ++---
 Makefile                                      |   5 +
 include/linux/module.h                        |   3 +
 include/linux/panic.h                         |  18 ++
 init/Kconfig.suse                             |  18 ++
 kernel/ksysfs.c                               |  27 +++
 kernel/module/main.c                          |  49 ++++++
 kernel/module/sysfs.c                         |  29 +++
 kernel/panic.c                                |   3 +
 kernel/sysctl.c                               |   9 +
 scripts/Makefile.modpost                      |   6 +
 scripts/mod/modpost.c                         | 166 +++++++++++++++++-
 14 files changed, 386 insertions(+), 44 deletions(-)

--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -6471,6 +6471,14 @@
 	unknown_nmi_panic
 			[X86] Cause panic on unknown NMI.
 
+	unsupported	Allow loading of unsupported kernel modules:
+			0 = only allow supported modules,
+			1 = warn when loading unsupported modules,
+			2 = don't warn.
+
+			CONFIG_SUSE_KERNEL_SUPPORTED must be enabled for this
+			to have any effect.
+
 	usbcore.authorized_default=
 			[USB] Default USB device authorization:
 			(default -1 = authorized except for wireless USB,
--- a/Documentation/admin-guide/sysctl/kernel.rst
+++ b/Documentation/admin-guide/sysctl/kernel.rst
@@ -1393,26 +1393,27 @@ tainted
 Non-zero if the kernel has been tainted. Numeric values, which can be
 ORed together. The letters are seen in "Tainted" line of Oops reports.
 
-======  =====  ==============================================================
-     1  `(P)`  proprietary module was loaded
-     2  `(F)`  module was force loaded
-     4  `(S)`  kernel running on an out of specification system
-     8  `(R)`  module was force unloaded
-    16  `(M)`  processor reported a Machine Check Exception (MCE)
-    32  `(B)`  bad page referenced or some unexpected page flags
-    64  `(U)`  taint requested by userspace application
-   128  `(D)`  kernel died recently, i.e. there was an OOPS or BUG
-   256  `(A)`  an ACPI table was overridden by user
-   512  `(W)`  kernel issued warning
-  1024  `(C)`  staging driver was loaded
-  2048  `(I)`  workaround for bug in platform firmware applied
-  4096  `(O)`  externally-built ("out-of-tree") module was loaded
-  8192  `(E)`  unsigned module was loaded
- 16384  `(L)`  soft lockup occurred
- 32768  `(K)`  kernel has been live patched
- 65536  `(X)`  Auxiliary taint, defined and used by for distros
-131072  `(T)`  The kernel was built with the struct randomization plugin
-======  =====  ==============================================================
+==========  =====  ==========================================================
+         1  `(P)`  proprietary module was loaded
+         2  `(F)`  module was force loaded
+	  4  `(S)`  kernel running on an out of specification system
+         8  `(R)`  module was force unloaded
+        16  `(M)`  processor reported a Machine Check Exception (MCE)
+        32  `(B)`  bad page referenced or some unexpected page flags
+        64  `(U)`  taint requested by userspace application
+       128  `(D)`  kernel died recently, i.e. there was an OOPS or BUG
+       256  `(A)`  an ACPI table was overridden by user
+       512  `(W)`  kernel issued warning
+      1024  `(C)`  staging driver was loaded
+      2048  `(I)`  workaround for bug in platform firmware applied
+      4096  `(O)`  externally-built ("out-of-tree") module was loaded
+      8192  `(E)`  unsigned module was loaded
+     16384  `(L)`  soft lockup occurred
+     32768  `(K)`  kernel has been live patched
+     65536  `(X)`  A kernel module with external support was loaded
+    131072  `(T)`  The kernel was built with the struct randomization plugin
+2147483648  `(n)`  An unsupported kernel module was loaded
+==========  =====  ==========================================================
 
 See Documentation/admin-guide/tainted-kernels.rst for more information.
 
--- a/Documentation/admin-guide/tainted-kernels.rst
+++ b/Documentation/admin-guide/tainted-kernels.rst
@@ -79,29 +79,31 @@ which bits are set::
 Table for decoding tainted state
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-===  ===  ======  ========================================================
-Bit  Log  Number  Reason that got the kernel tainted
-===  ===  ======  ========================================================
-  0  G/P       1  proprietary module was loaded
-  1  _/F       2  module was force loaded
-  2  _/S       4  kernel running on an out of specification system
-  3  _/R       8  module was force unloaded
-  4  _/M      16  processor reported a Machine Check Exception (MCE)
-  5  _/B      32  bad page referenced or some unexpected page flags
-  6  _/U      64  taint requested by userspace application
-  7  _/D     128  kernel died recently, i.e. there was an OOPS or BUG
-  8  _/A     256  ACPI table overridden by user
-  9  _/W     512  kernel issued warning
- 10  _/C    1024  staging driver was loaded
- 11  _/I    2048  workaround for bug in platform firmware applied
- 12  _/O    4096  externally-built ("out-of-tree") module was loaded
- 13  _/E    8192  unsigned module was loaded
- 14  _/L   16384  soft lockup occurred
- 15  _/K   32768  kernel has been live patched
- 16  _/X   65536  auxiliary taint, defined for and used by distros
- 17  _/T  131072  kernel was built with the struct randomization plugin
- 18  _/N  262144  an in-kernel test has been run
-===  ===  ======  ========================================================
+===  ===  ==========  ========================================================
+Bit  Log  Number      Reason that got the kernel tainted
+===  ===  ==========  ========================================================
+  0  G/P           1  proprietary module was loaded
+  1  _/F           2  module was force loaded
+  2  _/S           4  kernel running on an out of specification system
+  3  _/R           8  module was force unloaded
+  4  _/M          16  processor reported a Machine Check Exception (MCE)
+  5  _/B          32  bad page referenced or some unexpected page flags
+  6  _/U          64  taint requested by userspace application
+  7  _/D         128  kernel died recently, i.e. there was an OOPS or BUG
+  8  _/A         256  ACPI table overridden by user
+  9  _/W         512  kernel issued warning
+ 10  _/C        1024  staging driver was loaded
+ 11  _/I        2048  workaround for bug in platform firmware applied
+ 12  _/O        4096  externally-built ("out-of-tree") module was loaded
+ 13  _/E        8192  unsigned module was loaded
+ 14  _/L       16384  soft lockup occurred
+ 15  _/K       32768  kernel has been live patched
+ 16  _/X       65536  auxiliary taint, defined for and used by distros
+ 17  _/T      131072  kernel was built with the struct randomization plugin
+ 18  _/N      262144  an in-kernel test has been run
+ 31  _/n  2147483648  an unsupported kernel module was loaded
+
+===  ===  ==========  ========================================================
 
 Note: The character ``_`` is representing a blank in this table to make reading
 easier.
--- a/Makefile
+++ b/Makefile
@@ -379,6 +379,11 @@ else # !mixed-build
 
 include $(srctree)/scripts/Kbuild.include
 
+# Warn about unsupported modules in kernels built inside Autobuild
+ifneq ($(wildcard /.buildenv),)
+CFLAGS		+= -DUNSUPPORTED_MODULES=2
+endif
+
 # Read KERNELRELEASE from include/config/kernel.release (if it exists)
 KERNELRELEASE = $(call read-file, include/config/kernel.release)
 KERNELVERSION = $(VERSION)$(if $(PATCHLEVEL),.$(PATCHLEVEL)$(if $(SUBLEVEL),.$(SUBLEVEL)))$(EXTRAVERSION)
--- a/include/linux/module.h
+++ b/include/linux/module.h
@@ -572,6 +572,9 @@ bool is_module_address(unsigned long addr);
 bool __is_module_percpu_address(unsigned long addr, unsigned long *can_addr);
 bool is_module_percpu_address(unsigned long addr);
 bool is_module_text_address(unsigned long addr);
+#ifdef CONFIG_SUSE_KERNEL_SUPPORTED
+const char *supported_printable(int taint);
+#endif
 
 static inline bool within_module_core(unsigned long addr,
 				      const struct module *mod)
--- a/include/linux/panic.h
+++ b/include/linux/panic.h
@@ -32,6 +32,10 @@ extern int sysctl_panic_on_stackoverflow;
 
 extern bool crash_kexec_post_notifiers;
 
+#ifdef CONFIG_SUSE_KERNEL_SUPPORTED
+extern int suse_unsupported;
+#endif
+
 /*
  * panic_cpu is used for synchronizing panic() and crash_kexec() execution. It
  * holds a CPU number which is executing panic() currently. A value of
@@ -73,6 +77,20 @@ static inline void set_arch_panic_timeout(int timeout, int arch_default_timeout)
 #define TAINT_FLAGS_COUNT		19
 #define TAINT_FLAGS_MAX			((1UL << TAINT_FLAGS_COUNT) - 1)
 
+#ifdef CONFIG_SUSE_KERNEL_SUPPORTED
+/*
+ * Take the upper bits to hopefully allow them
+ * to stay the same for more than one release.
+ */
+#  define TAINT_EXTERNAL_SUPPORT	TAINT_AUX
+#  define TAINT_NO_SUPPORT		31
+#  if TAINT_FLAGS_COUNT >= TAINT_NO_SUPPORT
+#    error Upstream taint flags overlap with SUSE flags
+#  endif
+#  undef TAINT_FLAGS_COUNT
+#  define TAINT_FLAGS_COUNT		32
+#endif
+
 struct taint_flag {
 	char c_true;	/* character printed when tainted */
 	char c_false;	/* character printed when not tainted */
--- a/init/Kconfig.suse
+++ b/init/Kconfig.suse
@@ -1,2 +1,20 @@
 config SUSE_KERNEL
 	def_bool y
+
+config SUSE_KERNEL_SUPPORTED
+	bool "Enable enterprise support facility"
+	depends on SUSE_KERNEL
+	help
+	  This feature enables the handling of the "supported" module flag.
+	  This flag can be used to report unsupported module loads or even
+	  refuse them entirely. It is useful when ensuring that the kernel
+	  remains in a state that SUSE, or its technical partners, is
+	  prepared to support.
+
+	  Modules in the list of supported modules will be marked supported
+	  on build. The default enforcement mode is to report, but not
+	  deny, loading of unsupported modules.
+
+	  If you aren't building a kernel for an enterprise distribution,
+	  say n.
+
--- a/kernel/ksysfs.c
+++ b/kernel/ksysfs.c
@@ -229,6 +229,30 @@ static struct bin_attribute notes_attr __ro_after_init  = {
 struct kobject *kernel_kobj;
 EXPORT_SYMBOL_GPL(kernel_kobj);
 
+#ifdef CONFIG_SUSE_KERNEL_SUPPORTED
+const char *supported_printable(int taint)
+{
+	int mask = (1 << TAINT_PROPRIETARY_MODULE) | (1 << TAINT_NO_SUPPORT);
+	if ((taint & mask) == mask)
+		return "No, Proprietary and Unsupported modules are loaded";
+	else if (taint & (1 << TAINT_PROPRIETARY_MODULE))
+		return "No, Proprietary modules are loaded";
+	else if (taint & (1 << TAINT_NO_SUPPORT))
+		return "No, Unsupported modules are loaded";
+	else if (taint & (1 << TAINT_EXTERNAL_SUPPORT))
+		return "Yes, External";
+	else
+		return "Yes";
+}
+
+static ssize_t supported_show(struct kobject *kobj,
+			      struct kobj_attribute *attr, char *buf)
+{
+	return sprintf(buf, "%s\n", supported_printable(get_taint()));
+}
+KERNEL_ATTR_RO(supported);
+#endif
+
 static struct attribute * kernel_attrs[] = {
 	&fscaps_attr.attr,
 	&uevent_seqnum_attr.attr,
@@ -250,6 +274,9 @@ static struct attribute * kernel_attrs[] = {
 #ifndef CONFIG_TINY_RCU
 	&rcu_expedited_attr.attr,
 	&rcu_normal_attr.attr,
+#endif
+#ifdef CONFIG_SUSE_KERNEL_SUPPORTED
+	&supported_attr.attr,
 #endif
 	NULL
 };
--- a/kernel/module/main.c
+++ b/kernel/module/main.c
@@ -91,6 +91,22 @@ struct symsearch {
 	enum mod_license license;
 };
 
+#ifdef CONFIG_SUSE_KERNEL_SUPPORTED
+/* Allow unsupported modules switch. */
+#ifdef UNSUPPORTED_MODULES
+int suse_unsupported = UNSUPPORTED_MODULES;
+#else
+int suse_unsupported = 2;  /* don't warn when loading unsupported modules. */
+#endif
+
+static int __init unsupported_setup(char *str)
+{
+	get_option(&str, &suse_unsupported);
+	return 1;
+}
+__setup("unsupported=", unsupported_setup);
+#endif
+
 /*
  * Bounds of module text, for speeding up __module_address.
  * Protected by module_mutex.
@@ -964,6 +980,33 @@ static ssize_t show_taint(struct module_attribute *mattr,
 static struct module_attribute modinfo_taint =
 	__ATTR(taint, 0444, show_taint, NULL);
 
+#ifdef CONFIG_SUSE_KERNEL_SUPPORTED
+static void setup_modinfo_supported(struct module *mod, const char *s)
+{
+	if (!s) {
+		mod->taints |= (1 << TAINT_NO_SUPPORT);
+		return;
+	}
+
+	if (strcmp(s, "external") == 0)
+		mod->taints |= (1 << TAINT_EXTERNAL_SUPPORT);
+	else if (strcmp(s, "yes"))
+		mod->taints |= (1 << TAINT_NO_SUPPORT);
+}
+
+static ssize_t show_modinfo_supported(struct module_attribute *mattr,
+				      struct module_kobject *mk, char *buffer)
+{
+	return sprintf(buffer, "%s\n", supported_printable(mk->mod->taints));
+}
+
+static struct module_attribute modinfo_supported = {
+	.attr = { .name = "supported", .mode = 0444 },
+	.show = show_modinfo_supported,
+	.setup = setup_modinfo_supported,
+};
+#endif
+
 struct module_attribute *modinfo_attrs[] = {
 	&module_uevent,
 	&modinfo_version,
@@ -975,6 +1018,9 @@ struct module_attribute *modinfo_attrs[] = {
 #endif
 	&modinfo_initsize,
 	&modinfo_taint,
+#ifdef CONFIG_SUSE_KERNEL_SUPPORTED
+	&modinfo_supported,
+#endif
 #ifdef CONFIG_MODULE_UNLOAD
 	&modinfo_refcnt,
 #endif
@@ -3124,4 +3170,7 @@ void print_modules(void)
 		pr_cont(" [last unloaded: %s%s]", last_unloaded_module.name,
 			last_unloaded_module.taints);
 	pr_cont("\n");
+#ifdef CONFIG_SUSE_KERNEL_SUPPORTED
+	printk("Supported: %s\n", supported_printable(get_taint()));
+#endif
 }
--- a/kernel/module/sysfs.c
+++ b/kernel/module/sysfs.c
@@ -399,8 +399,37 @@ int mod_sysfs_setup(struct module *mod,
 	add_sect_attrs(mod, info);
 	add_notes_attrs(mod, info);
 
+#ifdef CONFIG_SUSE_KERNEL_SUPPORTED
+	if (mod->taints & (1 << TAINT_EXTERNAL_SUPPORT)) {
+		pr_notice("%s: externally supported module, "
+			  "setting X kernel taint flag.\n", mod->name);
+		add_taint(TAINT_EXTERNAL_SUPPORT, LOCKDEP_STILL_OK);
+	} else if (mod->taints & (1 << TAINT_NO_SUPPORT)) {
+		if (suse_unsupported == 0) {
+			printk(KERN_WARNING "%s: module not supported by "
+			       "SUSE, refusing to load. To override, echo "
+			       "1 > /proc/sys/kernel/unsupported\n", mod->name);
+			err = -ENOEXEC;
+			goto out_remove_attrs;
+		}
+		add_taint(TAINT_NO_SUPPORT, LOCKDEP_STILL_OK);
+		if (suse_unsupported == 1) {
+			printk(KERN_WARNING "%s: module is not supported by "
+			       "SUSE. Our support organization may not be "
+			       "able to address your support request if it "
+			       "involves a kernel fault.\n", mod->name);
+		}
+	}
+#endif
+
 	return 0;
 
+#ifdef CONFIG_SUSE_KERNEL_SUPPORTED
+out_remove_attrs:
+	remove_notes_attrs(mod);
+	remove_sect_attrs(mod);
+	del_usage_links(mod);
+#endif
 out_unreg_modinfo_attrs:
 	module_remove_modinfo_attrs(mod, -1);
 out_unreg_param:
--- a/kernel/panic.c
+++ b/kernel/panic.c
@@ -470,6 +470,9 @@ const struct taint_flag taint_flags[TAINT_FLAGS_COUNT] = {
 	[ TAINT_AUX ]			= { 'X', ' ', true },
 	[ TAINT_RANDSTRUCT ]		= { 'T', ' ', true },
 	[ TAINT_TEST ]			= { 'N', ' ', true },
+#ifdef CONFIG_SUSE_KERNEL_SUPPORTED
+	[ TAINT_NO_SUPPORT ]		= { 'n', ' ', true },
+#endif
 };
 
 /**
--- a/kernel/sysctl.c
+++ b/kernel/sysctl.c
@@ -1840,6 +1840,15 @@ static struct ctl_table kern_table[] = {
 		.extra1		= &pid_max_min,
 		.extra2		= &pid_max_max,
 	},
+#if defined(CONFIG_MODULES) && defined(CONFIG_SUSE_KERNEL_SUPPORTED)
+	{
+		.procname	= "unsupported",
+		.data		= &suse_unsupported,
+		.maxlen		= sizeof(int),
+		.mode		= 0644,
+		.proc_handler	= &proc_dointvec,
+	},
+#endif
 	{
 		.procname	= "panic_on_oops",
 		.data		= &panic_on_oops,
--- a/scripts/Makefile.modpost
+++ b/scripts/Makefile.modpost
@@ -56,6 +56,11 @@ ifneq ($(findstring i,$(filter-out --%,$(MAKEFLAGS))),)
 modpost-args += -n
 endif
 
+ifdef CONFIG_SUSE_KERNEL_SUPPORTED
+modpost-args += -S $(firstword $(wildcard $(extmod-Module.supported) \
+		                          Module.supported /dev/null))
+endif
+
 # Read out modules.order to pass in modpost.
 # Otherwise, allmodconfig would fail with "Argument list too long".
 ifdef KBUILD_MODULES
@@ -119,6 +124,7 @@ endif
 
 modpost-args += -e $(addprefix -i , $(KBUILD_EXTRA_SYMBOLS))
 
+extmod-Module.supported := $(KBUILD_EXTMOD)/Module.supported
 endif # ($(KBUILD_EXTMOD),)
 
 ifneq ($(missing-input),)
--- a/scripts/mod/modpost.c
+++ b/scripts/mod/modpost.c
@@ -17,10 +17,12 @@
 #include <stdio.h>
 #include <ctype.h>
 #include <string.h>
+#include <fnmatch.h>
 #include <limits.h>
 #include <stdbool.h>
 #include <errno.h>
 #include "modpost.h"
+#include "../../include/generated/autoconf.h"
 #include "../../include/linux/license.h"
 
 /* Are we using CONFIG_MODVERSIONS? */
@@ -1783,6 +1785,133 @@ static void mod_set_crcs(struct module *mod)
 	free(buf);
 }
 
+#ifdef CONFIG_SUSE_KERNEL_SUPPORTED
+/*
+ * Replace dashes with underscores.
+ * Dashes inside character range patterns (e.g. [0-9]) are left unchanged.
+ * (copied from module-init-tools/util.c)
+ */
+static char *underscores(char *string)
+{
+	unsigned int i;
+
+	if (!string)
+		return NULL;
+
+	for (i = 0; string[i]; i++) {
+		switch (string[i]) {
+		case '-':
+			string[i] = '_';
+			break;
+
+		case ']':
+			warn("Unmatched bracket in %s\n", string);
+			break;
+
+		case '[':
+			i += strcspn(&string[i], "]");
+			if (!string[i])
+				warn("Unmatched bracket in %s\n", string);
+			break;
+		}
+	}
+	return string;
+}
+
+/**
+  * Return a copy of the next line in a mmap'ed file.
+  * spaces in the beginning of the line is trimmed away.
+  * Return a pointer to a static buffer.
+  **/
+static char *get_next_line(unsigned long *pos, void *file, unsigned long size)
+{
+	static char line[4096];
+	int skip = 1;
+	size_t len = 0;
+	signed char *p = (signed char *)file + *pos;
+	char *s = line;
+
+	for (; *pos < size ; (*pos)++) {
+		if (skip && isspace(*p)) {
+			p++;
+			continue;
+		}
+		skip = 0;
+		if (*p != '\n' && (*pos < size)) {
+			len++;
+			*s++ = *p++;
+			if (len > 4095)
+				break; /* Too long, stop */
+		} else {
+			/* End of string */
+			*s = '\0';
+			return line;
+		}
+	}
+	/* End of buffer */
+	return NULL;
+}
+
+char *supported_file;
+unsigned long supported_size;
+
+static const char *supported(const char *modname)
+{
+	unsigned long pos = 0;
+	char *line;
+
+	/* In a first shot, do a simple linear scan. */
+	while ((line = get_next_line(&pos, supported_file,
+				     supported_size))) {
+		const char *how = "yes";
+		char *l = line;
+		char *pat_basename, *mod, *orig_mod, *mod_basename;
+
+		/* optional type-of-support flag */
+		for (l = line; *l != '\0'; l++) {
+			if (*l == ' ' || *l == '\t') {
+				*l = '\0';
+				how = l + 1;
+				break;
+			}
+		}
+		/* strip .ko extension */
+		l = line + strlen(line);
+		if (l - line > 3 && !strcmp(l-3, ".ko"))
+			*(l-3) = '\0';
+
+		/*
+		 * convert dashes to underscores in the last path component
+		 * of line and mod
+		 */
+		if ((pat_basename = strrchr(line, '/')))
+			pat_basename++;
+		else
+			pat_basename = line;
+		underscores(pat_basename);
+
+		orig_mod = mod = strdup(modname);
+		if ((mod_basename = strrchr(mod, '/')))
+			mod_basename++;
+		else
+			mod_basename = mod;
+		underscores(mod_basename);
+
+		/* only compare the last component if no wildcards are used */
+		if (strcspn(line, "[]*?") == strlen(line)) {
+			line = pat_basename;
+			mod = mod_basename;
+		}
+		if (!fnmatch(line, mod, 0)) {
+			free(orig_mod);
+			return how;
+		}
+		free(orig_mod);
+	}
+	return NULL;
+}
+#endif
+
 static void read_symbols(const char *modname)
 {
 	const char *symname;
@@ -2040,6 +2169,15 @@ static void add_exported_symbols(struct buffer *buf, struct module *mod)
 	}
 }
 
+#ifdef CONFIG_SUSE_KERNEL_SUPPORTED
+static void add_supported_flag(struct buffer *b, struct module *mod)
+{
+	const char *how = supported(mod->name);
+	if (how)
+		buf_printf(b, "\nMODULE_INFO(supported, \"%s\");\n", how);
+}
+#endif
+
 /**
  * Record CRCs for unresolved symbols
  **/
@@ -2197,6 +2335,9 @@ static void write_mod_c_file(struct module *mod)
 
 	add_header(&buf, mod);
 	add_exported_symbols(&buf, mod);
+#ifdef CONFIG_SUSE_KERNEL_SUPPORTED
+	add_supported_flag(&buf, mod);
+#endif
 	add_versions(&buf, mod);
 	add_depends(&buf, mod);
 	add_moddevtable(&buf, mod);
@@ -2214,6 +2355,17 @@ static void write_mod_c_file(struct module *mod)
 	free(buf.p);
 }
 
+#ifdef CONFIG_SUSE_KERNEL_SUPPORTED
+static void read_supported(const char *fname)
+{
+	if (fname) {
+		supported_file = grab_file(fname, &supported_size);
+		if (!supported_file)
+			; /* ignore error */
+	}
+}
+#endif
+
 /* parse Module.symvers file. line format:
  * 0x12345678<tab>symbol<tab>module<tab>export<tab>namespace
  **/
@@ -2330,11 +2482,14 @@ int main(int argc, char **argv)
 	struct module *mod;
 	char *missing_namespace_deps = NULL;
 	char *dump_write = NULL, *files_source = NULL;
+#ifdef CONFIG_SUSE_KERNEL_SUPPORTED
+	const char *supported = NULL;
+#endif
 	int opt;
 	LIST_HEAD(dump_lists);
 	struct dump_list *dl, *dl2;
 
-	while ((opt = getopt(argc, argv, "ei:mnT:o:awENd:")) != -1) {
+	while ((opt = getopt(argc, argv, "ei:mnT:o:awENd:S:")) != -1) {
 		switch (opt) {
 		case 'e':
 			external_module = true;
@@ -2371,11 +2526,20 @@ int main(int argc, char **argv)
 		case 'd':
 			missing_namespace_deps = optarg;
 			break;
+		case 'S':
+#ifdef CONFIG_SUSE_KERNEL_SUPPORTED
+			supported = optarg;
+#endif
+			break;
 		default:
 			exit(1);
 		}
 	}
 
+#ifdef CONFIG_SUSE_KERNEL_SUPPORTED
+	read_supported(supported);
+#endif
+
 	list_for_each_entry_safe(dl, dl2, &dump_lists, list) {
 		read_dump(dl->file);
 		list_del(&dl->list);
