CVE-2014-9114: Improper Neutralization of Special Elements used in a Command ('Command Injection') (CWE-77)
value: Blkid in util-linux before 2.26rc-1 allows local users to execute arbitrary code.
CWE description: The product constructs all or part of a command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended command when it is sent to a downstream component.
CVSS v3 base score: 7.8
Published: 2017-03-31T16:59Z
Repository: https://github.com/karelzak/util-linux
Language: C
Files changed: libblkid/src/read.c, libblkid/src/save.c, misc-utils/blkid.8, misc-utils/blkid.c
Commit date: 2014-11-27 13:39:35 +0100
Commit message: libblkid: care about unsafe chars in cache

The high-level libblkid API uses /run/blkid/blkid.tab cache to
store probing results. The cache format is

   <device NAME="value" ...>devname</device>

and unfortunately the cache code does not escape quotation marks:

   # mkfs.ext4 -L 'AAA"BBB'

   # cat /run/blkid/blkid.tab
   ...
   <device ... LABEL="AAA"BBB" ...>/dev/sdb1</device>

such string is later incorrectly parsed and blkid(8) returns
nonsenses. And for use-cases like

   # eval $(blkid -o export /dev/sdb1)

it's also insecure.

Note that mount, udevd and blkid -p are based on low-level libblkid
API, it bypass the cache and directly read data from the devices.

The current udevd upstream does not depend on blkid(8) output at all,
it's directly linked with the library and all unsafe chars are encoded by
\x<hex> notation.

   # mkfs.ext4 -L 'X"`/tmp/foo` "' /dev/sdb1
   # udevadm info --export-db | grep LABEL
   ...
   E: ID_FS_LABEL=X__/tmp/foo___
   E: ID_FS_LABEL_ENC=X\x22\x60\x2ftmp\x2ffoo\x60\x20\x22

Signed-off-by: Karel Zak <kzak@redhat.com>

Diff: diff --git a/libblkid/src/read.c b/libblkid/src/read.c
index 0e91c9c2..81ab0dfd 100644
--- a/libblkid/src/read.c
+++ b/libblkid/src/read.c
@@ -250,19 +250,34 @@ static int parse_token(char **name, char **value, char **cp)
 	**value = '\0';
 	*name = strip_line(*cp);
 	*value = skip_over_blank(*value + 1);
 
 	if (**value == '"') {
-		end = strchr(*value + 1, '"');
-		if (!end) {
+		char *p = end = *value + 1;
+
+		/* convert 'foo\"bar'  to 'foo"bar' */
+		while (*p) {
+			if (*p == '\\') {
+				p++;
+				*end = *p;
+			} else {
+				*end = *p;
+				if (*p == '"')
+					break;
+			}
+			p++;
+			end++;
+		}
+
+		if (*end != '"') {
 			DBG(READ, ul_debug("unbalanced quotes at: %s", *value));
 			*cp = *value;
 			return -BLKID_ERR_CACHE;
 		}
 		(*value)++;
 		*end = '\0';
-		end++;
+		end = ++p;
 	} else {
 		end = skip_over_word(*value);
 		if (*end) {
 			*end = '\0';
 			end++;
diff --git a/libblkid/src/save.c b/libblkid/src/save.c
index 8216f091..5e8bbee8 100644
--- a/libblkid/src/save.c
+++ b/libblkid/src/save.c
@@ -24,10 +24,25 @@
 
 #include "closestream.h"
 
 #include "blkidP.h"
 
+
+static void save_quoted(const char *data, FILE *file)
+{
+	const char *p;
+
+	fputc('"', file);
+	for (p = data; p && *p; p++) {
+		if ((unsigned char) *p == 0x22 ||		/* " */
+		    (unsigned char) *p == 0x5c)			/* \ */
+			fputc('\\', file);
+
+		fputc(*p, file);
+	}
+	fputc('"', file);
+}
 static int save_dev(blkid_dev dev, FILE *file)
 {
 	struct list_head *p;
 
 	if (!dev || dev->bid_name[0] != '/')
@@ -41,13 +56,18 @@ static int save_dev(blkid_dev dev, FILE *file)
 			(long) dev->bid_time,
 			(long) dev->bid_utime);
 
 	if (dev->bid_pri)
 		fprintf(file, " PRI=\"%d\"", dev->bid_pri);
+
 	list_for_each(p, &dev->bid_tags) {
 		blkid_tag tag = list_entry(p, struct blkid_struct_tag, bit_tags);
-		fprintf(file, " %s=\"%s\"", tag->bit_name,tag->bit_val);
+
+		fputc(' ', file);			/* space between tags */
+		fputs(tag->bit_name, file);		/* tag NAME */
+		fputc('=', file);			/* separator between NAME and VALUE */
+		save_quoted(tag->bit_val, file);	/* tag "VALUE" */
 	}
 	fprintf(file, ">%s</device>\n", dev->bid_name);
 
 	return 0;
 }
diff --git a/misc-utils/blkid.8 b/misc-utils/blkid.8
index 156a14b6..c95b833d 100644
--- a/misc-utils/blkid.8
+++ b/misc-utils/blkid.8
@@ -198,11 +198,14 @@ The udev output returns the ID_FS_AMBIVALENT tag if more superblocks are detecte
 and ID_PART_ENTRY_* tags are always returned for all partitions including empty
 partitions.  This output format is \fBDEPRECATED\fR.
 .TP
 .B export
 print key=value pairs for easy import into the environment; this output format
-is automatically enabled when I/O Limits (\fB-i\fR option) are requested
+is automatically enabled when I/O Limits (\fB-i\fR option) are requested.
+
+The non-printing characters are encoded by ^ and M- notation and all
+potentially unsafe characters are escaped.
 .RE
 .TP
 .BI \-O " offset"
 Probe at the given \fIoffset\fR (only useful with \fB-p\fR).  This option can be
 used together with the \fB-i\fR option.
diff --git a/misc-utils/blkid.c b/misc-utils/blkid.c
index a6ca660c..1bd86465 100644
--- a/misc-utils/blkid.c
+++ b/misc-utils/blkid.c
@@ -304,20 +304,20 @@ static void print_value(int output, int num, const char *devname,
 	} else if (output & OUTPUT_EXPORT_LIST) {
 		if (num == 1 && devname)
 			printf("DEVNAME=%s\n", devname);
 		fputs(name, stdout);
 		fputs("=", stdout);
-		safe_print(value, valsz, NULL);
+		safe_print(value, valsz, " \\\"'$`<>");
 		fputs("\n", stdout);
 
 	} else {
 		if (num == 1 && devname)
 			printf("%s:", devname);
 		fputs(" ", stdout);
 		fputs(name, stdout);
 		fputs("=\"", stdout);
-		safe_print(value, valsz, "\"");
+		safe_print(value, valsz, "\"\\");
 		fputs("\"", stdout);
 	}
 }
 
 static void print_tags(blkid_dev dev, char *show[], int output)

Vulnerable code: 		end = strchr(*value + 1, '"');
		if (!end) {
		end++;
		fprintf(file, " %s=\"%s\"", tag->bit_name,tag->bit_val);
is automatically enabled when I/O Limits (\fB-i\fR option) are requested
		safe_print(value, valsz, NULL);
		safe_print(value, valsz, "\"");
Fixed code: 		char *p = end = *value + 1;

		/* convert 'foo\"bar'  to 'foo"bar' */
		while (*p) {
			if (*p == '\\') {
				p++;
				*end = *p;
			} else {
				*end = *p;
				if (*p == '"')
					break;
			}
			p++;
			end++;
		}

		if (*end != '"') {
		end = ++p;

static void save_quoted(const char *data, FILE *file)
{
	const char *p;

	fputc('"', file);
	for (p = data; p && *p; p++) {
		if ((unsigned char) *p == 0x22 ||		/* " */
		    (unsigned char) *p == 0x5c)			/* \ */
			fputc('\\', file);

		fputc(*p, file);
	}
	fputc('"', file);
}


		fputc(' ', file);			/* space between tags */
		fputs(tag->bit_name, file);		/* tag NAME */
		fputc('=', file);			/* separator between NAME and VALUE */
		save_quoted(tag->bit_val, file);	/* tag "VALUE" */
is automatically enabled when I/O Limits (\fB-i\fR option) are requested.

The non-printing characters are encoded by ^ and M- notation and all
potentially unsafe characters are escaped.
		safe_print(value, valsz, " \\\"'$`<>");
		safe_print(value, valsz, "\"\\");