svn commit: r351428 - stable/11/usr.bin/vtfontcvt

Ed Maste emaste at FreeBSD.org
Fri Aug 23 16:26:07 UTC 2019


Author: emaste
Date: Fri Aug 23 16:26:06 2019
New Revision: 351428
URL: https://svnweb.freebsd.org/changeset/base/351428

Log:
  vtfontcvt: improve BDF and hex font parsing
  
  MFC r348661: vtfontcvt: rework height/width setting
  
  Introduce VFNT_MAXDIMENSION to replace bare 128, add set_height, and
  consistently use set_height and set_width.
  
  Submitted by:	Dmitry Wagin
  
  MFC r348662: vtfontcvt: include width and height in verbose info
  
  Submitted by:	Dmitry Wagin
  
  MFC r348668: vtfontcvt: zero memory allocated by xmalloc
  
  Submitted by:	Dmitry Wagin
  
  MFC r348692: vtfontcvt: exit on error if the input font has too many glyphs
  
  The kernel has a limit of 131072 glyphs in a font; add the same check to
  vtfontcvt so that we won't create a font file that the kernel will not
  load.
  
  MFC r348796: vtfontcvt: allow out-of-order glyphs
  
  Reported by:	mi
  
  MFC r349049: vtfontcvt: add comments in add_glyph
  
  MFC r349100: vtfontcvt: improve BDF and hex font parsing
  
  Support larger font sizes.
  
  Submitted by:	Dmitry Wagin (original version)
  
  MFC r349101: vtfontcvt: initialize bbwbytes to avoid GCC 4.2.1 warning
  
  MFC r349105: vtfontcvt: initialize another variable to quiet GCC warning
  
  I believe this case could be triggered by a broken .bdf font.
  
  MFC r349107: vtfontcvt: improve .bdf verification
  
  Previously we would crash if the BBX y-offset was outside of the font
  bounding box.
  
  Reported by:	afl fuzzer
  
  MFC r349108: vtfontcvt: improve .bdf validation
  
  Previously if we had a BBX entry that had invalid values (e.g. bounding
  box outside of font bounding box) and failed sscanf (e.g., because it
  had fewer than four values) we skipped the BBX value validation and then
  triggered an assertion failure.
  
  Reported by:	afl fuzzer
  
  MFC r349111: vtfontcvt: correct typo in hex parsing update
  
  Submitted by:	Dmitry Wagin
  
  MFC r349333: vtfontcvt: improve .bdf validation
  
  Previously if we had a FONTBOUNDINGBOX or DWIDTH entry that had missing
  or invalid values and and failed sscanf, we would proceeded with
  partially initialized bounding box / device width variables.
  
  Reported by:	afl fuzzer (FONTBOUNDINGBOX)
  
  PR:		205707
  Sponsored by:	The FreeBSD Foundation

Modified:
  stable/11/usr.bin/vtfontcvt/vtfontcvt.c
Directory Properties:
  stable/11/   (props changed)

Modified: stable/11/usr.bin/vtfontcvt/vtfontcvt.c
==============================================================================
--- stable/11/usr.bin/vtfontcvt/vtfontcvt.c	Fri Aug 23 16:19:58 2019	(r351427)
+++ stable/11/usr.bin/vtfontcvt/vtfontcvt.c	Fri Aug 23 16:26:06 2019	(r351428)
@@ -49,6 +49,8 @@ __FBSDID("$FreeBSD$");
 #define VFNT_MAP_NORMAL_RH 1
 #define VFNT_MAP_BOLD 2
 #define VFNT_MAP_BOLD_RH 3
+#define VFNT_MAXGLYPHS 131072
+#define VFNT_MAXDIMENSION 128
 
 static unsigned int width = 8, wbytes, height = 16;
 
@@ -101,7 +103,7 @@ xmalloc(size_t size)
 {
 	void *m;
 
-	if ((m = malloc(size)) == NULL)
+	if ((m = calloc(1, size)) == NULL)
 		errx(1, "memory allocation failure");
 	return (m);
 }
@@ -109,7 +111,7 @@ xmalloc(size_t size)
 static int
 add_mapping(struct glyph *gl, unsigned int c, unsigned int map_idx)
 {
-	struct mapping *mp;
+	struct mapping *mp, *mp_temp;
 	struct mapping_list *ml;
 
 	mapping_total++;
@@ -120,10 +122,19 @@ add_mapping(struct glyph *gl, unsigned int c, unsigned
 	mp->m_length = 0;
 
 	ml = &maps[map_idx];
-	if (TAILQ_LAST(ml, mapping_list) != NULL &&
-	    TAILQ_LAST(ml, mapping_list)->m_char >= c)
-		errx(1, "Bad ordering at character %u", c);
-	TAILQ_INSERT_TAIL(ml, mp, m_list);
+	if (TAILQ_LAST(ml, mapping_list) == NULL ||
+	    TAILQ_LAST(ml, mapping_list)->m_char < c) {
+		/* Common case: empty list or new char at end of list. */
+		TAILQ_INSERT_TAIL(ml, mp, m_list);
+	} else {
+		/* Find insertion point for char; cannot be at end. */
+		TAILQ_FOREACH(mp_temp, ml, m_list) {
+			if (mp_temp->m_char >= c) {
+				TAILQ_INSERT_BEFORE(mp_temp, mp, m_list);
+				break;
+			}
+		}
+	}
 
 	map_count[map_idx]++;
 	mapping_unique++;
@@ -165,6 +176,7 @@ add_glyph(const uint8_t *bytes, unsigned int map_idx, 
 	glyph_total++;
 	glyph_count[map_idx]++;
 
+	/* Return existing glyph if we have an identical one. */
 	hash = fnv_32_buf(bytes, wbytes * height, FNV1_32_INIT) % FONTCVT_NHASH;
 	SLIST_FOREACH(gl, &glyph_hash[hash], g_hash) {
 		if (memcmp(gl->g_data, bytes, wbytes * height) == 0) {
@@ -173,6 +185,7 @@ add_glyph(const uint8_t *bytes, unsigned int map_idx, 
 		}
 	}
 
+	/* Allocate new glyph. */
 	gl = xmalloc(sizeof *gl);
 	gl->g_data = xmalloc(wbytes * height);
 	memcpy(gl->g_data, bytes, wbytes * height);
@@ -183,6 +196,8 @@ add_glyph(const uint8_t *bytes, unsigned int map_idx, 
 	SLIST_INSERT_HEAD(&glyph_hash[hash], gl, g_hash);
 
 	glyph_unique++;
+	if (glyph_unique > VFNT_MAXGLYPHS)
+		errx(1, "too many glyphs (%u)", glyph_unique);
 	return (gl);
 }
 
@@ -208,112 +223,243 @@ add_char(unsigned curchar, unsigned map_idx, uint8_t *
 	return (0);
 }
 
-
+/*
+ * Right-shift glyph row by _shift_ bits. Row _len_ bits wide, _size_ bytes.
+ */
 static int
-parse_bitmap_line(uint8_t *left, uint8_t *right, unsigned int line,
-    unsigned int dwidth)
+rshift_row(uint8_t *line, size_t size, size_t len, size_t shift)
 {
-	uint8_t *p;
-	unsigned int i, subline;
+	size_t d, s, i;
+	uint16_t t;
 
-	if (dwidth != width && dwidth != width * 2)
-		errx(1, "Bitmap with unsupported width %u!", dwidth);
+	assert(size > 0 && len > 0);
+	assert(size * 8 >= len);
 
-	/* Move pixel data right to simplify splitting double characters. */
-	line >>= (howmany(dwidth, 8) * 8) - dwidth;
+	if (shift == 0)
+		return (0);
 
-	for (i = dwidth / width; i > 0; i--) {
-		p = (i == 2) ? right : left;
+	d = shift / 8;
+	s = 8 - shift % 8;
+	i = howmany(len, 8);
 
-		subline = line & ((1 << width) - 1);
-		subline <<= (howmany(width, 8) * 8) - width;
+	while (i > 0) {
+		i--;
 
-		if (wbytes == 1) {
-			*p = subline;
-		} else if (wbytes == 2) {
-			*p++ = subline >> 8;
-			*p = subline;
-		} else {
-			errx(1, "Unsupported wbytes %u!", wbytes);
-		}
+		t = *(line + i);
+		*(line + i) = 0;
 
-		line >>= width;
+		t <<= s;
+
+		if (i + d + 1 < size)
+			*(line + i + d + 1) |= (uint8_t)t;
+		if (i + d < size)
+			*(line + i + d) = t >> 8;
 	}
+	return (0);
+}
 
+/*
+ * Split double-width characters into left and right half. Single-width
+ * characters in _left_ only.
+ */
+static int
+split_row(uint8_t *left, uint8_t *right, uint8_t *line, size_t w)
+{
+	size_t s, i;
+
+	s = wbytes * 8 - width;
+
+	memcpy(left, line, wbytes);
+	*(left + wbytes - 1) &= 0xFF << s;
+
+	if (w > width) { /* Double-width character. */
+		uint8_t t;
+
+		for (i = 0; i < wbytes; i++) {
+			t = *(line + wbytes + i - 1);
+			t <<= 8 - s;
+			t |= *(line + wbytes + i) >> s;
+			*(right + i) = t;
+		}
+		*(right + wbytes - 1) &= 0xFF << s;
+	}
 	return (0);
 }
 
+static void
+set_height(int h)
+{
+	if (h <= 0 || h > VFNT_MAXDIMENSION)
+		errx(1, "invalid height %d", h);
+	height = h;
+}
+
+static void
+set_width(int w)
+{
+	if (w <= 0 || w > VFNT_MAXDIMENSION)
+		errx(1, "invalid width %d", w);
+	width = w;
+	wbytes = howmany(width, 8);
+}
+
 static int
 parse_bdf(FILE *fp, unsigned int map_idx)
 {
-	char *ln;
+	char *line, *ln, *p;
 	size_t length;
-	uint8_t bytes[wbytes * height], bytes_r[wbytes * height];
-	unsigned int curchar = 0, dwidth = 0, i, line;
+	uint8_t *bytes, *bytes_r;
+	unsigned int curchar = 0, i, j, linenum = 0, bbwbytes;
+	int bbw, bbh, bbox, bboy;		/* Glyph bounding box. */
+	int fbbw = 0, fbbh, fbbox, fbboy;	/* Font bounding box. */
+	int dwidth = 0, dwy = 0;
+	int rv = -1;
+	char spc = '\0';
 
+	/*
+	 * Step 1: Parse FONT logical font descriptor and FONTBOUNDINGBOX
+	 * bounding box.
+	 */
 	while ((ln = fgetln(fp, &length)) != NULL) {
+		linenum++;
 		ln[length - 1] = '\0';
 
-		if (strncmp(ln, "ENCODING ", 9) == 0) {
-			curchar = atoi(ln + 9);
+		if (strncmp(ln, "FONT ", 5) == 0) {
+			p = ln + 5;
+			i = 0;
+			while ((p = strchr(p, '-')) != NULL) {
+				p++;
+				i++;
+				if (i == 11) {
+					spc = *p;
+					break;
+				}
+			}
+		} else if (strncmp(ln, "FONTBOUNDINGBOX ", 16) == 0) {
+			if (sscanf(ln + 16, "%d %d %d %d", &fbbw, &fbbh, &fbbox,
+			    &fbboy) != 4)
+				errx(1, "invalid FONTBOUNDINGBOX at line %u",
+				    linenum);
+			set_width(fbbw);
+			set_height(fbbh);
+			break;
 		}
+	}
+	if (fbbw == 0)
+		errx(1, "broken font header");
+	if (spc != 'c' && spc != 'C')
+		errx(1, "font spacing \"C\" (character cell) required");
 
+	/* Step 2: Validate DWIDTH (Device Width) of all glyphs. */
+	while ((ln = fgetln(fp, &length)) != NULL) {
+		linenum++;
+		ln[length - 1] = '\0';
+
 		if (strncmp(ln, "DWIDTH ", 7) == 0) {
-			dwidth = atoi(ln + 7);
+			if (sscanf(ln + 7, "%d %d", &dwidth, &dwy) != 2)
+				errx(1, "invalid DWIDTH at line %u", linenum);
+			if (dwy != 0 || (dwidth != fbbw && dwidth * 2 != fbbw))
+				errx(1, "bitmap with unsupported DWIDTH %d %d at line %u",
+				    dwidth, dwy, linenum);
+			if (dwidth < fbbw)
+				set_width(dwidth);
 		}
+	}
 
-		if (strncmp(ln, "BITMAP", 6) == 0 &&
+	/* Step 3: Restart at the beginning of the file and read glyph data. */
+	dwidth = bbw = bbh = 0;
+	rewind(fp);
+	linenum = 0;
+	bbwbytes = 0; /* GCC 4.2.1 "may be used uninitialized" workaround. */
+	bytes = xmalloc(wbytes * height);
+	bytes_r = xmalloc(wbytes * height);
+	line = xmalloc(wbytes * 2);
+	while ((ln = fgetln(fp, &length)) != NULL) {
+		linenum++;
+		ln[length - 1] = '\0';
+
+		if (strncmp(ln, "ENCODING ", 9) == 0) {
+			curchar = atoi(ln + 9);
+		} else if (strncmp(ln, "DWIDTH ", 7) == 0) {
+			dwidth = atoi(ln + 7);
+		} else if (strncmp(ln, "BBX ", 4) == 0) {
+			if (sscanf(ln + 4, "%d %d %d %d", &bbw, &bbh, &bbox,
+			     &bboy) != 4)
+				errx(1, "invalid BBX at line %u", linenum);
+			if (bbw < 1 || bbh < 1 || bbw > fbbw || bbh > fbbh ||
+			    bbox < fbbox || bboy < fbboy ||
+			    bbh + bboy > fbbh + fbboy)
+				errx(1, "broken bitmap with BBX %d %d %d %d at line %u",
+				    bbw, bbh, bbox, bboy, linenum);
+			bbwbytes = howmany(bbw, 8);
+		} else if (strncmp(ln, "BITMAP", 6) == 0 &&
 		    (ln[6] == ' ' || ln[6] == '\0')) {
+			if (dwidth == 0 || bbw == 0 || bbh == 0)
+				errx(1, "broken char header at line %u!",
+				    linenum);
+			memset(bytes, 0, wbytes * height);
+			memset(bytes_r, 0, wbytes * height);
+
 			/*
-			 * Assume that the next _height_ lines are bitmap
-			 * data.  ENDCHAR is allowed to terminate the bitmap
+			 * Assume that the next _bbh_ lines are bitmap data.
+			 * ENDCHAR is allowed to terminate the bitmap
 			 * early but is not otherwise checked; any extra data
 			 * is ignored.
 			 */
-			for (i = 0; i < height; i++) {
+			for (i = (fbbh + fbboy) - (bbh + bboy);
+			    i < (unsigned int)((fbbh + fbboy) - bboy); i++) {
 				if ((ln = fgetln(fp, &length)) == NULL)
-					errx(1, "Unexpected EOF!");
+					errx(1, "unexpected EOF");
+				linenum++;
 				ln[length - 1] = '\0';
-				if (strcmp(ln, "ENDCHAR") == 0) {
-					memset(bytes + i * wbytes, 0,
-					    (height - i) * wbytes);
-					memset(bytes_r + i * wbytes, 0,
-					    (height - i) * wbytes);
+				if (strcmp(ln, "ENDCHAR") == 0)
 					break;
+				if (strlen(ln) < bbwbytes * 2)
+					errx(1, "broken bitmap at line %u",
+					    linenum);
+				memset(line, 0, wbytes * 2);
+				for (j = 0; j < bbwbytes; j++) {
+					unsigned int val;
+					if (sscanf(ln + j * 2, "%2x", &val) ==
+					    0)
+						break;
+					*(line + j) = (uint8_t)val;
 				}
-				sscanf(ln, "%x", &line);
-				if (parse_bitmap_line(bytes + i * wbytes,
-				     bytes_r + i * wbytes, line, dwidth) != 0)
-					return (1);
+
+				rv = rshift_row(line, wbytes * 2, bbw,
+				    bbox - fbbox);
+				if (rv != 0)
+					goto out;
+
+				rv = split_row(bytes + i * wbytes,
+				     bytes_r + i * wbytes, line, dwidth);
+				if (rv != 0)
+					goto out;
 			}
 
-			if (add_char(curchar, map_idx, bytes,
-			    dwidth == width * 2 ? bytes_r : NULL) != 0)
-				return (1);
+			rv = add_char(curchar, map_idx, bytes,
+			    dwidth > (int)width ? bytes_r : NULL);
+			if (rv != 0)
+				goto out;
+
+			dwidth = bbw = bbh = 0;
 		}
 	}
 
-	return (0);
+out:
+	free(bytes);
+	free(bytes_r);
+	free(line);
+	return (rv);
 }
 
-static void
-set_width(int w)
-{
-
-	if (w <= 0 || w > 128)
-		errx(1, "invalid width %d", w);
-	width = w;
-	wbytes = howmany(width, 8);
-}
-
 static int
 parse_hex(FILE *fp, unsigned int map_idx)
 {
 	char *ln, *p;
-	char fmt_str[8];
 	size_t length;
-	uint8_t *bytes = NULL, *bytes_r = NULL;
-	unsigned curchar = 0, i, line, chars_per_row, dwidth;
+	uint8_t *bytes = NULL, *bytes_r = NULL, *line = NULL;
+	unsigned curchar = 0, gwidth, gwbytes, i, j, chars_per_row;
 	int rv = 0;
 
 	while ((ln = fgetln(fp, &length)) != NULL) {
@@ -322,7 +468,7 @@ parse_hex(FILE *fp, unsigned int map_idx)
 		if (strncmp(ln, "# Height: ", 10) == 0) {
 			if (bytes != NULL)
 				errx(1, "malformed input: Height tag after font data");
-			height = atoi(ln + 10);
+			set_height(atoi(ln + 10));
 		} else if (strncmp(ln, "# Width: ", 9) == 0) {
 			if (bytes != NULL)
 				errx(1, "malformed input: Width tag after font data");
@@ -331,36 +477,46 @@ parse_hex(FILE *fp, unsigned int map_idx)
 			if (bytes == NULL) {
 				bytes = xmalloc(wbytes * height);
 				bytes_r = xmalloc(wbytes * height);
+				line = xmalloc(wbytes * 2);
 			}
 			/* ln is guaranteed to have a colon here. */
 			p = strchr(ln, ':') + 1;
 			chars_per_row = strlen(p) / height;
-			dwidth = width;
-			if (chars_per_row / 2 > (width + 7) / 8)
-				dwidth *= 2; /* Double-width character. */
-			snprintf(fmt_str, sizeof(fmt_str), "%%%ux",
-			    chars_per_row);
+			if (chars_per_row < wbytes * 2)
+				errx(1,
+				    "malformed input: broken bitmap, character %06x",
+				    curchar);
+			gwidth = width * 2;
+			gwbytes = howmany(gwidth, 8);
+			if (chars_per_row < gwbytes * 2 || gwidth <= 8) {
+				gwidth = width; /* Single-width character. */
+				gwbytes = wbytes;
+			}
 
 			for (i = 0; i < height; i++) {
-				sscanf(p, fmt_str, &line);
-				p += chars_per_row;
-				if (parse_bitmap_line(bytes + i * wbytes,
-				    bytes_r + i * wbytes, line, dwidth) != 0) {
-					rv = 1;
-					goto out;
+				for (j = 0; j < gwbytes; j++) {
+					unsigned int val;
+					if (sscanf(p + j * 2, "%2x", &val) == 0)
+						break;
+					*(line + j) = (uint8_t)val;
 				}
+				rv = split_row(bytes + i * wbytes,
+				    bytes_r + i * wbytes, line, gwidth);
+				if (rv != 0)
+					goto out;
+				p += gwbytes * 2;
 			}
 
-			if (add_char(curchar, map_idx, bytes,
-			    dwidth == width * 2 ? bytes_r : NULL) != 0) {
-				rv = 1;
+			rv = add_char(curchar, map_idx, bytes,
+			    gwidth != width ? bytes_r : NULL);
+			if (rv != 0)
 				goto out;
-			}
 		}
 	}
 out:
 	free(bytes);
 	free(bytes_r);
+	free(line);
 	return (rv);
 }
 
@@ -512,6 +668,8 @@ print_font_info(void)
 {
 	printf(
 "Statistics:\n"
+"- width:                       %6u\n"
+"- height:                      %6u\n"
 "- glyph_total:                 %6u\n"
 "- glyph_normal:                %6u\n"
 "- glyph_normal_right:          %6u\n"
@@ -530,6 +688,7 @@ print_font_info(void)
 "- mapping_bold_right_folded:   %6u\n"
 "- mapping_unique:              %6u\n"
 "- mapping_dupe:                %6u\n",
+	    width, height,
 	    glyph_total,
 	    glyph_count[0],
 	    glyph_count[1],
@@ -547,7 +706,7 @@ print_font_info(void)
 int
 main(int argc, char *argv[])
 {
-	int ch, val, verbose = 0;
+	int ch, verbose = 0;
 
 	assert(sizeof(struct file_header) == 32);
 	assert(sizeof(struct file_mapping) == 8);
@@ -555,16 +714,13 @@ main(int argc, char *argv[])
 	while ((ch = getopt(argc, argv, "h:vw:")) != -1) {
 		switch (ch) {
 		case 'h':
-			val = atoi(optarg);
-			if (val <= 0 || val > 128)
-				errx(1, "Invalid height %d", val);
-			height = val;
+			height = atoi(optarg);
 			break;
 		case 'v':
 			verbose = 1;
 			break;
 		case 'w':
-			set_width(atoi(optarg));
+			width = atoi(optarg);
 			break;
 		case '?':
 		default:
@@ -577,7 +733,8 @@ main(int argc, char *argv[])
 	if (argc < 2 || argc > 3)
 		usage();
 
-	wbytes = howmany(width, 8);
+	set_width(width);
+	set_height(height);
 
 	if (parse_file(argv[0], VFNT_MAP_NORMAL) != 0)
 		return (1);


More information about the svn-src-all mailing list