Prevent an invalid memory writes in compileRule

Thanks to Han Zheng for reporting it

Fixes #1214

compileRule(FileInfo *file, TranslationTableHeader **table,
		DisplayTableHeader **displayTable, const MacroList **inScopeMacros) {
	CharsString token;
	TranslationTableOpcode opcode;
	CharsString ruleChars;
	CharsString ruleDots;
	CharsString cells;
	CharsString scratchPad;
	CharsString emphClass;
	TranslationTableCharacterAttributes after = 0;
	TranslationTableCharacterAttributes before = 0;
	int noback, nofor, nocross;
	noback = nofor = nocross = 0;
doOpcode:
	if (!getToken(file, &token, NULL)) return 1;				  /* blank line */
	if (token.chars[0] == '#' || token.chars[0] == '<') return 1; /* comment */
	if (file->lineNumber == 1 &&
			(eqasc2uni((unsigned char *)"ISO", token.chars, 3) ||
					eqasc2uni((unsigned char *)"UTF-8", token.chars, 5))) {
		if (table)
			compileHyphenation(file, &token, table);
		else
			/* ignore the whole file */
			while (_lou_getALine(file))
				;
		return 1;
	}
	opcode = getOpcode(file, &token);
	switch (opcode) {
	case CTO_Macro: {
		const Macro *macro;
#ifdef ENABLE_MACROS
		if (!inScopeMacros) {
			compileError(file, "Defining macros only allowed in table files.");
			return 0;
		}
		if (compileMacro(file, &macro)) {
			*inScopeMacros = cons_macro(macro, *inScopeMacros);
			return 1;
		}
		return 0;
#else
		compileError(file, "Macro feature is disabled.");
		return 0;
#endif
	}
	case CTO_IncludeFile: {
		CharsString includedFile;
		if (!getToken(file, &token, "include file name")) return 0;
		if (!parseChars(file, &includedFile, &token)) return 0;
		return includeFile(file, &includedFile, table, displayTable);
	}
	case CTO_NoBack:
		if (nofor) {
			compileError(file, "%s already specified.", _lou_findOpcodeName(CTO_NoFor));
			return 0;
		}
		noback = 1;
		goto doOpcode;
	case CTO_NoFor:
		if (noback) {
			compileError(file, "%s already specified.", _lou_findOpcodeName(CTO_NoBack));
			return 0;
		}
		nofor = 1;
		goto doOpcode;
	case CTO_Space:
		return compileCharDef(
				file, opcode, CTC_Space, noback, nofor, table, displayTable);
	case CTO_Digit:
		return compileCharDef(
				file, opcode, CTC_Digit, noback, nofor, table, displayTable);
	case CTO_LitDigit:
		return compileCharDef(
				file, opcode, CTC_LitDigit, noback, nofor, table, displayTable);
	case CTO_Punctuation:
		return compileCharDef(
				file, opcode, CTC_Punctuation, noback, nofor, table, displayTable);
	case CTO_Math:
		return compileCharDef(file, opcode, CTC_Math, noback, nofor, table, displayTable);
	case CTO_Sign:
		return compileCharDef(file, opcode, CTC_Sign, noback, nofor, table, displayTable);
	case CTO_Letter:
		return compileCharDef(
				file, opcode, CTC_Letter, noback, nofor, table, displayTable);
	case CTO_UpperCase:
		return compileCharDef(
				file, opcode, CTC_UpperCase, noback, nofor, table, displayTable);
	case CTO_LowerCase:
		return compileCharDef(
				file, opcode, CTC_LowerCase, noback, nofor, table, displayTable);
	case CTO_Grouping:
		return compileGrouping(file, noback, nofor, table, displayTable);
	case CTO_Display:
		if (!displayTable) return 1;  // ignore
		if (!getRuleCharsText(file, &ruleChars)) return 0;
		if (!getRuleDotsPattern(file, &ruleDots)) return 0;
		if (ruleChars.length != 1 || ruleDots.length != 1) {
			compileError(file, "Exactly one character and one cell are required.");
			return 0;
		}
		return putCharDotsMapping(
				file, ruleChars.chars[0], ruleDots.chars[0], displayTable);
	case CTO_UpLow:
	case CTO_None: {
		// check if token is a macro name
		if (inScopeMacros) {
			const MacroList *macros = *inScopeMacros;
			while (macros) {
				const Macro *m = macros->head;
				if (token.length == strlen(m->name) &&
						eqasc2uni((unsigned char *)m->name, token.chars, token.length)) {
					if (!inScopeMacros) {
						compileError(file, "Calling macros only allowed in table files.");
						return 0;
					}
					FileInfo tmpFile;
					memset(&tmpFile, 0, sizeof(tmpFile));
					tmpFile.fileName = file->fileName;
					tmpFile.sourceFile = file->sourceFile;
					tmpFile.lineNumber = file->lineNumber;
					tmpFile.encoding = noEncoding;
					tmpFile.status = 0;
					tmpFile.linepos = 0;
					tmpFile.linelen = 0;
					int argument_count = 0;
					CharsString *arguments =
							malloc(m->argument_count * sizeof(CharsString));
					while (argument_count < m->argument_count) {
						if (getToken(file, &token, "macro argument"))
							arguments[argument_count++] = token;
						else
							break;
					}
					if (argument_count < m->argument_count) {
						compileError(file, "Expected %d arguments", m->argument_count);
						return 0;
					}
					int i = 0;
					int subst = 0;
					int next = subst < m->substitution_count ? m->substitutions[2 * subst]
															 : m->definition_length;
					for (;;) {
						while (i < next) {
							widechar c = m->definition[i++];
							if (c == '\n') {
								if (!compileRule(&tmpFile, table, displayTable,
											inScopeMacros)) {
									_lou_logMessage(LOU_LOG_ERROR,
											"result of macro expansion was: %s",
											_lou_showString(
													tmpFile.line, tmpFile.linelen, 0));
									return 0;
								}
								tmpFile.linepos = 0;
								tmpFile.linelen = 0;
							} else if (tmpFile.linelen >= MAXSTRING) {
								compileError(file,
										"Line exceeds %d characters (post macro "
										"expansion)",
										MAXSTRING);
								return 0;
							} else
								tmpFile.line[tmpFile.linelen++] = c;
						}
						if (subst < m->substitution_count) {
							CharsString arg =
									arguments[m->substitutions[2 * subst + 1] - 1];
							for (int j = 0; j < arg.length; j++)
								tmpFile.line[tmpFile.linelen++] = arg.chars[j];
							subst++;
							next = subst < m->substitution_count
									? m->substitutions[2 * subst]
									: m->definition_length;
						} else {
							if (!compileRule(
										&tmpFile, table, displayTable, inScopeMacros)) {
								_lou_logMessage(LOU_LOG_ERROR,
										"result of macro expansion was: %s",
										_lou_showString(
												tmpFile.line, tmpFile.linelen, 0));
								return 0;
							}
							break;
						}
					}
					return 1;
				}
				macros = macros->tail;
			}
		}
		if (opcode == CTO_UpLow) {
			compileError(file, "The uplow opcode is deprecated.");
			return 0;
		}
		compileError(file, "opcode %s not defined.",
				_lou_showString(token.chars, token.length, 0));
		return 0;
	}

	/* now only opcodes follow that don't modify the display table */
	default:
		if (!table) return 1;
		switch (opcode) {
		case CTO_Locale:
			compileWarning(file,
					"The locale opcode is not implemented. Use the locale meta data "
					"instead.");
			return 1;
		case CTO_Undefined: {
			// not passing pointer because compileBrailleIndicator may reallocate table
			TranslationTableOffset ruleOffset = (*table)->undefined;
			if (!compileBrailleIndicator(file, "undefined character opcode",
						CTO_Undefined, &ruleOffset, noback, nofor, table))
				return 0;
			(*table)->undefined = ruleOffset;
			return 1;
		}
		case CTO_Match: {
			int ok = 0;
			widechar *patterns = NULL;
			TranslationTableRule *rule;
			TranslationTableOffset ruleOffset;
			CharsString ptn_before, ptn_after;
			TranslationTableOffset patternsOffset;
			int len, mrk;
			size_t patternsByteSize = sizeof(*patterns) * 27720;
			patterns = (widechar *)malloc(patternsByteSize);
			if (!patterns) _lou_outOfMemory();
			memset(patterns, 0xffff, patternsByteSize);
			noback = 1;
			getCharacters(file, &ptn_before);
			getRuleCharsText(file, &ruleChars);
			getCharacters(file, &ptn_after);
			getRuleDotsPattern(file, &ruleDots);
			if (!addRule(file, opcode, &ruleChars, &ruleDots, after, before, &ruleOffset,
						&rule, noback, nofor, table))
				goto CTO_Match_cleanup;
			if (ptn_before.chars[0] == '-' && ptn_before.length == 1)
				len = _lou_pattern_compile(
						&ptn_before.chars[0], 0, &patterns[1], 13841, *table, file);
			else
				len = _lou_pattern_compile(&ptn_before.chars[0], ptn_before.length,
						&patterns[1], 13841, *table, file);
			if (!len) goto CTO_Match_cleanup;
			mrk = patterns[0] = len + 1;
			_lou_pattern_reverse(&patterns[1]);
			if (ptn_after.chars[0] == '-' && ptn_after.length == 1)
				len = _lou_pattern_compile(
						&ptn_after.chars[0], 0, &patterns[mrk], 13841, *table, file);
			else
				len = _lou_pattern_compile(&ptn_after.chars[0], ptn_after.length,
						&patterns[mrk], 13841, *table, file);
			if (!len) goto CTO_Match_cleanup;
			len += mrk;
			if (!allocateSpaceInTranslationTable(
						file, &patternsOffset, len * sizeof(widechar), table))
				goto CTO_Match_cleanup;
			// allocateSpaceInTranslationTable may have moved table, so make sure rule is
			// still valid
			rule = (TranslationTableRule *)&(*table)->ruleArea[ruleOffset];
			memcpy(&(*table)->ruleArea[patternsOffset], patterns, len * sizeof(widechar));
			rule->patterns = patternsOffset;
			ok = 1;
		CTO_Match_cleanup:
			free(patterns);
			return ok;
		}

		case CTO_BackMatch: {
			int ok = 0;
			widechar *patterns = NULL;
			TranslationTableRule *rule;
			TranslationTableOffset ruleOffset;
			CharsString ptn_before, ptn_after;
			TranslationTableOffset patternOffset;
			int len, mrk;
			size_t patternsByteSize = sizeof(*patterns) * 27720;
			patterns = (widechar *)malloc(patternsByteSize);
			if (!patterns) _lou_outOfMemory();
			memset(patterns, 0xffff, patternsByteSize);
			nofor = 1;
			getCharacters(file, &ptn_before);
			getRuleCharsText(file, &ruleChars);
			getCharacters(file, &ptn_after);
			getRuleDotsPattern(file, &ruleDots);
			if (!addRule(file, opcode, &ruleChars, &ruleDots, 0, 0, &ruleOffset, &rule,
						noback, nofor, table))
				goto CTO_BackMatch_cleanup;
			if (ptn_before.chars[0] == '-' && ptn_before.length == 1)
				len = _lou_pattern_compile(
						&ptn_before.chars[0], 0, &patterns[1], 13841, *table, file);
			else
				len = _lou_pattern_compile(&ptn_before.chars[0], ptn_before.length,
						&patterns[1], 13841, *table, file);
			if (!len) goto CTO_BackMatch_cleanup;
			mrk = patterns[0] = len + 1;
			_lou_pattern_reverse(&patterns[1]);
			if (ptn_after.chars[0] == '-' && ptn_after.length == 1)
				len = _lou_pattern_compile(
						&ptn_after.chars[0], 0, &patterns[mrk], 13841, *table, file);
			else
				len = _lou_pattern_compile(&ptn_after.chars[0], ptn_after.length,
						&patterns[mrk], 13841, *table, file);
			if (!len) goto CTO_BackMatch_cleanup;
			len += mrk;
			if (!allocateSpaceInTranslationTable(
						file, &patternOffset, len * sizeof(widechar), table))
				goto CTO_BackMatch_cleanup;
			// allocateSpaceInTranslationTable may have moved table, so make sure rule is
			// still valid
			rule = (TranslationTableRule *)&(*table)->ruleArea[ruleOffset];
			memcpy(&(*table)->ruleArea[patternOffset], patterns, len * sizeof(widechar));
			rule->patterns = patternOffset;
			ok = 1;
		CTO_BackMatch_cleanup:
			free(patterns);
			return ok;
		}

		case CTO_CapsLetter:
		case CTO_BegCapsWord:
		case CTO_EndCapsWord:
		case CTO_BegCaps:
		case CTO_EndCaps:
		case CTO_BegCapsPhrase:
		case CTO_EndCapsPhrase:
		case CTO_LenCapsPhrase:
		/* these 8 general purpose opcodes are compiled further down to more specific
		 * internal opcodes:
		 * - modeletter
		 * - begmodeword
		 * - endmodeword
		 * - begmode
		 * - endmode
		 * - begmodephrase
		 * - endmodephrase
		 * - lenmodephrase
		 */
		case CTO_ModeLetter:
		case CTO_BegModeWord:
		case CTO_EndModeWord:
		case CTO_BegMode:
		case CTO_EndMode:
		case CTO_BegModePhrase:
		case CTO_EndModePhrase:
		case CTO_LenModePhrase: {
			TranslationTableCharacterAttributes mode;
			int i;
			switch (opcode) {
			case CTO_CapsLetter:
			case CTO_BegCapsWord:
			case CTO_EndCapsWord:
			case CTO_BegCaps:
			case CTO_EndCaps:
			case CTO_BegCapsPhrase:
			case CTO_EndCapsPhrase:
			case CTO_LenCapsPhrase:
				mode = CTC_UpperCase;
				i = 0;
				opcode += (CTO_ModeLetter - CTO_CapsLetter);
				break;
			default:
				if (!getToken(file, &token, "attribute name")) return 0;
				if (!(*table)->characterClasses && !allocateCharacterClasses(*table)) {
					return 0;
				}
				const CharacterClass *characterClass = findCharacterClass(&token, *table);
				if (!characterClass) {
					characterClass =
							addCharacterClass(file, token.chars, token.length, *table, 1);
					if (!characterClass) return 0;
				}
				mode = characterClass->attribute;
				if (!(mode == CTC_UpperCase || mode == CTC_Digit) && mode >= CTC_Space &&
						mode <= CTC_LitDigit) {
					compileError(file,
							"mode must be \"uppercase\", \"digit\", or a custom "
							"attribute name.");
					return 0;
				}
				/* check if this mode is already defined and if the number of modes does
				 * not exceed the maximal number */
				if (mode == CTC_UpperCase)
					i = 0;
				else {
					for (i = 1; i < MAX_MODES && (*table)->modes[i].value; i++) {
						if ((*table)->modes[i].mode == mode) {
							break;
						}
					}
					if (i == MAX_MODES) {
						compileError(file, "Max number of modes (%i) reached", MAX_MODES);
						return 0;
					}
				}
			}
			if (!(*table)->modes[i].value)
				(*table)->modes[i] = (EmphasisClass){ plain_text, mode,
					0x1 << (MAX_EMPH_CLASSES + i), MAX_EMPH_CLASSES + i };
			switch (opcode) {
			case CTO_BegModePhrase: {
				// not passing pointer because compileBrailleIndicator may reallocate
				// table
				TranslationTableOffset ruleOffset =
						(*table)->emphRules[MAX_EMPH_CLASSES + i][begPhraseOffset];
				if (!compileBrailleIndicator(file, "first word capital sign",
							CTO_BegCapsPhraseRule + (8 * i), &ruleOffset, noback, nofor,
							table))
					return 0;
				(*table)->emphRules[MAX_EMPH_CLASSES + i][begPhraseOffset] = ruleOffset;
				return 1;
			}
			case CTO_EndModePhrase: {
				TranslationTableOffset ruleOffset;
				switch (compileBeforeAfter(file)) {
				case 1:	 // before
					if ((*table)->emphRules[MAX_EMPH_CLASSES + i][endPhraseAfterOffset]) {
						compileError(
								file, "Capital sign after last word already defined.");
						return 0;
					}
					// not passing pointer because compileBrailleIndicator may reallocate
					// table
					ruleOffset = (*table)->emphRules[MAX_EMPH_CLASSES + i]
													[endPhraseBeforeOffset];
					if (!compileBrailleIndicator(file, "capital sign before last word",
								CTO_EndCapsPhraseBeforeRule + (8 * i), &ruleOffset,
								noback, nofor, table))
						return 0;
					(*table)->emphRules[MAX_EMPH_CLASSES + i][endPhraseBeforeOffset] =
							ruleOffset;
					return 1;
				case 2:	 // after
					if ((*table)->emphRules[MAX_EMPH_CLASSES + i]
										   [endPhraseBeforeOffset]) {
						compileError(
								file, "Capital sign before last word already defined.");
						return 0;
					}
					// not passing pointer because compileBrailleIndicator may reallocate
					// table
					ruleOffset = (*table)->emphRules[MAX_EMPH_CLASSES + i]
													[endPhraseAfterOffset];
					if (!compileBrailleIndicator(file, "capital sign after last word",
								CTO_EndCapsPhraseAfterRule + (8 * i), &ruleOffset, noback,
								nofor, table))
						return 0;
					(*table)->emphRules[MAX_EMPH_CLASSES + i][endPhraseAfterOffset] =
							ruleOffset;
					return 1;
				default:  // error
					compileError(file, "Invalid lastword indicator location.");
					return 0;
				}
				return 0;
			}
			case CTO_BegMode: {
				// not passing pointer because compileBrailleIndicator may reallocate
				// table
				TranslationTableOffset ruleOffset =
						(*table)->emphRules[MAX_EMPH_CLASSES + i][begOffset];
				if (!compileBrailleIndicator(file, "first letter capital sign",
							CTO_BegCapsRule + (8 * i), &ruleOffset, noback, nofor, table))
					return 0;
				(*table)->emphRules[MAX_EMPH_CLASSES + i][begOffset] = ruleOffset;
				return 1;
			}
			case CTO_EndMode: {
				// not passing pointer because compileBrailleIndicator may reallocate
				// table
				TranslationTableOffset ruleOffset =
						(*table)->emphRules[MAX_EMPH_CLASSES + i][endOffset];
				if (!compileBrailleIndicator(file, "last letter capital sign",
							CTO_EndCapsRule + (8 * i), &ruleOffset, noback, nofor, table))
					return 0;
				(*table)->emphRules[MAX_EMPH_CLASSES + i][endOffset] = ruleOffset;
				return 1;
			}
			case CTO_ModeLetter: {
				// not passing pointer because compileBrailleIndicator may reallocate
				// table
				TranslationTableOffset ruleOffset =
						(*table)->emphRules[MAX_EMPH_CLASSES + i][letterOffset];
				if (!compileBrailleIndicator(file, "single letter capital sign",
							CTO_CapsLetterRule + (8 * i), &ruleOffset, noback, nofor,
							table))
					return 0;
				(*table)->emphRules[MAX_EMPH_CLASSES + i][letterOffset] = ruleOffset;
				return 1;
			}
			case CTO_BegModeWord: {
				// not passing pointer because compileBrailleIndicator may reallocate
				// table
				TranslationTableOffset ruleOffset =
						(*table)->emphRules[MAX_EMPH_CLASSES + i][begWordOffset];
				if (!compileBrailleIndicator(file, "capital word",
							CTO_BegCapsWordRule + (8 * i), &ruleOffset, noback, nofor,
							table))
					return 0;
				(*table)->emphRules[MAX_EMPH_CLASSES + i][begWordOffset] = ruleOffset;
				return 1;
			}
			case CTO_EndModeWord: {
				// not passing pointer because compileBrailleIndicator may reallocate
				// table
				TranslationTableOffset ruleOffset =
						(*table)->emphRules[MAX_EMPH_CLASSES + i][endWordOffset];
				if (!compileBrailleIndicator(file, "capital word stop",
							CTO_EndCapsWordRule + (8 * i), &ruleOffset, noback, nofor,
							table))
					return 0;
				(*table)->emphRules[MAX_EMPH_CLASSES + i][endWordOffset] = ruleOffset;
				return 1;
			}
			case CTO_LenModePhrase:
				return (*table)->emphRules[MAX_EMPH_CLASSES + i][lenPhraseOffset] =
							   compileNumber(file);
			default:
				break;
			}
			break;
		}

		/* these 8 general purpose emphasis opcodes are compiled further down to more
		 * specific internal opcodes:
		 * - emphletter
		 * - begemphword
		 * - endemphword
		 * - begemph
		 * - endemph
		 * - begemphphrase
		 * - endemphphrase
		 * - lenemphphrase
		 */
		case CTO_EmphClass:
			if (!getToken(file, &emphClass, "emphasis class")) {
				compileError(file, "emphclass must be followed by a valid class name.");
				return 0;
			}
			int k, i;
			char *s = malloc(sizeof(char) * (emphClass.length + 1));
			for (k = 0; k < emphClass.length; k++) s[k] = (char)emphClass.chars[k];
			s[k++] = '\0';
			for (i = 0; i < MAX_EMPH_CLASSES && (*table)->emphClassNames[i]; i++)
				if (strcmp(s, (*table)->emphClassNames[i]) == 0) {
					_lou_logMessage(LOU_LOG_WARN, "Duplicate emphasis class: %s", s);
					warningCount++;
					free(s);
					return 1;
				}
			if (i == MAX_EMPH_CLASSES) {
				_lou_logMessage(LOU_LOG_ERROR,
						"Max number of emphasis classes (%i) reached", MAX_EMPH_CLASSES);
				errorCount++;
				free(s);
				return 0;
			}
			switch (i) {
			/* For backwards compatibility (i.e. because programs will assume
			 * the first 3 typeform bits are `italic', `underline' and `bold')
			 * we require that the first 3 emphclass definitions are (in that
			 * order):
			 *
			 *   emphclass italic
			 *   emphclass underline
			 *   emphclass bold
			 *
			 * While it would be possible to use the emphclass opcode only for
			 * defining _additional_ classes (not allowing for them to be called
			 * italic, underline or bold), thereby reducing the amount of
			 * boilerplate, we deliberately choose not to do that in order to
			 * not give italic, underline and bold any special status. The
			 * hope is that eventually all programs will use liblouis for
			 * emphasis the recommended way (i.e. by looking up the supported
			 * typeforms in the documentation or API) so that we can drop this
			 * restriction.
			 */
			case 0:
				if (strcmp(s, "italic") != 0) {
					_lou_logMessage(LOU_LOG_ERROR,
							"First emphasis class must be \"italic\" but got "
							"%s",
							s);
					errorCount++;
					free(s);
					return 0;
				}
				break;
			case 1:
				if (strcmp(s, "underline") != 0) {
					_lou_logMessage(LOU_LOG_ERROR,
							"Second emphasis class must be \"underline\" but "
							"got "
							"%s",
							s);
					errorCount++;
					free(s);
					return 0;
				}
				break;
			case 2:
				if (strcmp(s, "bold") != 0) {
					_lou_logMessage(LOU_LOG_ERROR,
							"Third emphasis class must be \"bold\" but got "
							"%s",
							s);
					errorCount++;
					free(s);
					return 0;
				}
				break;
			}
			(*table)->emphClassNames[i] = s;
			(*table)->emphClasses[i] = (EmphasisClass){ emph_1
						<< i, /* relies on the order of typeforms emph_1..emph_10 */
				0, 0x1 << i, i };
			return 1;
		case CTO_EmphLetter:
		case CTO_BegEmphWord:
		case CTO_EndEmphWord:
		case CTO_BegEmph:
		case CTO_EndEmph:
		case CTO_BegEmphPhrase:
		case CTO_EndEmphPhrase:
		case CTO_LenEmphPhrase:
		case CTO_EmphModeChars:
		case CTO_NoEmphChars: {
			if (!getToken(file, &token, "emphasis class")) return 0;
			if (!parseChars(file, &emphClass, &token)) return 0;
			char *s = malloc(sizeof(char) * (emphClass.length + 1));
			int k, i;
			for (k = 0; k < emphClass.length; k++) s[k] = (char)emphClass.chars[k];
			s[k++] = '\0';
			for (i = 0; i < MAX_EMPH_CLASSES && (*table)->emphClassNames[i]; i++)
				if (strcmp(s, (*table)->emphClassNames[i]) == 0) break;
			if (i == MAX_EMPH_CLASSES || !(*table)->emphClassNames[i]) {
				_lou_logMessage(LOU_LOG_ERROR, "Emphasis class %s not declared", s);
				errorCount++;
				free(s);
				return 0;
			}
			int ok = 0;
			switch (opcode) {
			case CTO_EmphLetter: {
				// not passing pointer because compileBrailleIndicator may reallocate
				// table
				TranslationTableOffset ruleOffset = (*table)->emphRules[i][letterOffset];
				if (!compileBrailleIndicator(file, "single letter",
							CTO_Emph1LetterRule + letterOffset + (8 * i), &ruleOffset,
							noback, nofor, table))
					break;
				(*table)->emphRules[i][letterOffset] = ruleOffset;
				ok = 1;
				break;
			}
			case CTO_BegEmphWord: {
				// not passing pointer because compileBrailleIndicator may reallocate
				// table
				TranslationTableOffset ruleOffset = (*table)->emphRules[i][begWordOffset];
				if (!compileBrailleIndicator(file, "word",
							CTO_Emph1LetterRule + begWordOffset + (8 * i), &ruleOffset,
							noback, nofor, table))
					break;
				(*table)->emphRules[i][begWordOffset] = ruleOffset;
				ok = 1;
				break;
			}
			case CTO_EndEmphWord: {
				// not passing pointer because compileBrailleIndicator may reallocate
				// table
				TranslationTableOffset ruleOffset = (*table)->emphRules[i][endWordOffset];
				if (!compileBrailleIndicator(file, "word stop",
							CTO_Emph1LetterRule + endWordOffset + (8 * i), &ruleOffset,
							noback, nofor, table))
					break;
				(*table)->emphRules[i][endWordOffset] = ruleOffset;
				ok = 1;
				break;
			}
			case CTO_BegEmph: {
				/* fail if both begemph and any of begemphphrase or begemphword are
				 * defined */
				if ((*table)->emphRules[i][begWordOffset] ||
						(*table)->emphRules[i][begPhraseOffset]) {
					compileError(file,
							"Cannot define emphasis for both no context and word or "
							"phrase context, i.e. cannot have both begemph and "
							"begemphword or begemphphrase.");
					break;
				}
				// not passing pointer because compileBrailleIndicator may reallocate
				// table
				TranslationTableOffset ruleOffset = (*table)->emphRules[i][begOffset];
				if (!compileBrailleIndicator(file, "first letter",
							CTO_Emph1LetterRule + begOffset + (8 * i), &ruleOffset,
							noback, nofor, table))
					break;
				(*table)->emphRules[i][begOffset] = ruleOffset;
				ok = 1;
				break;
			}
			case CTO_EndEmph: {
				if ((*table)->emphRules[i][endWordOffset] ||
						(*table)->emphRules[i][endPhraseBeforeOffset] ||
						(*table)->emphRules[i][endPhraseAfterOffset]) {
					compileError(file,
							"Cannot define emphasis for both no context and word or "
							"phrase context, i.e. cannot have both endemph and "
							"endemphword or endemphphrase.");
					break;
				}
				// not passing pointer because compileBrailleIndicator may reallocate
				// table
				TranslationTableOffset ruleOffset = (*table)->emphRules[i][endOffset];
				if (!compileBrailleIndicator(file, "last letter",
							CTO_Emph1LetterRule + endOffset + (8 * i), &ruleOffset,
							noback, nofor, table))
					break;
				(*table)->emphRules[i][endOffset] = ruleOffset;
				ok = 1;
				break;
			}
			case CTO_BegEmphPhrase: {
				// not passing pointer because compileBrailleIndicator may reallocate
				// table
				TranslationTableOffset ruleOffset =
						(*table)->emphRules[i][begPhraseOffset];
				if (!compileBrailleIndicator(file, "first word",
							CTO_Emph1LetterRule + begPhraseOffset + (8 * i), &ruleOffset,
							noback, nofor, table))
					break;
				(*table)->emphRules[i][begPhraseOffset] = ruleOffset;
				ok = 1;
				break;
			}
			case CTO_EndEmphPhrase:
				switch (compileBeforeAfter(file)) {
				case 1: {  // before
					if ((*table)->emphRules[i][endPhraseAfterOffset]) {
						compileError(file, "last word after already defined.");
						break;
					}
					// not passing pointer because compileBrailleIndicator may reallocate
					// table
					TranslationTableOffset ruleOffset =
							(*table)->emphRules[i][endPhraseBeforeOffset];
					if (!compileBrailleIndicator(file, "last word before",
								CTO_Emph1LetterRule + endPhraseBeforeOffset + (8 * i),
								&ruleOffset, noback, nofor, table))
						break;
					(*table)->emphRules[i][endPhraseBeforeOffset] = ruleOffset;
					ok = 1;
					break;
				}
				case 2: {  // after
					if ((*table)->emphRules[i][endPhraseBeforeOffset]) {
						compileError(file, "last word before already defined.");
						break;
					}
					// not passing pointer because compileBrailleIndicator may reallocate
					// table
					TranslationTableOffset ruleOffset =
							(*table)->emphRules[i][endPhraseAfterOffset];
					if (!compileBrailleIndicator(file, "last word after",
								CTO_Emph1LetterRule + endPhraseAfterOffset + (8 * i),
								&ruleOffset, noback, nofor, table))
						break;
					(*table)->emphRules[i][endPhraseAfterOffset] = ruleOffset;
					ok = 1;
					break;
				}
				default:  // error
					compileError(file, "Invalid lastword indicator location.");
					break;
				}
				break;
			case CTO_LenEmphPhrase:
				if (((*table)->emphRules[i][lenPhraseOffset] = compileNumber(file)))
					ok = 1;
				break;
			case CTO_EmphModeChars: {
				if (!getRuleCharsText(file, &ruleChars)) break;
				widechar *emphmodechars = (*table)->emphModeChars[i];
				int len;
				for (len = 0; len < EMPHMODECHARSSIZE && emphmodechars[len]; len++)
					;
				if (len + ruleChars.length > EMPHMODECHARSSIZE) {
					compileError(file, "More than %d characters", EMPHMODECHARSSIZE);
					break;
				}
				ok = 1;
				for (int k = 0; k < ruleChars.length; k++) {
					if (!getChar(ruleChars.chars[k], *table, NULL)) {
						compileError(file, "Emphasis mode character undefined");
						ok = 0;
						break;
					}
					emphmodechars[len++] = ruleChars.chars[k];
				}
				break;
			}
			case CTO_NoEmphChars: {
				if (!getRuleCharsText(file, &ruleChars)) break;
				widechar *noemphchars = (*table)->noEmphChars[i];
				int len;
				for (len = 0; len < NOEMPHCHARSSIZE && noemphchars[len]; len++)
					;
				if (len + ruleChars.length > NOEMPHCHARSSIZE) {
					compileError(file, "More than %d characters", NOEMPHCHARSSIZE);
					break;
				}
				ok = 1;
				for (int k = 0; k < ruleChars.length; k++) {
					if (!getChar(ruleChars.chars[k], *table, NULL)) {
						compileError(file, "Character undefined");
						ok = 0;
						break;
					}
					noemphchars[len++] = ruleChars.chars[k];
				}
				break;
			}
			default:
				break;
			}
			free(s);
			return ok;
		}
		case CTO_LetterSign: {
			// not passing pointer because compileBrailleIndicator may reallocate table
			TranslationTableOffset ruleOffset = (*table)->letterSign;
			if (!compileBrailleIndicator(file, "letter sign", CTO_LetterRule, &ruleOffset,
						noback, nofor, table))
				return 0;
			(*table)->letterSign = ruleOffset;
			return 1;
		}
		case CTO_NoLetsignBefore:
			if (!getRuleCharsText(file, &ruleChars)) return 0;
			if (((*table)->noLetsignBeforeCount + ruleChars.length) > LETSIGNBEFORESIZE) {
				compileError(file, "More than %d characters", LETSIGNBEFORESIZE);
				return 0;
			}
			for (int k = 0; k < ruleChars.length; k++)
				(*table)->noLetsignBefore[(*table)->noLetsignBeforeCount++] =
						ruleChars.chars[k];
			return 1;
		case CTO_NoLetsign:
			if (!getRuleCharsText(file, &ruleChars)) return 0;
			if (((*table)->noLetsignCount + ruleChars.length) > LETSIGNSIZE) {
				compileError(file, "More than %d characters", LETSIGNSIZE);
				return 0;
			}
			for (int k = 0; k < ruleChars.length; k++)
				(*table)->noLetsign[(*table)->noLetsignCount++] = ruleChars.chars[k];
			return 1;
		case CTO_NoLetsignAfter:
			if (!getRuleCharsText(file, &ruleChars)) return 0;
			if (((*table)->noLetsignAfterCount + ruleChars.length) > LETSIGNAFTERSIZE) {
				compileError(file, "More than %d characters", LETSIGNAFTERSIZE);
				return 0;
			}
			for (int k = 0; k < ruleChars.length; k++)
				(*table)->noLetsignAfter[(*table)->noLetsignAfterCount++] =
						ruleChars.chars[k];
			return 1;
		case CTO_NumberSign: {
			// not passing pointer because compileBrailleIndicator may reallocate table
			TranslationTableOffset ruleOffset = (*table)->numberSign;
			if (!compileBrailleIndicator(file, "number sign", CTO_NumberRule, &ruleOffset,
						noback, nofor, table))
				return 0;
			(*table)->numberSign = ruleOffset;
			return 1;
		}

		case CTO_NumericModeChars:
			if (!getRuleCharsText(file, &ruleChars)) return 0;
			for (int k = 0; k < ruleChars.length; k++) {
				TranslationTableCharacter *c = getChar(ruleChars.chars[k], *table, NULL);
				if (!c) {
					compileError(file, "Numeric mode character undefined: %s",
							_lou_showString(&ruleChars.chars[k], 1, 0));
					return 0;
				}
				c->attributes |= CTC_NumericMode;
				(*table)->usesNumericMode = 1;
			}
			return 1;

		case CTO_MidEndNumericModeChars:
			if (!getRuleCharsText(file, &ruleChars)) return 0;
			for (int k = 0; k < ruleChars.length; k++) {
				TranslationTableCharacter *c = getChar(ruleChars.chars[k], *table, NULL);
				if (!c) {
					compileError(file, "Midendnumeric mode character undefined");
					return 0;
				}
				c->attributes |= CTC_MidEndNumericMode;
				(*table)->usesNumericMode = 1;
			}
			return 1;

		case CTO_NumericNoContractChars:
			if (!getRuleCharsText(file, &ruleChars)) return 0;
			for (int k = 0; k < ruleChars.length; k++) {
				TranslationTableCharacter *c = getChar(ruleChars.chars[k], *table, NULL);
				if (!c) {
					compileError(file, "Numeric no contraction character undefined");
					return 0;
				}
				c->attributes |= CTC_NumericNoContract;
				(*table)->usesNumericMode = 1;
			}
			return 1;

		case CTO_NoContractSign: {
			// not passing pointer because compileBrailleIndicator may reallocate table
			TranslationTableOffset ruleOffset = (*table)->noContractSign;
			if (!compileBrailleIndicator(file, "no contractions sign", CTO_NoContractRule,
						&ruleOffset, noback, nofor, table))
				return 0;
			(*table)->noContractSign = ruleOffset;
			return 1;
		}
		case CTO_SeqDelimiter:
			if (!getRuleCharsText(file, &ruleChars)) return 0;
			for (int k = 0; k < ruleChars.length; k++) {
				TranslationTableCharacter *c = getChar(ruleChars.chars[k], *table, NULL);
				if (!c) {
					compileError(file, "Sequence delimiter character undefined");
					return 0;
				}
				c->attributes |= CTC_SeqDelimiter;
				(*table)->usesSequences = 1;
			}
			return 1;

		case CTO_SeqBeforeChars:
			if (!getRuleCharsText(file, &ruleChars)) return 0;
			for (int k = 0; k < ruleChars.length; k++) {
				TranslationTableCharacter *c = getChar(ruleChars.chars[k], *table, NULL);
				if (!c) {
					compileError(file, "Sequence before character undefined");
					return 0;
				}
				c->attributes |= CTC_SeqBefore;
			}
			return 1;

		case CTO_SeqAfterChars:
			if (!getRuleCharsText(file, &ruleChars)) return 0;
			for (int k = 0; k < ruleChars.length; k++) {
				TranslationTableCharacter *c = getChar(ruleChars.chars[k], *table, NULL);
				if (!c) {
					compileError(file, "Sequence after character undefined");
					return 0;
				}
				c->attributes |= CTC_SeqAfter;
			}
			return 1;

		case CTO_SeqAfterPattern:
			if (!getRuleCharsText(file, &ruleChars)) return 0;
			if (((*table)->seqPatternsCount + ruleChars.length + 1) > SEQPATTERNSIZE) {
				compileError(file, "More than %d characters", SEQPATTERNSIZE);
				return 0;
			}
			for (int k = 0; k < ruleChars.length; k++)
				(*table)->seqPatterns[(*table)->seqPatternsCount++] = ruleChars.chars[k];
			(*table)->seqPatterns[(*table)->seqPatternsCount++] = 0;
			return 1;

		case CTO_SeqAfterExpression:
			if (!getRuleCharsText(file, &ruleChars)) return 0;
			for ((*table)->seqAfterExpressionLength = 0;
					(*table)->seqAfterExpressionLength < ruleChars.length;
					(*table)->seqAfterExpressionLength++)
				(*table)->seqAfterExpression[(*table)->seqAfterExpressionLength] =
						ruleChars.chars[(*table)->seqAfterExpressionLength];
			(*table)->seqAfterExpression[(*table)->seqAfterExpressionLength] = 0;
			return 1;

		case CTO_CapsModeChars:
			if (!getRuleCharsText(file, &ruleChars)) return 0;
			for (int k = 0; k < ruleChars.length; k++) {
				TranslationTableCharacter *c = getChar(ruleChars.chars[k], *table, NULL);
				if (!c) {
					compileError(file, "Capital mode character undefined");
					return 0;
				}
				c->attributes |= CTC_CapsMode;
				(*table)->hasCapsModeChars = 1;
			}
			return 1;

		case CTO_BegComp: {
			// not passing pointer because compileBrailleIndicator may reallocate table
			TranslationTableOffset ruleOffset = (*table)->begComp;
			if (!compileBrailleIndicator(file, "begin computer braille", CTO_BegCompRule,
						&ruleOffset, noback, nofor, table))
				return 0;
			(*table)->begComp = ruleOffset;
			return 1;
		}
		case CTO_EndComp: {
			// not passing pointer because compileBrailleIndicator may reallocate table
			TranslationTableOffset ruleOffset = (*table)->endComp;
			if (!compileBrailleIndicator(file, "end computer braslle", CTO_EndCompRule,
						&ruleOffset, noback, nofor, table))
				return 0;
			(*table)->endComp = ruleOffset;
			return 1;
		}
		case CTO_NoCross:
			if (nocross) {
				compileError(
						file, "%s already specified.", _lou_findOpcodeName(CTO_NoCross));
				return 0;
			}
			nocross = 1;
			goto doOpcode;
		case CTO_Syllable:
			(*table)->syllables = 1;
		case CTO_Always:
		case CTO_LargeSign:
		case CTO_WholeWord:
		case CTO_PartWord:
		case CTO_JoinNum:
		case CTO_JoinableWord:
		case CTO_LowWord:
		case CTO_SuffixableWord:
		case CTO_PrefixableWord:
		case CTO_BegWord:
		case CTO_BegMidWord:
		case CTO_MidWord:
		case CTO_MidEndWord:
		case CTO_EndWord:
		case CTO_PrePunc:
		case CTO_PostPunc:
		case CTO_BegNum:
		case CTO_MidNum:
		case CTO_EndNum:
		case CTO_Repeated:
		case CTO_RepWord:
			if (!getRuleCharsText(file, &ruleChars)) return 0;
			if (!getRuleDotsPattern(file, &ruleDots)) return 0;
			if (ruleDots.length == 0)
				// check that all characters in a rule with `=` as second operand are
				// defined (or based on another character)
				for (int k = 0; k < ruleChars.length; k++) {
					TranslationTableCharacter *c =
							getChar(ruleChars.chars[k], *table, NULL);
					if (!(c && (c->definitionRule || c->basechar))) {
						compileError(file, "Character %s is not defined",
								_lou_showString(&ruleChars.chars[k], 1, 0));
						return 0;
					}
				}
			TranslationTableRule *r;
			if (!addRule(file, opcode, &ruleChars, &ruleDots, after, before, NULL, &r,
						noback, nofor, table))
				return 0;
			if (nocross) r->nocross = 1;
			return 1;
			// if (opcode == CTO_MidNum)
			// {
			//   TranslationTableCharacter *c = getChar(ruleChars.chars[0]);
			//   if(c)
			//     c->attributes |= CTC_NumericMode;
			// }
		case CTO_RepEndWord:
			if (!getRuleCharsText(file, &ruleChars)) return 0;
			CharsString dots;
			if (!getToken(file, &dots, "dots,dots operand")) return 0;
			int len = dots.length;
			for (int k = 0; k < len - 1; k++) {
				if (dots.chars[k] == ',') {
					dots.length = k;
					if (!parseDots(file, &ruleDots, &dots)) return 0;
					ruleDots.chars[ruleDots.length++] = ',';
					k++;
					if (k == len - 1 && dots.chars[k] == '=') {
						// check that all characters are defined (or based on another
						// character)
						for (int l = 0; l < ruleChars.length; l++) {
							TranslationTableCharacter *c =
									getChar(ruleChars.chars[l], *table, NULL);
							if (!(c && (c->definitionRule || c->basechar))) {
								compileError(file, "Character %s is not defined",
										_lou_showString(&ruleChars.chars[l], 1, 0));
								return 0;
							}
						}
					} else {
						CharsString x, y;
						x.length = 0;
						while (k < len) x.chars[x.length++] = dots.chars[k++];
						if (parseDots(file, &y, &x))
							for (int l = 0; l < y.length; l++)
								ruleDots.chars[ruleDots.length++] = y.chars[l];
					}
					return addRule(file, opcode, &ruleChars, &ruleDots, after, before,
							NULL, NULL, noback, nofor, table);
				}
			}
			return 0;
		case CTO_CompDots:
		case CTO_Comp6: {
			TranslationTableOffset ruleOffset;
			if (!getRuleCharsText(file, &ruleChars)) return 0;
			if (ruleChars.length != 1) {
				compileError(file, "first operand must be 1 character");
				return 0;
			}
			if (nofor || noback) {
				compileWarning(file, "nofor and noback not allowed on comp6 rules");
			}
			if (!getRuleDotsPattern(file, &ruleDots)) return 0;
			if (!addRule(file, opcode, &ruleChars, &ruleDots, after, before, &ruleOffset,
						NULL, noback, nofor, table))
				return 0;
			return 1;
		}
		case CTO_ExactDots:
			if (!getRuleCharsText(file, &ruleChars)) return 0;
			if (ruleChars.chars[0] != '@') {
				compileError(file, "The operand must begin with an at sign (@)");
				return 0;
			}
			for (int k = 1; k < ruleChars.length; k++)
				scratchPad.chars[k - 1] = ruleChars.chars[k];
			scratchPad.length = ruleChars.length - 1;
			if (!parseDots(file, &ruleDots, &scratchPad)) return 0;
			return addRule(file, opcode, &ruleChars, &ruleDots, before, after, NULL, NULL,
					noback, nofor, table);
		case CTO_CapsNoCont: {
			TranslationTableOffset ruleOffset;
			ruleChars.length = 1;
			ruleChars.chars[0] = 'a';
			if (!addRule(file, CTO_CapsNoContRule, &ruleChars, NULL, after, before,
						&ruleOffset, NULL, noback, nofor, table))
				return 0;
			(*table)->capsNoCont = ruleOffset;
			return 1;
		}
		case CTO_Replace:
			if (getRuleCharsText(file, &ruleChars)) {
				if (atEndOfLine(file))
					ruleDots.length = ruleDots.chars[0] = 0;
				else {
					getRuleDotsText(file, &ruleDots);
					if (ruleDots.chars[0] == '#')
						ruleDots.length = ruleDots.chars[0] = 0;
					else if (ruleDots.chars[0] == '\\' && ruleDots.chars[1] == '#')
						memmove(&ruleDots.chars[0], &ruleDots.chars[1],
								ruleDots.length-- * CHARSIZE);
				}
			}
			for (int k = 0; k < ruleChars.length; k++)
				putChar(file, ruleChars.chars[k], table, NULL);
			for (int k = 0; k < ruleDots.length; k++)
				putChar(file, ruleDots.chars[k], table, NULL);
			return addRule(file, opcode, &ruleChars, &ruleDots, after, before, NULL, NULL,
					noback, nofor, table);
		case CTO_Correct:
			(*table)->corrections = 1;
			goto doPass;
		case CTO_Pass2:
			if ((*table)->numPasses < 2) (*table)->numPasses = 2;
			goto doPass;
		case CTO_Pass3:
			if ((*table)->numPasses < 3) (*table)->numPasses = 3;
			goto doPass;
		case CTO_Pass4:
			if ((*table)->numPasses < 4) (*table)->numPasses = 4;
		doPass:
		case CTO_Context:
			if (!(nofor || noback)) {
				compileError(file, "%s or %s must be specified.",
						_lou_findOpcodeName(CTO_NoFor), _lou_findOpcodeName(CTO_NoBack));
				return 0;
			}
			return compilePassOpcode(file, opcode, noback, nofor, table);
		case CTO_Contraction:
		case CTO_NoCont:
		case CTO_CompBrl:
		case CTO_Literal:
			if (!getRuleCharsText(file, &ruleChars)) return 0;
			// check that all characters in a compbrl, contraction,
			// nocont or literal rule are defined (or based on another
			// character)
			for (int k = 0; k < ruleChars.length; k++) {
				TranslationTableCharacter *c = getChar(ruleChars.chars[k], *table, NULL);
				if (!(c && (c->definitionRule || c->basechar))) {
					compileError(file, "Character %s is not defined",
							_lou_showString(&ruleChars.chars[k], 1, 0));
					return 0;
				}
			}
			return addRule(file, opcode, &ruleChars, NULL, after, before, NULL, NULL,
					noback, nofor, table);
		case CTO_MultInd: {
			ruleChars.length = 0;
			if (!getToken(file, &token, "multiple braille indicators") ||
					!parseDots(file, &cells, &token))
				return 0;
			while (getToken(file, &token, "multind opcodes")) {
				opcode = getOpcode(file, &token);
				if (opcode == CTO_None) {
					compileError(file, "opcode %s not defined.",
							_lou_showString(token.chars, token.length, 0));
					return 0;
				}
				if (!(opcode >= CTO_CapsLetter && opcode < CTO_MultInd)) {
					compileError(file, "Not a braille indicator opcode.");
					return 0;
				}
				ruleChars.chars[ruleChars.length++] = (widechar)opcode;
				if (atEndOfLine(file)) break;
			}
			return addRule(file, CTO_MultInd, &ruleChars, &cells, after, before, NULL,
					NULL, noback, nofor, table);
		}

		case CTO_Class:
			compileWarning(file, "class is deprecated, use attribute instead");
		case CTO_Attribute: {
			if (nofor || noback) {
				compileWarning(
						file, "nofor and noback not allowed before class/attribute");
			}
			if ((opcode == CTO_Class && (*table)->usesAttributeOrClass == 1) ||
					(opcode == CTO_Attribute && (*table)->usesAttributeOrClass == 2)) {
				compileError(file,
						"attribute and class rules must not be both present in a table");
				return 0;
			}
			if (opcode == CTO_Class)
				(*table)->usesAttributeOrClass = 2;
			else
				(*table)->usesAttributeOrClass = 1;
			if (!getToken(file, &token, "attribute name")) {
				compileError(file, "Expected %s", "attribute name");
				return 0;
			}
			if (!(*table)->characterClasses && !allocateCharacterClasses(*table)) {
				return 0;
			}

			TranslationTableCharacterAttributes attribute = 0;
			{
				int attrNumber = -1;
				switch (token.chars[0]) {
				case '0':
				case '1':
				case '2':
				case '3':
				case '4':
				case '5':
				case '6':
				case '7':
				case '8':
				case '9':
					attrNumber = token.chars[0] - '0';
					break;
				}
				if (attrNumber >= 0) {
					if (opcode == CTO_Class) {
						compileError(file,
								"Invalid class name: may not contain digits, use "
								"attribute instead of class");
						return 0;
					}
					if (token.length > 1 || attrNumber > 7) {
						compileError(file,
								"Invalid attribute name: must be a digit between 0 and 7 "
								"or a word containing only letters");
						return 0;
					}
					if (!(*table)->numberedAttributes[attrNumber])
						// attribute not used before yet: assign it a value
						(*table)->numberedAttributes[attrNumber] =
								getNextNumberedAttribute(*table);
					attribute = (*table)->numberedAttributes[attrNumber];
				} else {
					const CharacterClass *namedAttr = findCharacterClass(&token, *table);
					if (!namedAttr) {
						// no class with that name: create one
						namedAttr = addCharacterClass(
								file, &token.chars[0], token.length, *table, 1);
						if (!namedAttr) return 0;
					}
					// there is a class with that name or a new class was successfully
					// created
					attribute = namedAttr->attribute;
					if (attribute == CTC_UpperCase || attribute == CTC_LowerCase)
						attribute |= CTC_Letter;
				}
			}
			CharsString characters;
			if (!getCharacters(file, &characters)) return 0;
			for (int i = 0; i < characters.length; i++) {
				// get the character from the table, or if it is not defined yet,
				// define it
				TranslationTableCharacter *character =
						putChar(file, characters.chars[i], table, NULL);
				// set the attribute
				character->attributes |= attribute;
				// also set the attribute on the associated dots (if any)
				if (character->basechar)
					character = (TranslationTableCharacter *)&(*table)
										->ruleArea[character->basechar];
				if (character->definitionRule) {
					TranslationTableRule *defRule =
							(TranslationTableRule *)&(*table)
									->ruleArea[character->definitionRule];
					if (defRule->dotslen == 1) {
						TranslationTableCharacter *dots =
								getDots(defRule->charsdots[defRule->charslen], *table);
						if (dots) dots->attributes |= attribute;
					}
				}
			}
			return 1;
		}

			{
				TranslationTableCharacterAttributes *attributes;
				const CharacterClass *class;
			case CTO_After:
				attributes = &after;
				goto doBeforeAfter;
			case CTO_Before:
				attributes = &before;
			doBeforeAfter:
				if (!(*table)->characterClasses) {
					if (!allocateCharacterClasses(*table)) return 0;
				}
				if (!getToken(file, &token, "attribute name")) return 0;
				if (!(class = findCharacterClass(&token, *table))) {
					compileError(file, "attribute not defined");
					return 0;
				}
				*attributes |= class->attribute;
				goto doOpcode;
			}
		case CTO_Base:
			if (nofor || noback) {
				compileWarning(file, "nofor and noback not allowed before base");
			}
			if (!getToken(file, &token, "attribute name")) {
				compileError(
						file, "base opcode must be followed by a valid attribute name.");
				return 0;
			}
			if (!(*table)->characterClasses && !allocateCharacterClasses(*table)) {
				return 0;
			}
			const CharacterClass *mode = findCharacterClass(&token, *table);
			if (!mode) {
				mode = addCharacterClass(file, token.chars, token.length, *table, 1);
				if (!mode) return 0;
			}
			if (!(mode->attribute == CTC_UpperCase || mode->attribute == CTC_Digit) &&
					mode->attribute >= CTC_Space && mode->attribute <= CTC_LitDigit) {
				compileError(file,
						"base opcode must be followed by \"uppercase\", \"digit\", or a "
						"custom attribute name.");
				return 0;
			}
			if (!getRuleCharsText(file, &token)) return 0;
			if (token.length != 1) {
				compileError(file,
						"Exactly one character followed by one base character is "
						"required.");
				return 0;
			}
			TranslationTableOffset characterOffset;
			TranslationTableCharacter *character =
					putChar(file, token.chars[0], table, &characterOffset);
			if (!getRuleCharsText(file, &token)) return 0;
			if (token.length != 1) {
				compileError(file, "Exactly one base character is required.");
				return 0;
			}
			if (character->definitionRule) {
				TranslationTableRule *prevRule =
						(TranslationTableRule *)&(*table)
								->ruleArea[character->definitionRule];
				_lou_logMessage(LOU_LOG_DEBUG,
						"%s:%d: Character already defined (%s). The base rule will take "
						"precedence.",
						file->fileName, file->lineNumber,
						printSource(file, prevRule->sourceFile, prevRule->sourceLine));
				character->definitionRule = 0;
			}
			TranslationTableOffset basechar;
			putChar(file, token.chars[0], table, &basechar);
			// putChar may have moved table, so make sure character is still valid
			character = (TranslationTableCharacter *)&(*table)->ruleArea[characterOffset];
			if (character->basechar) {
				if (character->basechar == basechar &&
						character->mode == mode->attribute) {
					_lou_logMessage(LOU_LOG_DEBUG, "%s:%d: Duplicate base rule.",
							file->fileName, file->lineNumber);
				} else {
					_lou_logMessage(LOU_LOG_DEBUG,
							"%s:%d: A different base rule already exists for this "
							"character (%s). The new rule will take precedence.",
							file->fileName, file->lineNumber,
							printSource(
									file, character->sourceFile, character->sourceLine));
				}
			}
			character->basechar = basechar;
			character->mode = mode->attribute;
			character->sourceFile = file->sourceFile;
			character->sourceLine = file->lineNumber;
			/* some other processing is done at the end of the compilation, in
			 * finalizeTable() */
			return 1;
		case CTO_EmpMatchBefore:
			before |= CTC_EmpMatch;
			goto doOpcode;
		case CTO_EmpMatchAfter:
			after |= CTC_EmpMatch;
			goto doOpcode;

		case CTO_SwapCc:
		case CTO_SwapCd:
		case CTO_SwapDd:
			return compileSwap(file, opcode, noback, nofor, table);
		case CTO_Hyphen:
		case CTO_DecPoint:
			//	case CTO_Apostrophe:
			//	case CTO_Initial:
			if (!getRuleCharsText(file, &ruleChars)) return 0;
			if (!getRuleDotsPattern(file, &ruleDots)) return 0;
			if (ruleChars.length != 1 || ruleDots.length < 1) {
				compileError(file,
						"One Unicode character and at least one cell are "
						"required.");
				return 0;
			}
			return addRule(file, opcode, &ruleChars, &ruleDots, after, before, NULL, NULL,
					noback, nofor, table);
			// if (opcode == CTO_DecPoint)
			// {
			//   TranslationTableCharacter *c =
			//   getChar(ruleChars.chars[0]);
			//   if(c)
			//     c->attributes |= CTC_NumericMode;
			// }
		default:
			compileError(file, "unimplemented opcode.");
			return 0;
		}
	}
	return 0;
}

Liblouis 3.21.0 has an out-of-bounds write in compileRule in compileTranslationTable.c, as demonstrated by lou_trace.


flxdec: Don't unref() parent in the chain function

We don't own the reference here, it is owned by the caller and given to
us for the scope of this function. Leftover mistake from 0.10 porting.

https://bugzilla.gnome.org/show_bug.cgi?id=774897

gst_flxdec_chain (GstPad * pad, GstObject * parent, GstBuffer * buf)
{
  GstCaps *caps;
  guint avail;
  GstFlowReturn res = GST_FLOW_OK;

  GstFlxDec *flxdec;
  FlxHeader *flxh;

  g_return_val_if_fail (buf != NULL, GST_FLOW_ERROR);
  flxdec = (GstFlxDec *) parent;
  g_return_val_if_fail (flxdec != NULL, GST_FLOW_ERROR);

  gst_adapter_push (flxdec->adapter, buf);
  avail = gst_adapter_available (flxdec->adapter);

  if (flxdec->state == GST_FLXDEC_READ_HEADER) {
    if (avail >= FlxHeaderSize) {
      const guint8 *data = gst_adapter_map (flxdec->adapter, FlxHeaderSize);
      GstCaps *templ;

      memcpy ((gchar *) & flxdec->hdr, data, FlxHeaderSize);
      FLX_HDR_FIX_ENDIANNESS (&(flxdec->hdr));
      gst_adapter_unmap (flxdec->adapter);
      gst_adapter_flush (flxdec->adapter, FlxHeaderSize);

      flxh = &flxdec->hdr;

      /* check header */
      if (flxh->type != FLX_MAGICHDR_FLI &&
          flxh->type != FLX_MAGICHDR_FLC && flxh->type != FLX_MAGICHDR_FLX)
        goto wrong_type;

      GST_LOG ("size      :  %d", flxh->size);
      GST_LOG ("frames    :  %d", flxh->frames);
      GST_LOG ("width     :  %d", flxh->width);
      GST_LOG ("height    :  %d", flxh->height);
      GST_LOG ("depth     :  %d", flxh->depth);
      GST_LOG ("speed     :  %d", flxh->speed);

      flxdec->next_time = 0;

      if (flxh->type == FLX_MAGICHDR_FLI) {
        flxdec->frame_time = JIFFIE * flxh->speed;
      } else if (flxh->speed == 0) {
        flxdec->frame_time = GST_SECOND / 70;
      } else {
        flxdec->frame_time = flxh->speed * GST_MSECOND;
      }

      flxdec->duration = flxh->frames * flxdec->frame_time;
      GST_LOG ("duration   :  %" GST_TIME_FORMAT,
          GST_TIME_ARGS (flxdec->duration));

      templ = gst_pad_get_pad_template_caps (flxdec->srcpad);
      caps = gst_caps_copy (templ);
      gst_caps_unref (templ);
      gst_caps_set_simple (caps,
          "width", G_TYPE_INT, flxh->width,
          "height", G_TYPE_INT, flxh->height,
          "framerate", GST_TYPE_FRACTION, (gint) GST_MSECOND,
          (gint) flxdec->frame_time / 1000, NULL);

      gst_pad_set_caps (flxdec->srcpad, caps);
      gst_caps_unref (caps);

      if (flxh->depth <= 8)
        flxdec->converter =
            flx_colorspace_converter_new (flxh->width, flxh->height);

      if (flxh->type == FLX_MAGICHDR_FLC || flxh->type == FLX_MAGICHDR_FLX) {
        GST_LOG ("(FLC) aspect_dx :  %d", flxh->aspect_dx);
        GST_LOG ("(FLC) aspect_dy :  %d", flxh->aspect_dy);
        GST_LOG ("(FLC) oframe1   :  0x%08x", flxh->oframe1);
        GST_LOG ("(FLC) oframe2   :  0x%08x", flxh->oframe2);
      }

      flxdec->size = ((guint) flxh->width * (guint) flxh->height);

      /* create delta and output frame */
      flxdec->frame_data = g_malloc (flxdec->size);
      flxdec->delta_data = g_malloc (flxdec->size);

      flxdec->state = GST_FLXDEC_PLAYING;
    }
  } else if (flxdec->state == GST_FLXDEC_PLAYING) {
    GstBuffer *out;

    /* while we have enough data in the adapter */
    while (avail >= FlxFrameChunkSize && res == GST_FLOW_OK) {
      FlxFrameChunk flxfh;
      guchar *chunk;
      const guint8 *data;
      GstMapInfo map;

      chunk = NULL;
      data = gst_adapter_map (flxdec->adapter, FlxFrameChunkSize);
      memcpy (&flxfh, data, FlxFrameChunkSize);
      FLX_FRAME_CHUNK_FIX_ENDIANNESS (&flxfh);
      gst_adapter_unmap (flxdec->adapter);

      switch (flxfh.id) {
        case FLX_FRAME_TYPE:
          /* check if we have the complete frame */
          if (avail < flxfh.size)
            goto need_more_data;

          /* flush header */
          gst_adapter_flush (flxdec->adapter, FlxFrameChunkSize);

          chunk = gst_adapter_take (flxdec->adapter,
              flxfh.size - FlxFrameChunkSize);
          FLX_FRAME_TYPE_FIX_ENDIANNESS ((FlxFrameType *) chunk);
          if (((FlxFrameType *) chunk)->chunks == 0)
            break;

          /* create 32 bits output frame */
//          res = gst_pad_alloc_buffer_and_set_caps (flxdec->srcpad,
//              GST_BUFFER_OFFSET_NONE,
//              flxdec->size * 4, GST_PAD_CAPS (flxdec->srcpad), &out);
//          if (res != GST_FLOW_OK)
//            break;

          out = gst_buffer_new_and_alloc (flxdec->size * 4);

          /* decode chunks */
          if (!flx_decode_chunks (flxdec,
                  ((FlxFrameType *) chunk)->chunks,
                  chunk + FlxFrameTypeSize, flxdec->frame_data)) {
            GST_ELEMENT_ERROR (flxdec, STREAM, DECODE,
                ("%s", "Could not decode chunk"), NULL);
            return GST_FLOW_ERROR;
          }

          /* save copy of the current frame for possible delta. */
          memcpy (flxdec->delta_data, flxdec->frame_data, flxdec->size);

          gst_buffer_map (out, &map, GST_MAP_WRITE);
          /* convert current frame. */
          flx_colorspace_convert (flxdec->converter, flxdec->frame_data,
              map.data);
          gst_buffer_unmap (out, &map);

          GST_BUFFER_TIMESTAMP (out) = flxdec->next_time;
          flxdec->next_time += flxdec->frame_time;

          res = gst_pad_push (flxdec->srcpad, out);
          break;
        default:
          /* check if we have the complete frame */
          if (avail < flxfh.size)
            goto need_more_data;

          gst_adapter_flush (flxdec->adapter, flxfh.size);
          break;
      }

      g_free (chunk);

      avail = gst_adapter_available (flxdec->adapter);
    }
  }
need_more_data:
  return res;

  /* ERRORS */
wrong_type:
  {
    GST_ELEMENT_ERROR (flxdec, STREAM, WRONG_TYPE, (NULL),
        ("not a flx file (type %x)", flxh->type));
    gst_object_unref (flxdec);
    return GST_FLOW_ERROR;
  }
}

The gst_decode_chain_free_internal function in the flxdex decoder in gst-plugins-good in GStreamer before 1.10.2 allows remote attackers to cause a denial of service (invalid memory read and crash) via an invalid file, which triggers an incorrect unref call.


cgroup-v1: Require capabilities to set release_agent

The cgroup release_agent is called with call_usermodehelper.  The function
call_usermodehelper starts the release_agent with a full set fo capabilities.
Therefore require capabilities when setting the release_agaent.

Reported-by: Tabitha Sable <tabitha.c.sable@gmail.com>
Tested-by: Tabitha Sable <tabitha.c.sable@gmail.com>
Fixes: 81a6a5cdd2c5 ("Task Control Groups: automatic userspace notification of idle cgroups")
Cc: stable@vger.kernel.org # v2.6.24+
Signed-off-by: "Eric W. Biederman" <ebiederm@xmission.com>
Signed-off-by: Tejun Heo <tj@kernel.org>

static ssize_t cgroup_release_agent_write(struct kernfs_open_file *of,
					  char *buf, size_t nbytes, loff_t off)
{
	struct cgroup *cgrp;

	BUILD_BUG_ON(sizeof(cgrp->root->release_agent_path) < PATH_MAX);

	cgrp = cgroup_kn_lock_live(of->kn, false);
	if (!cgrp)
		return -ENODEV;
	spin_lock(&release_agent_path_lock);
	strlcpy(cgrp->root->release_agent_path, strstrip(buf),
		sizeof(cgrp->root->release_agent_path));
	spin_unlock(&release_agent_path_lock);
	cgroup_kn_unlock(of->kn);
	return nbytes;
}

A vulnerability was found in the Linux kernel’s cgroup_release_agent_write in the kernel/cgroup/cgroup-v1.c function. This flaw, under certain circumstances, allows the use of the cgroups v1 release_agent feature to escalate privileges and bypass the namespace isolation unexpectedly.


SERVER-59071 Treat '$sample' as unsharded when connecting directly to shards

(cherry picked from commit f3604b901d688c194de5e430c7fbab060c9dc8e0)

createRandomCursorExecutor(const CollectionPtr& coll,
                           const boost::intrusive_ptr<ExpressionContext>& expCtx,
                           long long sampleSize,
                           long long numRecords,
                           boost::optional<BucketUnpacker> bucketUnpacker) {
    OperationContext* opCtx = expCtx->opCtx;

    // Verify that we are already under a collection lock. We avoid taking locks ourselves in this
    // function because double-locking forces any PlanExecutor we create to adopt a NO_YIELD policy.
    invariant(opCtx->lockState()->isCollectionLockedForMode(coll->ns(), MODE_IS));

    static const double kMaxSampleRatioForRandCursor = 0.05;
    if (!expCtx->ns.isTimeseriesBucketsCollection()) {
        if (sampleSize > numRecords * kMaxSampleRatioForRandCursor || numRecords <= 100) {
            return std::pair{nullptr, false};
        }
    } else {
        // Suppose that a time-series bucket collection is observed to contain 200 buckets, and the
        // 'gTimeseriesBucketMaxCount' parameter is set to 1000. If all buckets are full, then the
        // maximum possible measurment count would be 200 * 1000 = 200,000. While the
        // 'SampleFromTimeseriesBucket' plan is more efficient when the sample size is small
        // relative to the total number of measurements in the time-series collection, for larger
        // sample sizes the top-k sort based sample is faster. Experiments have approximated that
        // the tipping point is roughly when the requested sample size is greater than 1% of the
        // maximum possible number of measurements in the collection (i.e. numBuckets *
        // maxMeasurementsPerBucket).
        static const double kCoefficient = 0.01;
        if (sampleSize > kCoefficient * numRecords * gTimeseriesBucketMaxCount) {
            return std::pair{nullptr, false};
        }
    }

    // Attempt to get a random cursor from the RecordStore.
    auto rsRandCursor = coll->getRecordStore()->getRandomCursor(opCtx);
    if (!rsRandCursor) {
        // The storage engine has no random cursor support.
        return std::pair{nullptr, false};
    }

    // Build a MultiIteratorStage and pass it the random-sampling RecordCursor.
    auto ws = std::make_unique<WorkingSet>();
    std::unique_ptr<PlanStage> root =
        std::make_unique<MultiIteratorStage>(expCtx.get(), ws.get(), coll);
    static_cast<MultiIteratorStage*>(root.get())->addIterator(std::move(rsRandCursor));

    // If the incoming operation is sharded, use the CSS to infer the filtering metadata for the
    // collection, otherwise treat it as unsharded
    auto collectionFilter =
        CollectionShardingState::get(opCtx, coll->ns())
            ->getOwnershipFilter(
                opCtx, CollectionShardingState::OrphanCleanupPolicy::kDisallowOrphanCleanup);

    TrialStage* trialStage = nullptr;

    // Because 'numRecords' includes orphan documents, our initial decision to optimize the $sample
    // cursor may have been mistaken. For sharded collections, build a TRIAL plan that will switch
    // to a collection scan if the ratio of orphaned to owned documents encountered over the first
    // 100 works() is such that we would have chosen not to optimize.
    static const size_t kMaxPresampleSize = 100;
    if (collectionFilter.isSharded() && !expCtx->ns.isTimeseriesBucketsCollection()) {
        // The ratio of owned to orphaned documents must be at least equal to the ratio between the
        // requested sampleSize and the maximum permitted sampleSize for the original constraints to
        // be satisfied. For instance, if there are 200 documents and the sampleSize is 5, then at
        // least (5 / (200*0.05)) = (5/10) = 50% of those documents must be owned. If less than 5%
        // of the documents in the collection are owned, we default to the backup plan.
        const auto minAdvancedToWorkRatio = std::max(
            sampleSize / (numRecords * kMaxSampleRatioForRandCursor), kMaxSampleRatioForRandCursor);
        // The trial plan is SHARDING_FILTER-MULTI_ITERATOR.
        auto randomCursorPlan = std::make_unique<ShardFilterStage>(
            expCtx.get(), collectionFilter, ws.get(), std::move(root));
        // The backup plan is SHARDING_FILTER-COLLSCAN.
        std::unique_ptr<PlanStage> collScanPlan = std::make_unique<CollectionScan>(
            expCtx.get(), coll, CollectionScanParams{}, ws.get(), nullptr);
        collScanPlan = std::make_unique<ShardFilterStage>(
            expCtx.get(), collectionFilter, ws.get(), std::move(collScanPlan));
        // Place a TRIAL stage at the root of the plan tree, and pass it the trial and backup plans.
        root = std::make_unique<TrialStage>(expCtx.get(),
                                            ws.get(),
                                            std::move(randomCursorPlan),
                                            std::move(collScanPlan),
                                            kMaxPresampleSize,
                                            minAdvancedToWorkRatio);
        trialStage = static_cast<TrialStage*>(root.get());
    } else if (expCtx->ns.isTimeseriesBucketsCollection()) {
        // Use a 'TrialStage' to run a trial between 'SampleFromTimeseriesBucket' and
        // 'UnpackTimeseriesBucket' with $sample left in the pipeline in-place. If the buckets are
        // not sufficiently full, or the 'SampleFromTimeseriesBucket' plan draws too many
        // duplicates, then we will fall back to the 'TrialStage' backup plan. This backup plan uses
        // the top-k sort sampling approach.
        //
        // Suppose the 'gTimeseriesBucketMaxCount' is 1000, but each bucket only contains 500
        // documents on average. The observed trial advanced/work ratio approximates the average
        // bucket fullness, noted here as "abf". In this example, abf = 500 / 1000 = 0.5.
        // Experiments have shown that the optimized 'SampleFromTimeseriesBucket' algorithm performs
        // better than backup plan when
        //
        //     sampleSize < 0.02 * abf * numRecords * gTimeseriesBucketMaxCount
        //
        //  This inequality can be rewritten as
        //
        //     abf > sampleSize / (0.02 * numRecords * gTimeseriesBucketMaxCount)
        //
        // Therefore, if the advanced/work ratio exceeds this threshold, we will use the
        // 'SampleFromTimeseriesBucket' plan. Note that as the sample size requested by the user
        // becomes larger with respect to the number of buckets, we require a higher advanced/work
        // ratio in order to justify using 'SampleFromTimeseriesBucket'.
        //
        // Additionally, we require the 'TrialStage' to approximate the abf as at least 0.25. When
        // buckets are mostly empty, the 'SampleFromTimeseriesBucket' will be inefficient due to a
        // lot of sampling "misses".
        static const auto kCoefficient = 0.02;
        static const auto kMinBucketFullness = 0.25;
        const auto minAdvancedToWorkRatio = std::max(
            std::min(sampleSize / (kCoefficient * numRecords * gTimeseriesBucketMaxCount), 1.0),
            kMinBucketFullness);

        auto arhashPlan = std::make_unique<SampleFromTimeseriesBucket>(
            expCtx.get(),
            ws.get(),
            std::move(root),
            *bucketUnpacker,
            // By using a quantity slightly higher than 'kMaxPresampleSize', we ensure that the
            // 'SampleFromTimeseriesBucket' stage won't fail due to too many consecutive sampling
            // attempts during the 'TrialStage's trial period.
            kMaxPresampleSize + 5,
            sampleSize,
            gTimeseriesBucketMaxCount);

        std::unique_ptr<PlanStage> collScanPlan = std::make_unique<CollectionScan>(
            expCtx.get(), coll, CollectionScanParams{}, ws.get(), nullptr);

        auto topkSortPlan = std::make_unique<UnpackTimeseriesBucket>(
            expCtx.get(), ws.get(), std::move(collScanPlan), *bucketUnpacker);

        root = std::make_unique<TrialStage>(expCtx.get(),
                                            ws.get(),
                                            std::move(arhashPlan),
                                            std::move(topkSortPlan),
                                            kMaxPresampleSize,
                                            minAdvancedToWorkRatio);
        trialStage = static_cast<TrialStage*>(root.get());
    }

    auto execStatus = plan_executor_factory::make(expCtx,
                                                  std::move(ws),
                                                  std::move(root),
                                                  &coll,
                                                  opCtx->inMultiDocumentTransaction()
                                                      ? PlanYieldPolicy::YieldPolicy::INTERRUPT_ONLY
                                                      : PlanYieldPolicy::YieldPolicy::YIELD_AUTO,
                                                  QueryPlannerParams::RETURN_OWNED_DATA);
    if (!execStatus.isOK()) {
        return execStatus.getStatus();
    }

    // For sharded collections, the root of the plan tree is a TrialStage that may have chosen
    // either a random-sampling cursor trial plan or a COLLSCAN backup plan. We can only optimize
    // the $sample aggregation stage if the trial plan was chosen.
    return std::pair{std::move(execStatus.getValue()),
                     !trialStage || !trialStage->pickedBackupPlan()};
}

An authorized user may trigger an invariant which may result in denial of service or server exit if a relevant aggregation request is sent to a shard. Usually, the requests are sent via mongos and special privileges are required in order to know the address of the shards and to log in to the shards of an auth enabled environment.


patch 8.2.3950: going beyond the end of the line with /\%V

Problem:    Going beyond the end of the line with /\%V.
Solution:   Check for valid column in getvcol().

getvcol(
    win_T	*wp,
    pos_T	*pos,
    colnr_T	*start,
    colnr_T	*cursor,
    colnr_T	*end)
{
    colnr_T	vcol;
    char_u	*ptr;		// points to current char
    char_u	*posptr;	// points to char at pos->col
    char_u	*line;		// start of the line
    int		incr;
    int		head;
#ifdef FEAT_VARTABS
    int		*vts = wp->w_buffer->b_p_vts_array;
#endif
    int		ts = wp->w_buffer->b_p_ts;
    int		c;

    vcol = 0;
    line = ptr = ml_get_buf(wp->w_buffer, pos->lnum, FALSE);
    if (pos->col == MAXCOL)
	posptr = NULL;  // continue until the NUL
    else
    {
	// Special check for an empty line, which can happen on exit, when
	// ml_get_buf() always returns an empty string.
	if (*ptr == NUL)
	    pos->col = 0;
	posptr = ptr + pos->col;
	if (has_mbyte)
	    // always start on the first byte
	    posptr -= (*mb_head_off)(line, posptr);
    }

    /*
     * This function is used very often, do some speed optimizations.
     * When 'list', 'linebreak', 'showbreak' and 'breakindent' are not set
     * use a simple loop.
     * Also use this when 'list' is set but tabs take their normal size.
     */
    if ((!wp->w_p_list || wp->w_lcs_chars.tab1 != NUL)
#ifdef FEAT_LINEBREAK
	    && !wp->w_p_lbr && *get_showbreak_value(wp) == NUL && !wp->w_p_bri
#endif
       )
    {
	for (;;)
	{
	    head = 0;
	    c = *ptr;
	    // make sure we don't go past the end of the line
	    if (c == NUL)
	    {
		incr = 1;	// NUL at end of line only takes one column
		break;
	    }
	    // A tab gets expanded, depending on the current column
	    if (c == TAB)
#ifdef FEAT_VARTABS
		incr = tabstop_padding(vcol, ts, vts);
#else
		incr = ts - (vcol % ts);
#endif
	    else
	    {
		if (has_mbyte)
		{
		    // For utf-8, if the byte is >= 0x80, need to look at
		    // further bytes to find the cell width.
		    if (enc_utf8 && c >= 0x80)
			incr = utf_ptr2cells(ptr);
		    else
			incr = g_chartab[c] & CT_CELL_MASK;

		    // If a double-cell char doesn't fit at the end of a line
		    // it wraps to the next line, it's like this char is three
		    // cells wide.
		    if (incr == 2 && wp->w_p_wrap && MB_BYTE2LEN(*ptr) > 1
			    && in_win_border(wp, vcol))
		    {
			++incr;
			head = 1;
		    }
		}
		else
		    incr = g_chartab[c] & CT_CELL_MASK;
	    }

	    if (posptr != NULL && ptr >= posptr) // character at pos->col
		break;

	    vcol += incr;
	    MB_PTR_ADV(ptr);
	}
    }
    else
    {
	for (;;)
	{
	    // A tab gets expanded, depending on the current column
	    head = 0;
	    incr = win_lbr_chartabsize(wp, line, ptr, vcol, &head);
	    // make sure we don't go past the end of the line
	    if (*ptr == NUL)
	    {
		incr = 1;	// NUL at end of line only takes one column
		break;
	    }

	    if (posptr != NULL && ptr >= posptr) // character at pos->col
		break;

	    vcol += incr;
	    MB_PTR_ADV(ptr);
	}
    }
    if (start != NULL)
	*start = vcol + head;
    if (end != NULL)
	*end = vcol + incr - 1;
    if (cursor != NULL)
    {
	if (*ptr == TAB
		&& (State & NORMAL)
		&& !wp->w_p_list
		&& !virtual_active()
		&& !(VIsual_active
				&& (*p_sel == 'e' || LTOREQ_POS(*pos, VIsual)))
		)
	    *cursor = vcol + incr - 1;	    // cursor at end
	else
	    *cursor = vcol + head;	    // cursor at start
    }
}


fix(plugin): Add default limits for chunks and message size

Based on a reported DoS vulnerability reported by Team82 (Claroty
Research).

setup_secureChannel(void) {
    TestingPolicy(&dummyPolicy, dummyCertificate, &fCalled, &keySizes);
    UA_SecureChannel_init(&testChannel, &UA_ConnectionConfig_default);
    UA_SecureChannel_setSecurityPolicy(&testChannel, &dummyPolicy, &dummyCertificate);

    testingConnection = createDummyConnection(65535, &sentData);
    UA_Connection_attachSecureChannel(&testingConnection, &testChannel);
    testChannel.connection = &testingConnection;

    testChannel.state = UA_SECURECHANNELSTATE_OPEN;
}

The package open62541/open62541 before 1.2.5, from 1.3-rc1 and before 1.3.1 are vulnerable to Denial of Service (DoS) due to a missing limitation on the number of received chunks - per single session or in total for all concurrent sessions. An attacker can exploit this vulnerability by sending an unlimited number of huge chunks (e.g. 2GB each) without sending the Final closing chunk.


patch 8.2.5164: invalid memory access after diff buffer manipulations

Problem:    Invalid memory access after diff buffer manipulations.
Solution:   Use zero offset when change removes all lines in a diff block.

diff_mark_adjust_tp(
    tabpage_T	*tp,
    int		idx,
    linenr_T	line1,
    linenr_T	line2,
    long	amount,
    long	amount_after)
{
    diff_T	*dp;
    diff_T	*dprev;
    diff_T	*dnext;
    int		i;
    int		inserted, deleted;
    int		n, off;
    linenr_T	last;
    linenr_T	lnum_deleted = line1;	// lnum of remaining deletion
    int		check_unchanged;

    if (diff_internal())
    {
	// Will update diffs before redrawing.  Set _invalid to update the
	// diffs themselves, set _update to also update folds properly just
	// before redrawing.
	// Do update marks here, it is needed for :%diffput.
	tp->tp_diff_invalid = TRUE;
	tp->tp_diff_update = TRUE;
    }

    if (line2 == MAXLNUM)
    {
	// mark_adjust(99, MAXLNUM, 9, 0): insert lines
	inserted = amount;
	deleted = 0;
    }
    else if (amount_after > 0)
    {
	// mark_adjust(99, 98, MAXLNUM, 9): a change that inserts lines
	inserted = amount_after;
	deleted = 0;
    }
    else
    {
	// mark_adjust(98, 99, MAXLNUM, -2): delete lines
	inserted = 0;
	deleted = -amount_after;
    }

    dprev = NULL;
    dp = tp->tp_first_diff;
    for (;;)
    {
	// If the change is after the previous diff block and before the next
	// diff block, thus not touching an existing change, create a new diff
	// block.  Don't do this when ex_diffgetput() is busy.
	if ((dp == NULL || dp->df_lnum[idx] - 1 > line2
		    || (line2 == MAXLNUM && dp->df_lnum[idx] > line1))
		&& (dprev == NULL
		    || dprev->df_lnum[idx] + dprev->df_count[idx] < line1)
		&& !diff_busy)
	{
	    dnext = diff_alloc_new(tp, dprev, dp);
	    if (dnext == NULL)
		return;

	    dnext->df_lnum[idx] = line1;
	    dnext->df_count[idx] = inserted;
	    for (i = 0; i < DB_COUNT; ++i)
		if (tp->tp_diffbuf[i] != NULL && i != idx)
		{
		    if (dprev == NULL)
			dnext->df_lnum[i] = line1;
		    else
			dnext->df_lnum[i] = line1
			    + (dprev->df_lnum[i] + dprev->df_count[i])
			    - (dprev->df_lnum[idx] + dprev->df_count[idx]);
		    dnext->df_count[i] = deleted;
		}
	}

	// if at end of the list, quit
	if (dp == NULL)
	    break;

	/*
	 * Check for these situations:
	 *	  1  2	3
	 *	  1  2	3
	 * line1     2	3  4  5
	 *	     2	3  4  5
	 *	     2	3  4  5
	 * line2     2	3  4  5
	 *		3     5  6
	 *		3     5  6
	 */
	// compute last line of this change
	last = dp->df_lnum[idx] + dp->df_count[idx] - 1;

	// 1. change completely above line1: nothing to do
	if (last >= line1 - 1)
	{
	    // 6. change below line2: only adjust for amount_after; also when
	    // "deleted" became zero when deleted all lines between two diffs
	    if (dp->df_lnum[idx] - (deleted + inserted != 0) > line2)
	    {
		if (amount_after == 0)
		    break;	// nothing left to change
		dp->df_lnum[idx] += amount_after;
	    }
	    else
	    {
		check_unchanged = FALSE;

		// 2. 3. 4. 5.: inserted/deleted lines touching this diff.
		if (deleted > 0)
		{
		    if (dp->df_lnum[idx] >= line1)
		    {
			off = dp->df_lnum[idx] - lnum_deleted;
			if (last <= line2)
			{
			    // 4. delete all lines of diff
			    if (dp->df_next != NULL
				    && dp->df_next->df_lnum[idx] - 1 <= line2)
			    {
				// delete continues in next diff, only do
				// lines until that one
				n = dp->df_next->df_lnum[idx] - lnum_deleted;
				deleted -= n;
				n -= dp->df_count[idx];
				lnum_deleted = dp->df_next->df_lnum[idx];
			    }
			    else
				n = deleted - dp->df_count[idx];
			    dp->df_count[idx] = 0;
			}
			else
			{
			    // 5. delete lines at or just before top of diff
			    n = off;
			    dp->df_count[idx] -= line2 - dp->df_lnum[idx] + 1;
			    check_unchanged = TRUE;
			}
			dp->df_lnum[idx] = line1;
		    }
		    else
		    {
			off = 0;
			if (last < line2)
			{
			    // 2. delete at end of diff
			    dp->df_count[idx] -= last - lnum_deleted + 1;
			    if (dp->df_next != NULL
				    && dp->df_next->df_lnum[idx] - 1 <= line2)
			    {
				// delete continues in next diff, only do
				// lines until that one
				n = dp->df_next->df_lnum[idx] - 1 - last;
				deleted -= dp->df_next->df_lnum[idx]
							       - lnum_deleted;
				lnum_deleted = dp->df_next->df_lnum[idx];
			    }
			    else
				n = line2 - last;
			    check_unchanged = TRUE;
			}
			else
			{
			    // 3. delete lines inside the diff
			    n = 0;
			    dp->df_count[idx] -= deleted;
			}
		    }

		    for (i = 0; i < DB_COUNT; ++i)
			if (tp->tp_diffbuf[i] != NULL && i != idx)
			{
			    dp->df_lnum[i] -= off;
			    dp->df_count[i] += n;
			}
		}
		else
		{
		    if (dp->df_lnum[idx] <= line1)
		    {
			// inserted lines somewhere in this diff
			dp->df_count[idx] += inserted;
			check_unchanged = TRUE;
		    }
		    else
			// inserted lines somewhere above this diff
			dp->df_lnum[idx] += inserted;
		}

		if (check_unchanged)
		    // Check if inserted lines are equal, may reduce the
		    // size of the diff.  TODO: also check for equal lines
		    // in the middle and perhaps split the block.
		    diff_check_unchanged(tp, dp);
	    }
	}

	// check if this block touches the previous one, may merge them.
	if (dprev != NULL && dprev->df_lnum[idx] + dprev->df_count[idx]
							  == dp->df_lnum[idx])
	{
	    for (i = 0; i < DB_COUNT; ++i)
		if (tp->tp_diffbuf[i] != NULL)
		    dprev->df_count[i] += dp->df_count[i];
	    dprev->df_next = dp->df_next;
	    vim_free(dp);
	    dp = dprev->df_next;
	}
	else
	{
	    // Advance to next entry.
	    dprev = dp;
	    dp = dp->df_next;
	}
    }

    dprev = NULL;
    dp = tp->tp_first_diff;
    while (dp != NULL)
    {
	// All counts are zero, remove this entry.
	for (i = 0; i < DB_COUNT; ++i)
	    if (tp->tp_diffbuf[i] != NULL && dp->df_count[i] != 0)
		break;
	if (i == DB_COUNT)
	{
	    dnext = dp->df_next;
	    vim_free(dp);
	    dp = dnext;
	    if (dprev == NULL)
		tp->tp_first_diff = dnext;
	    else
		dprev->df_next = dnext;
	}
	else
	{
	    // Advance to next entry.
	    dprev = dp;
	    dp = dp->df_next;
	}

    }

    if (tp == curtab)
    {
	// Don't redraw right away, this updates the diffs, which can be slow.
	need_diff_redraw = TRUE;

	// Need to recompute the scroll binding, may remove or add filler
	// lines (e.g., when adding lines above w_topline). But it's slow when
	// making many changes, postpone until redrawing.
	diff_need_scrollbind = TRUE;
    }
}

Out-of-bounds Write in GitHub repository vim/vim prior to 8.2.


void mobi_buffer_move(MOBIBuffer *buf, const int offset, const size_t len) {
    size_t aoffset = (size_t) abs(offset);
    unsigned char *source = buf->data + buf->offset;
    if (offset >= 0) {
        if (buf->offset + aoffset + len > buf->maxlen) {
            debug_print("%s", "End of buffer\n");
            buf->error = MOBI_BUFFER_END;
            return;
        }
        source += aoffset;
    } else {
        if (buf->offset < aoffset) {
            debug_print("%s", "End of buffer\n");
            buf->error = MOBI_BUFFER_END;
            return;
        }
        source -= aoffset;
    }
    memmove(buf->data + buf->offset, source, len);
    buf->offset += len;
}

libmobi is vulnerable to Out-of-bounds Write


Reject the ZIP file and report it as corrupt if the size of the
central directory and/or the offset of start of central directory
point beyond the end of the ZIP file. [CVE-2018-6484]

__zzip_fetch_disk_trailer(int fd, zzip_off_t filesize,
                          struct _disk_trailer *_zzip_restrict trailer,
                          zzip_plugin_io_t io)
{
#ifdef DEBUG
#define return(val) { e=val; HINT2("%s", zzip_strerror(e)); goto cleanup; }
#else
#define return(val) { e=val; goto cleanup; }
#endif
    register int e;

#ifndef _LOWSTK
    auto char buffer[2 * ZZIP_BUFSIZ];
    char *buf = buffer;
#else
    char *buf = malloc(2 * ZZIP_BUFSIZ);
#endif
    zzip_off_t offset = 0;
    zzip_ssize_t maplen = 0;    /* mmap(),read(),getpagesize() use size_t !! */
    char *fd_map = 0;

    if (! trailer)
        { return(EINVAL); }

    if (filesize < __sizeof(struct zzip_disk_trailer))
          { return(ZZIP_DIR_TOO_SHORT); }

    if (! buf)
        { return(ZZIP_OUTOFMEM); }

    offset = filesize;          /* a.k.a. old offset */
    while (1)                   /* outer loop */
    {
        register unsigned char *mapped;

        if (offset <= 0)
            { return(ZZIP_DIR_EDH_MISSING); }

        /* trailer cannot be farther away than 64K from fileend */
        if (filesize - offset > 64 * 1024)
            { return(ZZIP_DIR_EDH_MISSING); }

        /* the new offset shall overlap with the area after the old offset! */
        if (USE_MMAP && io->fd.sys)
        {
            zzip_off_t mapoff = offset;
            {
                zzip_ssize_t pagesize = _zzip_getpagesize(io->fd.sys);
                if (pagesize < ZZIP_BUFSIZ)
                    goto non_mmap;      /* an error? */
                if (mapoff == filesize && filesize > pagesize)
                    mapoff -= pagesize;
                if (mapoff < pagesize)
                {
                    maplen = (zzip_ssize_t) mapoff + pagesize;
                    mapoff = 0;
                } else
                {
                    mapoff -= pagesize;
                    maplen = 2 * pagesize;
                    if ((zzip_ssize_t) mapoff & (pagesize - 1))
                    {           /*only 1. run */
                        pagesize -= (zzip_ssize_t) mapoff & (pagesize - 1);
                        mapoff += pagesize;
                        maplen -= pagesize;
                    }
                }
                if (mapoff + maplen > filesize)
                    maplen = filesize - mapoff;
            }

            fd_map = _zzip_mmap(io->fd.sys, fd, mapoff, (zzip_size_t) maplen);
            if (fd_map == MAP_FAILED)
                goto non_mmap;
            mapped = (unsigned char *) fd_map;
            offset = mapoff;    /* success */
            HINT3("mapped *%p len=%li", fd_map, (long) maplen);
        } else
        {
          non_mmap:
            fd_map = 0;         /* have no mmap */
            {
                zzip_off_t pagesize = ZZIP_BUFSIZ;
                if (offset == filesize && filesize > pagesize)
                    offset -= pagesize;
                if (offset < pagesize)
                {
                    maplen = (zzip_ssize_t) offset + pagesize;
                    offset = 0;
                } else
                {
                    offset -= pagesize;
                    maplen = 2 * pagesize;
                    if ((zzip_ssize_t) offset & (pagesize - 1))
                    {           /*on 1st run */
                        pagesize -= (zzip_ssize_t) offset & (pagesize - 1);
                        offset += pagesize;
                        maplen -= pagesize;
                    }
                }
                if (offset + maplen > filesize)
                    maplen = filesize - offset;
            }

            if (io->fd.seeks(fd, offset, SEEK_SET) < 0)
                { return(ZZIP_DIR_SEEK); }
            if (io->fd.read(fd, buf, (zzip_size_t) maplen) < maplen)
                { return(ZZIP_DIR_READ); }
            mapped = (unsigned char *) buf;     /* success */
            HINT5("offs=$%lx len=%li filesize=%li pagesize=%i",
                  (long) offset, (long) maplen, (long) filesize, ZZIP_BUFSIZ);
        }

        {                       /* now, check for the trailer-magic, hopefully near the end of file */
            register unsigned char *end = mapped + maplen;
            register unsigned char *tail;
            for (tail = end - 1; (tail >= mapped); tail--)
            {
                if ((*tail == 'P') &&   /* quick pre-check for trailer magic */
                    end - tail >= __sizeof(struct zzip_disk_trailer) - 2 &&
                    zzip_disk_trailer_check_magic(tail))
                {
#                  ifndef ZZIP_DISK64_TRAILER
                    /* if the file-comment is not present, it happens
                       that the z_comment field often isn't either */
                    if (end - tail >= __sizeof(*trailer))
                    {
                        memcpy(trailer, tail, sizeof(*trailer));
                    } else
                    {
                        memcpy(trailer, tail, sizeof(*trailer) - 2);
                        trailer->z_comment[0] = 0;
                        trailer->z_comment[1] = 0;
                    }
#                  else
                    struct zzip_disk_trailer *orig =
                        (struct zzip_disk_trailer *) tail;
                    trailer->zz_tail = tail;
                    trailer->zz_entries = zzip_disk_trailer_localentries(orig);
                    trailer->zz_finalentries =
                        zzip_disk_trailer_finalentries(orig);
                    trailer->zz_rootseek = zzip_disk_trailer_rootseek(orig);
                    trailer->zz_rootsize = zzip_disk_trailer_rootsize(orig);
#                  endif

                    __fixup_rootseek(offset + tail - mapped, trailer);
                    { return(0); }
                } else if ((*tail == 'P') &&
                           end - tail >=
                           __sizeof(struct zzip_disk64_trailer) - 2
                           && zzip_disk64_trailer_check_magic(tail))
                {
#                  ifndef ZZIP_DISK64_TRAILER
                    return (ZZIP_DIR_LARGEFILE);
#                  else
                    struct zzip_disk64_trailer *orig =
                        (struct zzip_disk64_trailer *) tail;
                    trailer->zz_tail = tail;
                    trailer->zz_entries =
                        zzip_disk64_trailer_localentries(orig);
                    trailer->zz_finalentries =
                        zzip_disk64_trailer_finalentries(orig);
                    trailer->zz_rootseek = zzip_disk64_trailer_rootseek(orig);
                    trailer->zz_rootsize = zzip_disk64_trailer_rootsize(orig);
                    { return(0); }
#                  endif
                }
            }
        }

        if (USE_MMAP && fd_map)
        {
            HINT3("unmap *%p len=%li", fd_map, (long) maplen);
            _zzip_munmap(io->fd.sys, fd_map, (zzip_size_t) maplen);
            fd_map = 0;
        }
    }                           /*outer loop */

  cleanup:
    if (USE_MMAP && fd_map)
    {
        HINT3("unmap *%p len=%li", fd_map, (long) maplen);
        _zzip_munmap(io->fd.sys, fd_map, (zzip_size_t) maplen);
    }
#   ifdef _LOWSTK
    free(buf);
#   endif
#   undef return
    return e;
}

In ZZIPlib 0.13.68, there is an uncontrolled memory allocation and a crash in the __zzip_parse_root_directory function of zzip/zip.c. Remote attackers could leverage this vulnerability to cause a denial of service via a crafted zip file.


cadence_gem: switch to use qemu_receive_packet() for loopback

This patch switches to use qemu_receive_packet() which can detect
reentrancy and return early.

This is intended to address CVE-2021-3416.

Cc: Prasad J Pandit <ppandit@redhat.com>
Cc: qemu-stable@nongnu.org
Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com>
Signed-off-by: Alexander Bulekov <alxndr@bu.edu>
Signed-off-by: Jason Wang <jasowang@redhat.com>

static void gem_transmit(CadenceGEMState *s)
{
    uint32_t desc[DESC_MAX_NUM_WORDS];
    hwaddr packet_desc_addr;
    uint8_t     *p;
    unsigned    total_bytes;
    int q = 0;

    /* Do nothing if transmit is not enabled. */
    if (!(s->regs[GEM_NWCTRL] & GEM_NWCTRL_TXENA)) {
        return;
    }

    DB_PRINT("\n");

    /* The packet we will hand off to QEMU.
     * Packets scattered across multiple descriptors are gathered to this
     * one contiguous buffer first.
     */
    p = s->tx_packet;
    total_bytes = 0;

    for (q = s->num_priority_queues - 1; q >= 0; q--) {
        /* read current descriptor */
        packet_desc_addr = gem_get_tx_desc_addr(s, q);

        DB_PRINT("read descriptor 0x%" HWADDR_PRIx "\n", packet_desc_addr);
        address_space_read(&s->dma_as, packet_desc_addr,
                           MEMTXATTRS_UNSPECIFIED, desc,
                           sizeof(uint32_t) * gem_get_desc_len(s, false));
        /* Handle all descriptors owned by hardware */
        while (tx_desc_get_used(desc) == 0) {

            /* Do nothing if transmit is not enabled. */
            if (!(s->regs[GEM_NWCTRL] & GEM_NWCTRL_TXENA)) {
                return;
            }
            print_gem_tx_desc(desc, q);

            /* The real hardware would eat this (and possibly crash).
             * For QEMU let's lend a helping hand.
             */
            if ((tx_desc_get_buffer(s, desc) == 0) ||
                (tx_desc_get_length(desc) == 0)) {
                DB_PRINT("Invalid TX descriptor @ 0x%" HWADDR_PRIx "\n",
                         packet_desc_addr);
                break;
            }

            if (tx_desc_get_length(desc) > gem_get_max_buf_len(s, true) -
                                               (p - s->tx_packet)) {
                qemu_log_mask(LOG_GUEST_ERROR, "TX descriptor @ 0x%" \
                         HWADDR_PRIx " too large: size 0x%x space 0x%zx\n",
                         packet_desc_addr, tx_desc_get_length(desc),
                         gem_get_max_buf_len(s, true) - (p - s->tx_packet));
                gem_set_isr(s, q, GEM_INT_AMBA_ERR);
                break;
            }

            /* Gather this fragment of the packet from "dma memory" to our
             * contig buffer.
             */
            address_space_read(&s->dma_as, tx_desc_get_buffer(s, desc),
                               MEMTXATTRS_UNSPECIFIED,
                               p, tx_desc_get_length(desc));
            p += tx_desc_get_length(desc);
            total_bytes += tx_desc_get_length(desc);

            /* Last descriptor for this packet; hand the whole thing off */
            if (tx_desc_get_last(desc)) {
                uint32_t desc_first[DESC_MAX_NUM_WORDS];
                hwaddr desc_addr = gem_get_tx_desc_addr(s, q);

                /* Modify the 1st descriptor of this packet to be owned by
                 * the processor.
                 */
                address_space_read(&s->dma_as, desc_addr,
                                   MEMTXATTRS_UNSPECIFIED, desc_first,
                                   sizeof(desc_first));
                tx_desc_set_used(desc_first);
                address_space_write(&s->dma_as, desc_addr,
                                    MEMTXATTRS_UNSPECIFIED, desc_first,
                                    sizeof(desc_first));
                /* Advance the hardware current descriptor past this packet */
                if (tx_desc_get_wrap(desc)) {
                    s->tx_desc_addr[q] = gem_get_tx_queue_base_addr(s, q);
                } else {
                    s->tx_desc_addr[q] = packet_desc_addr +
                                         4 * gem_get_desc_len(s, false);
                }
                DB_PRINT("TX descriptor next: 0x%08x\n", s->tx_desc_addr[q]);

                s->regs[GEM_TXSTATUS] |= GEM_TXSTATUS_TXCMPL;
                gem_set_isr(s, q, GEM_INT_TXCMPL);

                /* Handle interrupt consequences */
                gem_update_int_status(s);

                /* Is checksum offload enabled? */
                if (s->regs[GEM_DMACFG] & GEM_DMACFG_TXCSUM_OFFL) {
                    net_checksum_calculate(s->tx_packet, total_bytes, CSUM_ALL);
                }

                /* Update MAC statistics */
                gem_transmit_updatestats(s, s->tx_packet, total_bytes);

                /* Send the packet somewhere */
                if (s->phy_loop || (s->regs[GEM_NWCTRL] &
                                    GEM_NWCTRL_LOCALLOOP)) {
                    gem_receive(qemu_get_queue(s->nic), s->tx_packet,
                                total_bytes);
                } else {
                    qemu_send_packet(qemu_get_queue(s->nic), s->tx_packet,
                                     total_bytes);
                }

                /* Prepare for next packet */
                p = s->tx_packet;
                total_bytes = 0;
            }

            /* read next descriptor */
            if (tx_desc_get_wrap(desc)) {

                if (s->regs[GEM_DMACFG] & GEM_DMACFG_ADDR_64B) {
                    packet_desc_addr = s->regs[GEM_TBQPH];
                    packet_desc_addr <<= 32;
                } else {
                    packet_desc_addr = 0;
                }
                packet_desc_addr |= gem_get_tx_queue_base_addr(s, q);
            } else {
                packet_desc_addr += 4 * gem_get_desc_len(s, false);
            }
            DB_PRINT("read descriptor 0x%" HWADDR_PRIx "\n", packet_desc_addr);
            address_space_read(&s->dma_as, packet_desc_addr,
                               MEMTXATTRS_UNSPECIFIED, desc,
                               sizeof(uint32_t) * gem_get_desc_len(s, false));
        }

        if (tx_desc_get_used(desc)) {
            s->regs[GEM_TXSTATUS] |= GEM_TXSTATUS_USED;
            /* IRQ TXUSED is defined only for queue 0 */
            if (q == 0) {
                gem_set_isr(s, 0, GEM_INT_TXUSED);
            }
            gem_update_int_status(s);
        }
    }
}

A potential stack overflow via infinite loop issue was found in various NIC emulators of QEMU in versions up to and including 5.2.0. The issue occurs in loopback mode of a NIC wherein reentrant DMA checks get bypassed. A guest user/process may use this flaw to consume CPU cycles or crash the QEMU process on the host resulting in DoS scenario.