Limit depth of nesting in direct objects (fixes #202)

This fixes CVE-2018-9918.

QPDFObjectHandle::parseInternal(PointerHolder<InputSource> input,
                                std::string const& object_description,
                                QPDFTokenizer& tokenizer, bool& empty,
                                StringDecrypter* decrypter, QPDF* context,
                                bool content_stream)
{
    // This method must take care not to resolve any objects. Don't
    // check the type of any object without first ensuring that it is
    // a direct object. Otherwise, doing so may have the side effect
    // of reading the object and changing the file pointer.

    empty = false;

    QPDFObjectHandle object;

    std::vector<std::vector<QPDFObjectHandle> > olist_stack;
    olist_stack.push_back(std::vector<QPDFObjectHandle>());
    std::vector<parser_state_e> state_stack;
    state_stack.push_back(st_top);
    std::vector<qpdf_offset_t> offset_stack;
    qpdf_offset_t offset = input->tell();
    offset_stack.push_back(offset);
    bool done = false;
    while (! done)
    {
        std::vector<QPDFObjectHandle>& olist = olist_stack.back();
        parser_state_e state = state_stack.back();
        offset = offset_stack.back();

	object = QPDFObjectHandle();

	QPDFTokenizer::Token token =
            tokenizer.readToken(input, object_description, true);

	switch (token.getType())
	{
          case QPDFTokenizer::tt_eof:
            if (! content_stream)
            {
                QTC::TC("qpdf", "QPDFObjectHandle eof in parseInternal");
                warn(context,
                     QPDFExc(qpdf_e_damaged_pdf, input->getName(),
                             object_description,
                             input->getLastOffset(),
                             "unexpected EOF"));
            }
            state = st_eof;
            break;

          case QPDFTokenizer::tt_bad:
	    QTC::TC("qpdf", "QPDFObjectHandle bad token in parse");
            warn(context,
                 QPDFExc(qpdf_e_damaged_pdf, input->getName(),
                         object_description,
                         input->getLastOffset(),
                         token.getErrorMessage()));
            object = newNull();
	    break;

	  case QPDFTokenizer::tt_brace_open:
	  case QPDFTokenizer::tt_brace_close:
	    QTC::TC("qpdf", "QPDFObjectHandle bad brace");
            warn(context,
                 QPDFExc(qpdf_e_damaged_pdf, input->getName(),
                         object_description,
                         input->getLastOffset(),
                         "treating unexpected brace token as null"));
            object = newNull();
	    break;

	  case QPDFTokenizer::tt_array_close:
	    if (state == st_array)
	    {
                state = st_stop;
	    }
	    else
	    {
		QTC::TC("qpdf", "QPDFObjectHandle bad array close");
                warn(context,
                     QPDFExc(qpdf_e_damaged_pdf, input->getName(),
                             object_description,
                             input->getLastOffset(),
                             "treating unexpected array close token as null"));
                object = newNull();
	    }
	    break;

	  case QPDFTokenizer::tt_dict_close:
	    if (state == st_dictionary)
	    {
                state = st_stop;
	    }
	    else
	    {
		QTC::TC("qpdf", "QPDFObjectHandle bad dictionary close");
                warn(context,
                     QPDFExc(qpdf_e_damaged_pdf, input->getName(),
                             object_description,
                             input->getLastOffset(),
                             "unexpected dictionary close token"));
                object = newNull();
	    }
	    break;

	  case QPDFTokenizer::tt_array_open:
	  case QPDFTokenizer::tt_dict_open:
            olist_stack.push_back(std::vector<QPDFObjectHandle>());
            state = st_start;
            offset_stack.push_back(input->tell());
            state_stack.push_back(
                (token.getType() == QPDFTokenizer::tt_array_open) ?
                st_array : st_dictionary);
	    break;

	  case QPDFTokenizer::tt_bool:
	    object = newBool((token.getValue() == "true"));
	    break;

	  case QPDFTokenizer::tt_null:
	    object = newNull();
	    break;

	  case QPDFTokenizer::tt_integer:
	    object = newInteger(QUtil::string_to_ll(token.getValue().c_str()));
	    break;

	  case QPDFTokenizer::tt_real:
	    object = newReal(token.getValue());
	    break;

	  case QPDFTokenizer::tt_name:
	    object = newName(token.getValue());
	    break;

	  case QPDFTokenizer::tt_word:
	    {
		std::string const& value = token.getValue();
                if (content_stream)
                {
                    object = QPDFObjectHandle::newOperator(value);
                }
		else if ((value == "R") && (state != st_top) &&
                         (olist.size() >= 2) &&
                         (! olist.at(olist.size() - 1).isIndirect()) &&
                         (olist.at(olist.size() - 1).isInteger()) &&
                         (! olist.at(olist.size() - 2).isIndirect()) &&
                         (olist.at(olist.size() - 2).isInteger()))
		{
                    if (context == 0)
                    {
                        QTC::TC("qpdf", "QPDFObjectHandle indirect without context");
                        throw std::logic_error(
                            "QPDFObjectHandle::parse called without context"
                            " on an object with indirect references");
                    }
		    // Try to resolve indirect objects
		    object = newIndirect(
			context,
			olist.at(olist.size() - 2).getIntValue(),
			olist.at(olist.size() - 1).getIntValue());
		    olist.pop_back();
		    olist.pop_back();
		}
		else if ((value == "endobj") && (state == st_top))
		{
		    // We just saw endobj without having read
		    // anything.  Treat this as a null and do not move
		    // the input source's offset.
		    object = newNull();
		    input->seek(input->getLastOffset(), SEEK_SET);
                    empty = true;
		}
		else
		{
                    QTC::TC("qpdf", "QPDFObjectHandle treat word as string");
                    warn(context,
                         QPDFExc(qpdf_e_damaged_pdf, input->getName(),
                                 object_description,
                                 input->getLastOffset(),
                                 "unknown token while reading object;"
                                 " treating as string"));
                    object = newString(value);
		}
	    }
	    break;

	  case QPDFTokenizer::tt_string:
	    {
		std::string val = token.getValue();
                if (decrypter)
                {
                    decrypter->decryptString(val);
                }
		object = QPDFObjectHandle::newString(val);
	    }

	    break;

	  default:
            warn(context,
                 QPDFExc(qpdf_e_damaged_pdf, input->getName(),
                         object_description,
                         input->getLastOffset(),
                         "treating unknown token type as null while "
                         "reading object"));
            object = newNull();
	    break;
	}

        if ((! object.isInitialized()) &&
            (! ((state == st_start) ||
                (state == st_stop) ||
                (state == st_eof))))
        {
            throw std::logic_error(
                "QPDFObjectHandle::parseInternal: "
                "unexpected uninitialized object");
            object = newNull();
        }

        switch (state)
        {
          case st_eof:
            if (state_stack.size() > 1)
            {
                warn(context,
                     QPDFExc(qpdf_e_damaged_pdf, input->getName(),
                             object_description,
                             input->getLastOffset(),
                             "parse error while reading object"));
            }
            done = true;
            // In content stream mode, leave object uninitialized to
            // indicate EOF
            if (! content_stream)
            {
                object = newNull();
            }
            break;

          case st_dictionary:
          case st_array:
            setObjectDescriptionFromInput(
                object, context, object_description, input,
                input->getLastOffset());
            olist.push_back(object);
            break;

          case st_top:
            done = true;
            break;

          case st_start:
            break;

          case st_stop:
            if ((state_stack.size() < 2) || (olist_stack.size() < 2))
            {
                throw std::logic_error(
                    "QPDFObjectHandle::parseInternal: st_stop encountered"
                    " with insufficient elements in stack");
            }
            parser_state_e old_state = state_stack.back();
            state_stack.pop_back();
            if (old_state == st_array)
            {
                object = newArray(olist);
                setObjectDescriptionFromInput(
                    object, context, object_description, input, offset);
            }
            else if (old_state == st_dictionary)
            {
                // Convert list to map. Alternating elements are keys.
                // Attempt to recover more or less gracefully from
                // invalid dictionaries.
                std::set<std::string> names;
                for (std::vector<QPDFObjectHandle>::iterator iter =
                         olist.begin();
                     iter != olist.end(); ++iter)
                {
                    if ((! (*iter).isIndirect()) && (*iter).isName())
                    {
                        names.insert((*iter).getName());
                    }
                }

                std::map<std::string, QPDFObjectHandle> dict;
                int next_fake_key = 1;
                for (unsigned int i = 0; i < olist.size(); ++i)
                {
                    QPDFObjectHandle key_obj = olist.at(i);
                    QPDFObjectHandle val;
                    if (key_obj.isIndirect() || (! key_obj.isName()))
                    {
                        bool found_fake = false;
                        std::string candidate;
                        while (! found_fake)
                        {
                            candidate =
                                "/QPDFFake" +
                                QUtil::int_to_string(next_fake_key++);
                            found_fake = (names.count(candidate) == 0);
                            QTC::TC("qpdf", "QPDFObjectHandle found fake",
                                    (found_fake ? 0 : 1));
                        }
                        warn(context,
                             QPDFExc(
                                 qpdf_e_damaged_pdf,
                                 input->getName(), object_description, offset,
                                 "expected dictionary key but found"
                                 " non-name object; inserting key " +
                                 candidate));
                        val = key_obj;
                        key_obj = newName(candidate);
                    }
                    else if (i + 1 >= olist.size())
                    {
                        QTC::TC("qpdf", "QPDFObjectHandle no val for last key");
                        warn(context,
                             QPDFExc(
                                 qpdf_e_damaged_pdf,
                                 input->getName(), object_description, offset,
                                 "dictionary ended prematurely; "
                                 "using null as value for last key"));
                        val = newNull();
                        setObjectDescriptionFromInput(
                            val, context, object_description, input, offset);
                    }
                    else
                    {
                        val = olist.at(++i);
                    }
                    dict[key_obj.getName()] = val;
                }
                object = newDictionary(dict);
                setObjectDescriptionFromInput(
                    object, context, object_description, input, offset);
            }
            olist_stack.pop_back();
            offset_stack.pop_back();
            if (state_stack.back() == st_top)
            {
                done = true;
            }
            else
            {
                olist_stack.back().push_back(object);
            }
        }
    }

    setObjectDescriptionFromInput(
        object, context, object_description, input, offset);
    return object;
}

libqpdf.a in QPDF through 8.0.2 mishandles certain "expected dictionary key but found non-name object" cases, allowing remote attackers to cause a denial of service (stack exhaustion), related to the QPDFObjectHandle and QPDF_Dictionary classes, because nesting in direct objects is not restricted.


QPDFObjectHandle::parseInternal(PointerHolder<InputSource> input,
                                std::string const& object_description,
                                QPDFTokenizer& tokenizer, bool& empty,
                                StringDecrypter* decrypter, QPDF* context,
                                bool content_stream)
{
    // This method must take care not to resolve any objects. Don't
    // check the type of any object without first ensuring that it is
    // a direct object. Otherwise, doing so may have the side effect
    // of reading the object and changing the file pointer.

    empty = false;

    QPDFObjectHandle object;

    std::vector<std::vector<QPDFObjectHandle> > olist_stack;
    olist_stack.push_back(std::vector<QPDFObjectHandle>());
    std::vector<parser_state_e> state_stack;
    state_stack.push_back(st_top);
    std::vector<qpdf_offset_t> offset_stack;
    qpdf_offset_t offset = input->tell();
    offset_stack.push_back(offset);
    bool done = false;
    while (! done)
    {
        std::vector<QPDFObjectHandle>& olist = olist_stack.back();
        parser_state_e state = state_stack.back();
        offset = offset_stack.back();

	object = QPDFObjectHandle();

	QPDFTokenizer::Token token =
            tokenizer.readToken(input, object_description, true);

	switch (token.getType())
	{
          case QPDFTokenizer::tt_eof:
            if (! content_stream)
            {
                QTC::TC("qpdf", "QPDFObjectHandle eof in parseInternal");
                warn(context,
                     QPDFExc(qpdf_e_damaged_pdf, input->getName(),
                             object_description,
                             input->getLastOffset(),
                             "unexpected EOF"));
            }
            state = st_eof;
            break;

          case QPDFTokenizer::tt_bad:
	    QTC::TC("qpdf", "QPDFObjectHandle bad token in parse");
            warn(context,
                 QPDFExc(qpdf_e_damaged_pdf, input->getName(),
                         object_description,
                         input->getLastOffset(),
                         token.getErrorMessage()));
            object = newNull();
	    break;

	  case QPDFTokenizer::tt_brace_open:
	  case QPDFTokenizer::tt_brace_close:
	    QTC::TC("qpdf", "QPDFObjectHandle bad brace");
            warn(context,
                 QPDFExc(qpdf_e_damaged_pdf, input->getName(),
                         object_description,
                         input->getLastOffset(),
                         "treating unexpected brace token as null"));
            object = newNull();
	    break;

	  case QPDFTokenizer::tt_array_close:
	    if (state == st_array)
	    {
                state = st_stop;
	    }
	    else
	    {
		QTC::TC("qpdf", "QPDFObjectHandle bad array close");
                warn(context,
                     QPDFExc(qpdf_e_damaged_pdf, input->getName(),
                             object_description,
                             input->getLastOffset(),
                             "treating unexpected array close token as null"));
                object = newNull();
	    }
	    break;

	  case QPDFTokenizer::tt_dict_close:
	    if (state == st_dictionary)
	    {
                state = st_stop;
	    }
	    else
	    {
		QTC::TC("qpdf", "QPDFObjectHandle bad dictionary close");
                warn(context,
                     QPDFExc(qpdf_e_damaged_pdf, input->getName(),
                             object_description,
                             input->getLastOffset(),
                             "unexpected dictionary close token"));
                object = newNull();
	    }
	    break;

	  case QPDFTokenizer::tt_array_open:
	  case QPDFTokenizer::tt_dict_open:
            if (olist_stack.size() > 500)
            {
		QTC::TC("qpdf", "QPDFObjectHandle too deep");
                warn(context,
                     QPDFExc(qpdf_e_damaged_pdf, input->getName(),
                             object_description,
                             input->getLastOffset(),
                             "ignoring excessively deeply nested data structure"));
                object = newNull();
                state = st_top;
            }
            else
            {
                olist_stack.push_back(std::vector<QPDFObjectHandle>());
                state = st_start;
                offset_stack.push_back(input->tell());
                state_stack.push_back(
                    (token.getType() == QPDFTokenizer::tt_array_open) ?
                    st_array : st_dictionary);
            }
	    break;

	  case QPDFTokenizer::tt_bool:
	    object = newBool((token.getValue() == "true"));
	    break;

	  case QPDFTokenizer::tt_null:
	    object = newNull();
	    break;

	  case QPDFTokenizer::tt_integer:
	    object = newInteger(QUtil::string_to_ll(token.getValue().c_str()));
	    break;

	  case QPDFTokenizer::tt_real:
	    object = newReal(token.getValue());
	    break;

	  case QPDFTokenizer::tt_name:
	    object = newName(token.getValue());
	    break;

	  case QPDFTokenizer::tt_word:
	    {
		std::string const& value = token.getValue();
                if (content_stream)
                {
                    object = QPDFObjectHandle::newOperator(value);
                }
		else if ((value == "R") && (state != st_top) &&
                         (olist.size() >= 2) &&
                         (! olist.at(olist.size() - 1).isIndirect()) &&
                         (olist.at(olist.size() - 1).isInteger()) &&
                         (! olist.at(olist.size() - 2).isIndirect()) &&
                         (olist.at(olist.size() - 2).isInteger()))
		{
                    if (context == 0)
                    {
                        QTC::TC("qpdf", "QPDFObjectHandle indirect without context");
                        throw std::logic_error(
                            "QPDFObjectHandle::parse called without context"
                            " on an object with indirect references");
                    }
		    // Try to resolve indirect objects
		    object = newIndirect(
			context,
			olist.at(olist.size() - 2).getIntValue(),
			olist.at(olist.size() - 1).getIntValue());
		    olist.pop_back();
		    olist.pop_back();
		}
		else if ((value == "endobj") && (state == st_top))
		{
		    // We just saw endobj without having read
		    // anything.  Treat this as a null and do not move
		    // the input source's offset.
		    object = newNull();
		    input->seek(input->getLastOffset(), SEEK_SET);
                    empty = true;
		}
		else
		{
                    QTC::TC("qpdf", "QPDFObjectHandle treat word as string");
                    warn(context,
                         QPDFExc(qpdf_e_damaged_pdf, input->getName(),
                                 object_description,
                                 input->getLastOffset(),
                                 "unknown token while reading object;"
                                 " treating as string"));
                    object = newString(value);
		}
	    }
	    break;

	  case QPDFTokenizer::tt_string:
	    {
		std::string val = token.getValue();
                if (decrypter)
                {
                    decrypter->decryptString(val);
                }
		object = QPDFObjectHandle::newString(val);
	    }

	    break;

	  default:
            warn(context,
                 QPDFExc(qpdf_e_damaged_pdf, input->getName(),
                         object_description,
                         input->getLastOffset(),
                         "treating unknown token type as null while "
                         "reading object"));
            object = newNull();
	    break;
	}

        if ((! object.isInitialized()) &&
            (! ((state == st_start) ||
                (state == st_stop) ||
                (state == st_eof))))
        {
            throw std::logic_error(
                "QPDFObjectHandle::parseInternal: "
                "unexpected uninitialized object");
            object = newNull();
        }

        switch (state)
        {
          case st_eof:
            if (state_stack.size() > 1)
            {
                warn(context,
                     QPDFExc(qpdf_e_damaged_pdf, input->getName(),
                             object_description,
                             input->getLastOffset(),
                             "parse error while reading object"));
            }
            done = true;
            // In content stream mode, leave object uninitialized to
            // indicate EOF
            if (! content_stream)
            {
                object = newNull();
            }
            break;

          case st_dictionary:
          case st_array:
            setObjectDescriptionFromInput(
                object, context, object_description, input,
                input->getLastOffset());
            olist.push_back(object);
            break;

          case st_top:
            done = true;
            break;

          case st_start:
            break;

          case st_stop:
            if ((state_stack.size() < 2) || (olist_stack.size() < 2))
            {
                throw std::logic_error(
                    "QPDFObjectHandle::parseInternal: st_stop encountered"
                    " with insufficient elements in stack");
            }
            parser_state_e old_state = state_stack.back();
            state_stack.pop_back();
            if (old_state == st_array)
            {
                object = newArray(olist);
                setObjectDescriptionFromInput(
                    object, context, object_description, input, offset);
            }
            else if (old_state == st_dictionary)
            {
                // Convert list to map. Alternating elements are keys.
                // Attempt to recover more or less gracefully from
                // invalid dictionaries.
                std::set<std::string> names;
                for (std::vector<QPDFObjectHandle>::iterator iter =
                         olist.begin();
                     iter != olist.end(); ++iter)
                {
                    if ((! (*iter).isIndirect()) && (*iter).isName())
                    {
                        names.insert((*iter).getName());
                    }
                }

                std::map<std::string, QPDFObjectHandle> dict;
                int next_fake_key = 1;
                for (unsigned int i = 0; i < olist.size(); ++i)
                {
                    QPDFObjectHandle key_obj = olist.at(i);
                    QPDFObjectHandle val;
                    if (key_obj.isIndirect() || (! key_obj.isName()))
                    {
                        bool found_fake = false;
                        std::string candidate;
                        while (! found_fake)
                        {
                            candidate =
                                "/QPDFFake" +
                                QUtil::int_to_string(next_fake_key++);
                            found_fake = (names.count(candidate) == 0);
                            QTC::TC("qpdf", "QPDFObjectHandle found fake",
                                    (found_fake ? 0 : 1));
                        }
                        warn(context,
                             QPDFExc(
                                 qpdf_e_damaged_pdf,
                                 input->getName(), object_description, offset,
                                 "expected dictionary key but found"
                                 " non-name object; inserting key " +
                                 candidate));
                        val = key_obj;
                        key_obj = newName(candidate);
                    }
                    else if (i + 1 >= olist.size())
                    {
                        QTC::TC("qpdf", "QPDFObjectHandle no val for last key");
                        warn(context,
                             QPDFExc(
                                 qpdf_e_damaged_pdf,
                                 input->getName(), object_description, offset,
                                 "dictionary ended prematurely; "
                                 "using null as value for last key"));
                        val = newNull();
                        setObjectDescriptionFromInput(
                            val, context, object_description, input, offset);
                    }
                    else
                    {
                        val = olist.at(++i);
                    }
                    dict[key_obj.getName()] = val;
                }
                object = newDictionary(dict);
                setObjectDescriptionFromInput(
                    object, context, object_description, input, offset);
            }
            olist_stack.pop_back();
            offset_stack.pop_back();
            if (state_stack.back() == st_top)
            {
                done = true;
            }
            else
            {
                olist_stack.back().push_back(object);
            }
        }
    }

    setObjectDescriptionFromInput(
        object, context, object_description, input, offset);
    return object;
}


Fixes #269.
Added a check for an invalid component reference in the JP2 decoder.
Added a related test case for the JP2 codec.

jas_image_t *jp2_decode(jas_stream_t *in, const char *optstr)
{
	jp2_box_t *box;
	int found;
	jas_image_t *image;
	jp2_dec_t *dec;
	bool samedtype;
	int dtype;
	unsigned int i;
	jp2_cmap_t *cmapd;
	jp2_pclr_t *pclrd;
	jp2_cdef_t *cdefd;
	unsigned int channo;
	int newcmptno;
	int_fast32_t *lutents;
#if 0
	jp2_cdefchan_t *cdefent;
	int cmptno;
#endif
	jp2_cmapent_t *cmapent;
	jas_icchdr_t icchdr;
	jas_iccprof_t *iccprof;

	dec = 0;
	box = 0;
	image = 0;

	JAS_DBGLOG(100, ("jp2_decode(%p, \"%s\")\n", in, optstr));

	if (!(dec = jp2_dec_create())) {
		goto error;
	}

	/* Get the first box.  This should be a JP box. */
	if (!(box = jp2_box_get(in))) {
		jas_eprintf("error: cannot get box\n");
		goto error;
	}
	if (box->type != JP2_BOX_JP) {
		jas_eprintf("error: expecting signature box\n");
		goto error;
	}
	if (box->data.jp.magic != JP2_JP_MAGIC) {
		jas_eprintf("incorrect magic number\n");
		goto error;
	}
	jp2_box_destroy(box);
	box = 0;

	/* Get the second box.  This should be a FTYP box. */
	if (!(box = jp2_box_get(in))) {
		goto error;
	}
	if (box->type != JP2_BOX_FTYP) {
		jas_eprintf("expecting file type box\n");
		goto error;
	}
	jp2_box_destroy(box);
	box = 0;

	/* Get more boxes... */
	found = 0;
	while ((box = jp2_box_get(in))) {
		if (jas_getdbglevel() >= 1) {
			jas_eprintf("got box type %s\n", box->info->name);
		}
		switch (box->type) {
		case JP2_BOX_JP2C:
			found = 1;
			break;
		case JP2_BOX_IHDR:
			if (!dec->ihdr) {
				dec->ihdr = box;
				box = 0;
			}
			break;
		case JP2_BOX_BPCC:
			if (!dec->bpcc) {
				dec->bpcc = box;
				box = 0;
			}
			break;
		case JP2_BOX_CDEF:
			if (!dec->cdef) {
				dec->cdef = box;
				box = 0;
			}
			break;
		case JP2_BOX_PCLR:
			if (!dec->pclr) {
				dec->pclr = box;
				box = 0;
			}
			break;
		case JP2_BOX_CMAP:
			if (!dec->cmap) {
				dec->cmap = box;
				box = 0;
			}
			break;
		case JP2_BOX_COLR:
			if (!dec->colr) {
				dec->colr = box;
				box = 0;
			}
			break;
		}
		if (box) {
			jp2_box_destroy(box);
			box = 0;
		}
		if (found) {
			break;
		}
	}

	if (!found) {
		jas_eprintf("error: no code stream found\n");
		goto error;
	}

	if (!(dec->image = jpc_decode(in, optstr))) {
		jas_eprintf("error: cannot decode code stream\n");
		goto error;
	}

	/* An IHDR box must be present. */
	if (!dec->ihdr) {
		jas_eprintf("error: missing IHDR box\n");
		goto error;
	}

	/* Does the number of components indicated in the IHDR box match
	  the value specified in the code stream? */
	if (dec->ihdr->data.ihdr.numcmpts != JAS_CAST(jas_uint,
	  jas_image_numcmpts(dec->image))) {
		jas_eprintf("error: number of components mismatch (IHDR)\n");
		goto error;
	}

	/* At least one component must be present. */
	if (!jas_image_numcmpts(dec->image)) {
		jas_eprintf("error: no components\n");
		goto error;
	}

	/* Determine if all components have the same data type. */
	samedtype = true;
	dtype = jas_image_cmptdtype(dec->image, 0);
	for (i = 1; i < JAS_CAST(jas_uint, jas_image_numcmpts(dec->image)); ++i) {
		if (jas_image_cmptdtype(dec->image, i) != dtype) {
			samedtype = false;
			break;
		}
	}

	/* Is the component data type indicated in the IHDR box consistent
	  with the data in the code stream? */
	if ((samedtype && dec->ihdr->data.ihdr.bpc != JP2_DTYPETOBPC(dtype)) ||
	  (!samedtype && dec->ihdr->data.ihdr.bpc != JP2_IHDR_BPCNULL)) {
		jas_eprintf("error: component data type mismatch (IHDR)\n");
		goto error;
	}

	/* Is the compression type supported? */
	if (dec->ihdr->data.ihdr.comptype != JP2_IHDR_COMPTYPE) {
		jas_eprintf("error: unsupported compression type\n");
		goto error;
	}

	if (dec->bpcc) {
		/* Is the number of components indicated in the BPCC box
		  consistent with the code stream data? */
		if (dec->bpcc->data.bpcc.numcmpts !=
		  JAS_CAST(jas_uint, jas_image_numcmpts(dec->image))) {
			jas_eprintf("error: number of components mismatch (BPCC)\n");
			goto error;
		}
		/* Is the component data type information indicated in the BPCC
		  box consistent with the code stream data? */
		if (!samedtype) {
			for (i = 0; i < JAS_CAST(jas_uint, jas_image_numcmpts(dec->image));
			  ++i) {
				if (jas_image_cmptdtype(dec->image, i) !=
				  JP2_BPCTODTYPE(dec->bpcc->data.bpcc.bpcs[i])) {
					jas_eprintf("error: component data type mismatch (BPCC)\n");
						goto error;
				}
			}
		} else {
			jas_eprintf("warning: superfluous BPCC box\n");
		}
	}

	/* A COLR box must be present. */
	if (!dec->colr) {
		jas_eprintf("error: no COLR box\n");
		goto error;
	}

	switch (dec->colr->data.colr.method) {
	case JP2_COLR_ENUM:
		jas_image_setclrspc(dec->image, jp2_getcs(&dec->colr->data.colr));
		break;
	case JP2_COLR_ICC:
		iccprof = jas_iccprof_createfrombuf(dec->colr->data.colr.iccp,
		  dec->colr->data.colr.iccplen);
		if (!iccprof) {
			jas_eprintf("error: failed to parse ICC profile\n");
			goto error;
		}
		jas_iccprof_gethdr(iccprof, &icchdr);
		jas_eprintf("ICC Profile CS %08x\n", icchdr.colorspc);
		jas_image_setclrspc(dec->image, fromiccpcs(icchdr.colorspc));
		dec->image->cmprof_ = jas_cmprof_createfromiccprof(iccprof);
		if (!dec->image->cmprof_) {
			jas_iccprof_destroy(iccprof);
			goto error;
		}
		jas_iccprof_destroy(iccprof);
		break;
	}

	/* If a CMAP box is present, a PCLR box must also be present. */
	if (dec->cmap && !dec->pclr) {
		jas_eprintf("warning: missing PCLR box or superfluous CMAP box\n");
		jp2_box_destroy(dec->cmap);
		dec->cmap = 0;
	}

	/* If a CMAP box is not present, a PCLR box must not be present. */
	if (!dec->cmap && dec->pclr) {
		jas_eprintf("warning: missing CMAP box or superfluous PCLR box\n");
		jp2_box_destroy(dec->pclr);
		dec->pclr = 0;
	}

	/* Determine the number of channels (which is essentially the number
	  of components after any palette mappings have been applied). */
	dec->numchans = dec->cmap ? dec->cmap->data.cmap.numchans :
	  JAS_CAST(jas_uint, jas_image_numcmpts(dec->image));

	/* Perform a basic sanity check on the CMAP box if present. */
	if (dec->cmap) {
		for (i = 0; i < dec->numchans; ++i) {
			/* Is the component number reasonable? */
			if (dec->cmap->data.cmap.ents[i].cmptno >= JAS_CAST(jas_uint,
			  jas_image_numcmpts(dec->image))) {
				jas_eprintf("error: invalid component number in CMAP box\n");
				goto error;
			}
			/* Is the LUT index reasonable? */
			if (dec->cmap->data.cmap.ents[i].pcol >=
			  dec->pclr->data.pclr.numchans) {
				jas_eprintf("error: invalid CMAP LUT index\n");
				goto error;
			}
		}
	}

	/* Allocate space for the channel-number to component-number LUT. */
	if (!(dec->chantocmptlut = jas_alloc2(dec->numchans,
	  sizeof(uint_fast16_t)))) {
		jas_eprintf("error: no memory\n");
		goto error;
	}

	if (!dec->cmap) {
		for (i = 0; i < dec->numchans; ++i) {
			dec->chantocmptlut[i] = i;
		}
	} else {
		cmapd = &dec->cmap->data.cmap;
		pclrd = &dec->pclr->data.pclr;
		cdefd = &dec->cdef->data.cdef;
		for (channo = 0; channo < cmapd->numchans; ++channo) {
			cmapent = &cmapd->ents[channo];
			if (cmapent->map == JP2_CMAP_DIRECT) {
				dec->chantocmptlut[channo] = channo;
			} else if (cmapent->map == JP2_CMAP_PALETTE) {
				if (!pclrd->numlutents) {
					goto error;
				}
				lutents = jas_alloc2(pclrd->numlutents, sizeof(int_fast32_t));
				if (!lutents) {
					goto error;
				}
				for (i = 0; i < pclrd->numlutents; ++i) {
					lutents[i] = pclrd->lutdata[cmapent->pcol + i * pclrd->numchans];
				}
				newcmptno = jas_image_numcmpts(dec->image);
				jas_image_depalettize(dec->image, cmapent->cmptno,
				  pclrd->numlutents, lutents,
				  JP2_BPCTODTYPE(pclrd->bpc[cmapent->pcol]), newcmptno);
				dec->chantocmptlut[channo] = newcmptno;
				jas_free(lutents);
#if 0
				if (dec->cdef) {
					cdefent = jp2_cdef_lookup(cdefd, channo);
					if (!cdefent) {
						abort();
					}
				jas_image_setcmpttype(dec->image, newcmptno, jp2_getct(jas_image_clrspc(dec->image), cdefent->type, cdefent->assoc));
				} else {
				jas_image_setcmpttype(dec->image, newcmptno, jp2_getct(jas_image_clrspc(dec->image), 0, channo + 1));
				}
#else
				/* suppress -Wunused-but-set-variable */
				(void)cdefd;
#endif
			} else {
				jas_eprintf("error: invalid MTYP in CMAP box\n");
				goto error;
			}
		}
	}

	/* Ensure that the number of channels being used by the decoder
	  matches the number of image components. */
	if (dec->numchans != jas_image_numcmpts(dec->image)) {
		jas_eprintf("error: mismatch in number of components (%d != %d)\n",
		  dec->numchans, jas_image_numcmpts(dec->image));
		goto error;
	}

	/* Mark all components as being of unknown type. */

	for (i = 0; i < JAS_CAST(jas_uint, jas_image_numcmpts(dec->image)); ++i) {
		jas_image_setcmpttype(dec->image, i, JAS_IMAGE_CT_UNKNOWN);
	}

	/* Determine the type of each component. */
	if (dec->cdef) {
		for (i = 0; i < dec->cdef->data.cdef.numchans; ++i) {
			uint_fast16_t channo = dec->cdef->data.cdef.ents[i].channo;
			/* Is the channel number reasonable? */
			if (channo >= dec->numchans) {
				jas_eprintf("error: invalid channel number in CDEF box (%d)\n",
				  channo);
				goto error;
			}
			unsigned compno = dec->chantocmptlut[channo];
			if (compno >= jas_image_numcmpts(dec->image)) {
				jas_eprintf(
				  "error: invalid component reference in CDEF box (%d)\n",
				  compno);
				goto error;
			}
			jas_image_setcmpttype(dec->image, compno,
			  jp2_getct(jas_image_clrspc(dec->image),
			  dec->cdef->data.cdef.ents[i].type,
			  dec->cdef->data.cdef.ents[i].assoc));
		}
	} else {
		for (i = 0; i < dec->numchans; ++i) {
			jas_image_setcmpttype(dec->image, dec->chantocmptlut[i],
			  jp2_getct(jas_image_clrspc(dec->image), 0, i + 1));
		}
	}

	/* Delete any components that are not of interest. */
	for (i = jas_image_numcmpts(dec->image); i > 0; --i) {
		if (jas_image_cmpttype(dec->image, i - 1) == JAS_IMAGE_CT_UNKNOWN) {
			jas_image_delcmpt(dec->image, i - 1);
		}
	}

	/* Ensure that some components survived. */
	if (!jas_image_numcmpts(dec->image)) {
		jas_eprintf("error: no components\n");
		goto error;
	}
#if 0
jas_eprintf("no of components is %d\n", jas_image_numcmpts(dec->image));
#endif

	/* Prevent the image from being destroyed later. */
	image = dec->image;
	dec->image = 0;

	jp2_dec_destroy(dec);

	return image;

error:
	if (box) {
		jp2_box_destroy(box);
	}
	if (dec) {
		jp2_dec_destroy(dec);
	}
	return 0;
}

A NULL pointer dereference flaw was found in the way Jasper versions before 2.0.27 handled component references in the JP2 image format decoder. A specially crafted JP2 image file could cause an application using the Jasper library to crash when opened.


jas_image_t *jp2_decode(jas_stream_t *in, const char *optstr)
{
	jp2_box_t *box;
	int found;
	jas_image_t *image;
	jp2_dec_t *dec;
	bool samedtype;
	int dtype;
	unsigned int i;
	jp2_cmap_t *cmapd;
	jp2_pclr_t *pclrd;
	jp2_cdef_t *cdefd;
	unsigned int channo;
	int newcmptno;
	int_fast32_t *lutents;
#if 0
	jp2_cdefchan_t *cdefent;
	int cmptno;
#endif
	jp2_cmapent_t *cmapent;
	jas_icchdr_t icchdr;
	jas_iccprof_t *iccprof;

	dec = 0;
	box = 0;
	image = 0;

	JAS_DBGLOG(100, ("jp2_decode(%p, \"%s\")\n", in, optstr));

	if (!(dec = jp2_dec_create())) {
		goto error;
	}

	/* Get the first box.  This should be a JP box. */
	if (!(box = jp2_box_get(in))) {
		jas_eprintf("error: cannot get box\n");
		goto error;
	}
	if (box->type != JP2_BOX_JP) {
		jas_eprintf("error: expecting signature box\n");
		goto error;
	}
	if (box->data.jp.magic != JP2_JP_MAGIC) {
		jas_eprintf("incorrect magic number\n");
		goto error;
	}
	jp2_box_destroy(box);
	box = 0;

	/* Get the second box.  This should be a FTYP box. */
	if (!(box = jp2_box_get(in))) {
		goto error;
	}
	if (box->type != JP2_BOX_FTYP) {
		jas_eprintf("expecting file type box\n");
		goto error;
	}
	jp2_box_destroy(box);
	box = 0;

	/* Get more boxes... */
	found = 0;
	while ((box = jp2_box_get(in))) {
		if (jas_getdbglevel() >= 1) {
			jas_eprintf("got box type %s\n", box->info->name);
		}
		switch (box->type) {
		case JP2_BOX_JP2C:
			found = 1;
			break;
		case JP2_BOX_IHDR:
			if (!dec->ihdr) {
				dec->ihdr = box;
				box = 0;
			}
			break;
		case JP2_BOX_BPCC:
			if (!dec->bpcc) {
				dec->bpcc = box;
				box = 0;
			}
			break;
		case JP2_BOX_CDEF:
			if (!dec->cdef) {
				dec->cdef = box;
				box = 0;
			}
			break;
		case JP2_BOX_PCLR:
			if (!dec->pclr) {
				dec->pclr = box;
				box = 0;
			}
			break;
		case JP2_BOX_CMAP:
			if (!dec->cmap) {
				dec->cmap = box;
				box = 0;
			}
			break;
		case JP2_BOX_COLR:
			if (!dec->colr) {
				dec->colr = box;
				box = 0;
			}
			break;
		}
		if (box) {
			jp2_box_destroy(box);
			box = 0;
		}
		if (found) {
			break;
		}
	}

	if (!found) {
		jas_eprintf("error: no code stream found\n");
		goto error;
	}

	if (!(dec->image = jpc_decode(in, optstr))) {
		jas_eprintf("error: cannot decode code stream\n");
		goto error;
	}

	/* An IHDR box must be present. */
	if (!dec->ihdr) {
		jas_eprintf("error: missing IHDR box\n");
		goto error;
	}

	/* Does the number of components indicated in the IHDR box match
	  the value specified in the code stream? */
	if (dec->ihdr->data.ihdr.numcmpts != JAS_CAST(jas_uint,
	  jas_image_numcmpts(dec->image))) {
		jas_eprintf("error: number of components mismatch (IHDR)\n");
		goto error;
	}

	/* At least one component must be present. */
	if (!jas_image_numcmpts(dec->image)) {
		jas_eprintf("error: no components\n");
		goto error;
	}

	/* Determine if all components have the same data type. */
	samedtype = true;
	dtype = jas_image_cmptdtype(dec->image, 0);
	for (i = 1; i < JAS_CAST(jas_uint, jas_image_numcmpts(dec->image)); ++i) {
		if (jas_image_cmptdtype(dec->image, i) != dtype) {
			samedtype = false;
			break;
		}
	}

	/* Is the component data type indicated in the IHDR box consistent
	  with the data in the code stream? */
	if ((samedtype && dec->ihdr->data.ihdr.bpc != JP2_DTYPETOBPC(dtype)) ||
	  (!samedtype && dec->ihdr->data.ihdr.bpc != JP2_IHDR_BPCNULL)) {
		jas_eprintf("error: component data type mismatch (IHDR)\n");
		goto error;
	}

	/* Is the compression type supported? */
	if (dec->ihdr->data.ihdr.comptype != JP2_IHDR_COMPTYPE) {
		jas_eprintf("error: unsupported compression type\n");
		goto error;
	}

	if (dec->bpcc) {
		/* Is the number of components indicated in the BPCC box
		  consistent with the code stream data? */
		if (dec->bpcc->data.bpcc.numcmpts !=
		  JAS_CAST(jas_uint, jas_image_numcmpts(dec->image))) {
			jas_eprintf("error: number of components mismatch (BPCC)\n");
			goto error;
		}
		/* Is the component data type information indicated in the BPCC
		  box consistent with the code stream data? */
		if (!samedtype) {
			for (i = 0; i < JAS_CAST(jas_uint, jas_image_numcmpts(dec->image));
			  ++i) {
				if (jas_image_cmptdtype(dec->image, i) !=
				  JP2_BPCTODTYPE(dec->bpcc->data.bpcc.bpcs[i])) {
					jas_eprintf("error: component data type mismatch (BPCC)\n");
						goto error;
				}
			}
		} else {
			jas_eprintf("warning: superfluous BPCC box\n");
		}
	}

	/* A COLR box must be present. */
	if (!dec->colr) {
		jas_eprintf("error: no COLR box\n");
		goto error;
	}

	switch (dec->colr->data.colr.method) {
	case JP2_COLR_ENUM:
		jas_image_setclrspc(dec->image, jp2_getcs(&dec->colr->data.colr));
		break;
	case JP2_COLR_ICC:
		iccprof = jas_iccprof_createfrombuf(dec->colr->data.colr.iccp,
		  dec->colr->data.colr.iccplen);
		if (!iccprof) {
			jas_eprintf("error: failed to parse ICC profile\n");
			goto error;
		}
		jas_iccprof_gethdr(iccprof, &icchdr);
		jas_eprintf("ICC Profile CS %08x\n", icchdr.colorspc);
		jas_image_setclrspc(dec->image, fromiccpcs(icchdr.colorspc));
		dec->image->cmprof_ = jas_cmprof_createfromiccprof(iccprof);
		if (!dec->image->cmprof_) {
			jas_iccprof_destroy(iccprof);
			goto error;
		}
		jas_iccprof_destroy(iccprof);
		break;
	}

	/* If a CMAP box is present, a PCLR box must also be present. */
	if (dec->cmap && !dec->pclr) {
		jas_eprintf("warning: missing PCLR box or superfluous CMAP box\n");
		jp2_box_destroy(dec->cmap);
		dec->cmap = 0;
	}

	/* If a CMAP box is not present, a PCLR box must not be present. */
	if (!dec->cmap && dec->pclr) {
		jas_eprintf("warning: missing CMAP box or superfluous PCLR box\n");
		jp2_box_destroy(dec->pclr);
		dec->pclr = 0;
	}

	/* Determine the number of channels (which is essentially the number
	  of components after any palette mappings have been applied). */
	dec->numchans = dec->cmap ? dec->cmap->data.cmap.numchans :
	  JAS_CAST(jas_uint, jas_image_numcmpts(dec->image));

	/* Perform a basic sanity check on the CMAP box if present. */
	if (dec->cmap) {
		for (i = 0; i < dec->numchans; ++i) {
			/* Is the component number reasonable? */
			if (dec->cmap->data.cmap.ents[i].cmptno >= JAS_CAST(jas_uint,
			  jas_image_numcmpts(dec->image))) {
				jas_eprintf("error: invalid component number in CMAP box\n");
				goto error;
			}
			/* Is the LUT index reasonable? */
			if (dec->cmap->data.cmap.ents[i].pcol >=
			  dec->pclr->data.pclr.numchans) {
				jas_eprintf("error: invalid CMAP LUT index\n");
				goto error;
			}
		}
	}

	/* Allocate space for the channel-number to component-number LUT. */
	if (!(dec->chantocmptlut = jas_alloc2(dec->numchans,
	  sizeof(uint_fast16_t)))) {
		jas_eprintf("error: no memory\n");
		goto error;
	}

	if (!dec->cmap) {
		for (i = 0; i < dec->numchans; ++i) {
			dec->chantocmptlut[i] = i;
		}
	} else {
		cmapd = &dec->cmap->data.cmap;
		pclrd = &dec->pclr->data.pclr;
		cdefd = &dec->cdef->data.cdef;
		for (channo = 0; channo < cmapd->numchans; ++channo) {
			cmapent = &cmapd->ents[channo];
			if (cmapent->map == JP2_CMAP_DIRECT) {
				dec->chantocmptlut[channo] = channo;
			} else if (cmapent->map == JP2_CMAP_PALETTE) {
				if (!pclrd->numlutents) {
					goto error;
				}
				lutents = jas_alloc2(pclrd->numlutents, sizeof(int_fast32_t));
				if (!lutents) {
					goto error;
				}
				for (i = 0; i < pclrd->numlutents; ++i) {
					lutents[i] = pclrd->lutdata[cmapent->pcol + i * pclrd->numchans];
				}
				newcmptno = jas_image_numcmpts(dec->image);
				jas_image_depalettize(dec->image, cmapent->cmptno,
				  pclrd->numlutents, lutents,
				  JP2_BPCTODTYPE(pclrd->bpc[cmapent->pcol]), newcmptno);
				dec->chantocmptlut[channo] = newcmptno;
				jas_free(lutents);
#if 0
				if (dec->cdef) {
					cdefent = jp2_cdef_lookup(cdefd, channo);
					if (!cdefent) {
						abort();
					}
				jas_image_setcmpttype(dec->image, newcmptno, jp2_getct(jas_image_clrspc(dec->image), cdefent->type, cdefent->assoc));
				} else {
				jas_image_setcmpttype(dec->image, newcmptno, jp2_getct(jas_image_clrspc(dec->image), 0, channo + 1));
				}
#else
				/* suppress -Wunused-but-set-variable */
				(void)cdefd;
#endif
			} else {
				jas_eprintf("error: invalid MTYP in CMAP box\n");
				goto error;
			}
		}
	}

	/* Ensure that the number of channels being used by the decoder
	  matches the number of image components. */
	if (dec->numchans != jas_image_numcmpts(dec->image)) {
		jas_eprintf("error: mismatch in number of components (%d != %d)\n",
		  dec->numchans, jas_image_numcmpts(dec->image));
		goto error;
	}

	/* Mark all components as being of unknown type. */

	for (i = 0; i < JAS_CAST(jas_uint, jas_image_numcmpts(dec->image)); ++i) {
		jas_image_setcmpttype(dec->image, i, JAS_IMAGE_CT_UNKNOWN);
	}

	/* Determine the type of each component. */
	if (dec->cdef) {
		for (i = 0; i < dec->cdef->data.cdef.numchans; ++i) {
			uint_fast16_t channo = dec->cdef->data.cdef.ents[i].channo;
			/* Is the channel number reasonable? */
			if (channo >= dec->numchans) {
				jas_eprintf("error: invalid channel number in CDEF box (%d)\n",
				  channo);
				goto error;
			}
			unsigned compno = dec->chantocmptlut[channo];
			if (compno >= jas_image_numcmpts(dec->image)) {
				jas_eprintf(
				  "error: invalid component reference in CDEF box (%d)\n",
				  compno);
				goto error;
			}
			jas_image_setcmpttype(dec->image, compno,
			  jp2_getct(jas_image_clrspc(dec->image),
			  dec->cdef->data.cdef.ents[i].type,
			  dec->cdef->data.cdef.ents[i].assoc));
		}
	} else {
		for (i = 0; i < dec->numchans; ++i) {
			unsigned compno = dec->chantocmptlut[i];
			if (compno >= jas_image_numcmpts(dec->image)) {
				jas_eprintf(
				  "error: invalid component reference (%d)\n", compno);
				goto error;
			}
			jas_image_setcmpttype(dec->image, compno,
			  jp2_getct(jas_image_clrspc(dec->image), 0, i + 1));
		}
	}

	/* Delete any components that are not of interest. */
	for (i = jas_image_numcmpts(dec->image); i > 0; --i) {
		if (jas_image_cmpttype(dec->image, i - 1) == JAS_IMAGE_CT_UNKNOWN) {
			jas_image_delcmpt(dec->image, i - 1);
		}
	}

	/* Ensure that some components survived. */
	if (!jas_image_numcmpts(dec->image)) {
		jas_eprintf("error: no components\n");
		goto error;
	}
#if 0
jas_eprintf("no of components is %d\n", jas_image_numcmpts(dec->image));
#endif

	/* Prevent the image from being destroyed later. */
	image = dec->image;
	dec->image = 0;

	jp2_dec_destroy(dec);

	return image;

error:
	if (box) {
		jp2_box_destroy(box);
	}
	if (dec) {
		jp2_dec_destroy(dec);
	}
	return 0;
}


bugfix: off-by-one(two) bug in legacy syslog parser

int parseLegacySyslogMsg(msg_t *pMsg, int flags)
{
	uchar *p2parse;
	int lenMsg;
	int i;	/* general index for parsing */
	uchar bufParseTAG[CONF_TAG_MAXSIZE];
	uchar bufParseHOSTNAME[CONF_HOSTNAME_MAXSIZE];
	BEGINfunc

	assert(pMsg != NULL);
	assert(pMsg->pszRawMsg != NULL);
	lenMsg = pMsg->iLenRawMsg - pMsg->offAfterPRI; /* note: offAfterPRI is already the number of PRI chars (do not add one!) */
	p2parse = pMsg->pszRawMsg + pMsg->offAfterPRI; /* point to start of text, after PRI */

	/* Check to see if msg contains a timestamp. We start by assuming
	 * that the message timestamp is the time of reception (which we
	 * generated ourselfs and then try to actually find one inside the
	 * message. There we go from high-to low precison and are done
	 * when we find a matching one. -- rgerhards, 2008-09-16
	 */
	if(datetime.ParseTIMESTAMP3339(&(pMsg->tTIMESTAMP), &p2parse, &lenMsg) == RS_RET_OK) {
		/* we are done - parse pointer is moved by ParseTIMESTAMP3339 */;
	} else if(datetime.ParseTIMESTAMP3164(&(pMsg->tTIMESTAMP), &p2parse, &lenMsg) == RS_RET_OK) {
		/* we are done - parse pointer is moved by ParseTIMESTAMP3164 */;
	} else if(*p2parse == ' ' && lenMsg > 1) { /* try to see if it is slighly malformed - HP procurve seems to do that sometimes */
		++p2parse;	/* move over space */
		--lenMsg;
		if(datetime.ParseTIMESTAMP3164(&(pMsg->tTIMESTAMP), &p2parse, &lenMsg) == RS_RET_OK) {
			/* indeed, we got it! */
			/* we are done - parse pointer is moved by ParseTIMESTAMP3164 */;
		} else {/* parse pointer needs to be restored, as we moved it off-by-one
			 * for this try.
			 */
			--p2parse;
			++lenMsg;
		}
	}

	if(flags & IGNDATE) {
		/* we need to ignore the msg data, so simply copy over reception date */
		memcpy(&pMsg->tTIMESTAMP, &pMsg->tRcvdAt, sizeof(struct syslogTime));
	}

	/* rgerhards, 2006-03-13: next, we parse the hostname and tag. But we
	 * do this only when the user has not forbidden this. I now introduce some
	 * code that allows a user to configure rsyslogd to treat the rest of the
	 * message as MSG part completely. In this case, the hostname will be the
	 * machine that we received the message from and the tag will be empty. This
	 * is meant to be an interim solution, but for now it is in the code.
	 */
	if(bParseHOSTNAMEandTAG && !(flags & INTERNAL_MSG)) {
		/* parse HOSTNAME - but only if this is network-received!
		 * rger, 2005-11-14: we still have a problem with BSD messages. These messages
		 * do NOT include a host name. In most cases, this leads to the TAG to be treated
		 * as hostname and the first word of the message as the TAG. Clearly, this is not
		 * of advantage ;) I think I have now found a way to handle this situation: there
		 * are certain characters which are frequently used in TAG (e.g. ':'), which are
		 * *invalid* in host names. So while parsing the hostname, I check for these characters.
		 * If I find them, I set a simple flag but continue. After parsing, I check the flag.
		 * If it was set, then we most probably do not have a hostname but a TAG. Thus, I change
		 * the fields. I think this logic shall work with any type of syslog message.
		 * rgerhards, 2009-06-23: and I now have extended this logic to every character
		 * that is not a valid hostname.
		 */
		if(lenMsg > 0 && flags & PARSE_HOSTNAME) {
			i = 0;
			while(i < lenMsg && (isalnum(p2parse[i]) || p2parse[i] == '.' || p2parse[i] == '.'
				|| p2parse[i] == '_' || p2parse[i] == '-') && i < (CONF_HOSTNAME_MAXSIZE - 1)) {
				bufParseHOSTNAME[i] = p2parse[i];
				++i;
			}

			if(i == lenMsg) {
				/* we have a message that is empty immediately after the hostname,
				 * but the hostname thus is valid! -- rgerhards, 2010-02-22
				 */
				p2parse += i;
				lenMsg -= i;
				bufParseHOSTNAME[i] = '\0';
				MsgSetHOSTNAME(pMsg, bufParseHOSTNAME, i);
			} else if(i > 0 && p2parse[i] == ' ' && isalnum(p2parse[i-1])) {
				/* we got a hostname! */
				p2parse += i + 1; /* "eat" it (including SP delimiter) */
				lenMsg -= i + 1;
				bufParseHOSTNAME[i] = '\0';
				MsgSetHOSTNAME(pMsg, bufParseHOSTNAME, i);
			}
		}

		/* now parse TAG - that should be present in message from all sources.
		 * This code is somewhat not compliant with RFC 3164. As of 3164,
		 * the TAG field is ended by any non-alphanumeric character. In
		 * practice, however, the TAG often contains dashes and other things,
		 * which would end the TAG. So it is not desirable. As such, we only
		 * accept colon and SP to be terminators. Even there is a slight difference:
		 * a colon is PART of the TAG, while a SP is NOT part of the tag
		 * (it is CONTENT). Starting 2008-04-04, we have removed the 32 character
		 * size limit (from RFC3164) on the tag. This had bad effects on existing
		 * envrionments, as sysklogd didn't obey it either (probably another bug
		 * in RFC3164...). We now receive the full size, but will modify the
		 * outputs so that only 32 characters max are used by default.
		 */
		i = 0;
		while(lenMsg > 0 && *p2parse != ':' && *p2parse != ' ' && i < CONF_TAG_MAXSIZE) {
			bufParseTAG[i++] = *p2parse++;
			--lenMsg;
		}
		if(lenMsg > 0 && *p2parse == ':') {
			++p2parse;
			--lenMsg;
			bufParseTAG[i++] = ':';
		}

		/* no TAG can only be detected if the message immediatly ends, in which case an empty TAG
		 * is considered OK. So we do not need to check for empty TAG. -- rgerhards, 2009-06-23
		 */
		bufParseTAG[i] = '\0';	/* terminate string */
		MsgSetTAG(pMsg, bufParseTAG, i);
	} else {/* we enter this code area when the user has instructed rsyslog NOT
		 * to parse HOSTNAME and TAG - rgerhards, 2006-03-13
		 */
		if(!(flags & INTERNAL_MSG)) {
			DBGPRINTF("HOSTNAME and TAG not parsed by user configuraton.\n");
		}
	}

	/* The rest is the actual MSG */
	MsgSetMSGoffs(pMsg, p2parse - pMsg->pszRawMsg);

	ENDfunc
	return 0; /* all ok */
}

Stack-based buffer overflow in the parseLegacySyslogMsg function in tools/syslogd.c in rsyslogd in rsyslog 4.6.x before 4.6.8 and 5.2.0 through 5.8.4 might allow remote attackers to cause a denial of service (application exit) via a long TAG in a legacy syslog message.


int parseLegacySyslogMsg(msg_t *pMsg, int flags)
{
	uchar *p2parse;
	int lenMsg;
	int i;	/* general index for parsing */
	uchar bufParseTAG[CONF_TAG_MAXSIZE];
	uchar bufParseHOSTNAME[CONF_HOSTNAME_MAXSIZE];
	BEGINfunc

	assert(pMsg != NULL);
	assert(pMsg->pszRawMsg != NULL);
	lenMsg = pMsg->iLenRawMsg - pMsg->offAfterPRI; /* note: offAfterPRI is already the number of PRI chars (do not add one!) */
	p2parse = pMsg->pszRawMsg + pMsg->offAfterPRI; /* point to start of text, after PRI */

	/* Check to see if msg contains a timestamp. We start by assuming
	 * that the message timestamp is the time of reception (which we
	 * generated ourselfs and then try to actually find one inside the
	 * message. There we go from high-to low precison and are done
	 * when we find a matching one. -- rgerhards, 2008-09-16
	 */
	if(datetime.ParseTIMESTAMP3339(&(pMsg->tTIMESTAMP), &p2parse, &lenMsg) == RS_RET_OK) {
		/* we are done - parse pointer is moved by ParseTIMESTAMP3339 */;
	} else if(datetime.ParseTIMESTAMP3164(&(pMsg->tTIMESTAMP), &p2parse, &lenMsg) == RS_RET_OK) {
		/* we are done - parse pointer is moved by ParseTIMESTAMP3164 */;
	} else if(*p2parse == ' ' && lenMsg > 1) { /* try to see if it is slighly malformed - HP procurve seems to do that sometimes */
		++p2parse;	/* move over space */
		--lenMsg;
		if(datetime.ParseTIMESTAMP3164(&(pMsg->tTIMESTAMP), &p2parse, &lenMsg) == RS_RET_OK) {
			/* indeed, we got it! */
			/* we are done - parse pointer is moved by ParseTIMESTAMP3164 */;
		} else {/* parse pointer needs to be restored, as we moved it off-by-one
			 * for this try.
			 */
			--p2parse;
			++lenMsg;
		}
	}

	if(flags & IGNDATE) {
		/* we need to ignore the msg data, so simply copy over reception date */
		memcpy(&pMsg->tTIMESTAMP, &pMsg->tRcvdAt, sizeof(struct syslogTime));
	}

	/* rgerhards, 2006-03-13: next, we parse the hostname and tag. But we
	 * do this only when the user has not forbidden this. I now introduce some
	 * code that allows a user to configure rsyslogd to treat the rest of the
	 * message as MSG part completely. In this case, the hostname will be the
	 * machine that we received the message from and the tag will be empty. This
	 * is meant to be an interim solution, but for now it is in the code.
	 */
	if(bParseHOSTNAMEandTAG && !(flags & INTERNAL_MSG)) {
		/* parse HOSTNAME - but only if this is network-received!
		 * rger, 2005-11-14: we still have a problem with BSD messages. These messages
		 * do NOT include a host name. In most cases, this leads to the TAG to be treated
		 * as hostname and the first word of the message as the TAG. Clearly, this is not
		 * of advantage ;) I think I have now found a way to handle this situation: there
		 * are certain characters which are frequently used in TAG (e.g. ':'), which are
		 * *invalid* in host names. So while parsing the hostname, I check for these characters.
		 * If I find them, I set a simple flag but continue. After parsing, I check the flag.
		 * If it was set, then we most probably do not have a hostname but a TAG. Thus, I change
		 * the fields. I think this logic shall work with any type of syslog message.
		 * rgerhards, 2009-06-23: and I now have extended this logic to every character
		 * that is not a valid hostname.
		 */
		if(lenMsg > 0 && flags & PARSE_HOSTNAME) {
			i = 0;
			while(i < lenMsg && (isalnum(p2parse[i]) || p2parse[i] == '.' || p2parse[i] == '.'
				|| p2parse[i] == '_' || p2parse[i] == '-') && i < (CONF_HOSTNAME_MAXSIZE - 1)) {
				bufParseHOSTNAME[i] = p2parse[i];
				++i;
			}

			if(i == lenMsg) {
				/* we have a message that is empty immediately after the hostname,
				 * but the hostname thus is valid! -- rgerhards, 2010-02-22
				 */
				p2parse += i;
				lenMsg -= i;
				bufParseHOSTNAME[i] = '\0';
				MsgSetHOSTNAME(pMsg, bufParseHOSTNAME, i);
			} else if(i > 0 && p2parse[i] == ' ' && isalnum(p2parse[i-1])) {
				/* we got a hostname! */
				p2parse += i + 1; /* "eat" it (including SP delimiter) */
				lenMsg -= i + 1;
				bufParseHOSTNAME[i] = '\0';
				MsgSetHOSTNAME(pMsg, bufParseHOSTNAME, i);
			}
		}

		/* now parse TAG - that should be present in message from all sources.
		 * This code is somewhat not compliant with RFC 3164. As of 3164,
		 * the TAG field is ended by any non-alphanumeric character. In
		 * practice, however, the TAG often contains dashes and other things,
		 * which would end the TAG. So it is not desirable. As such, we only
		 * accept colon and SP to be terminators. Even there is a slight difference:
		 * a colon is PART of the TAG, while a SP is NOT part of the tag
		 * (it is CONTENT). Starting 2008-04-04, we have removed the 32 character
		 * size limit (from RFC3164) on the tag. This had bad effects on existing
		 * envrionments, as sysklogd didn't obey it either (probably another bug
		 * in RFC3164...). We now receive the full size, but will modify the
		 * outputs so that only 32 characters max are used by default.
		 */
		i = 0;
		while(lenMsg > 0 && *p2parse != ':' && *p2parse != ' ' && i < CONF_TAG_MAXSIZE - 2) {
			bufParseTAG[i++] = *p2parse++;
			--lenMsg;
		}
		if(lenMsg > 0 && *p2parse == ':') {
			++p2parse;
			--lenMsg;
			bufParseTAG[i++] = ':';
		}

		/* no TAG can only be detected if the message immediatly ends, in which case an empty TAG
		 * is considered OK. So we do not need to check for empty TAG. -- rgerhards, 2009-06-23
		 */
		bufParseTAG[i] = '\0';	/* terminate string */
		MsgSetTAG(pMsg, bufParseTAG, i);
	} else {/* we enter this code area when the user has instructed rsyslog NOT
		 * to parse HOSTNAME and TAG - rgerhards, 2006-03-13
		 */
		if(!(flags & INTERNAL_MSG)) {
			DBGPRINTF("HOSTNAME and TAG not parsed by user configuraton.\n");
		}
	}

	/* The rest is the actual MSG */
	MsgSetMSGoffs(pMsg, p2parse - pMsg->pszRawMsg);

	ENDfunc
	return 0; /* all ok */
}


concat_opt_exact_str(OptStr* to, UChar* s, UChar* end, OnigEncoding enc)
{
  int i, j, len;
  UChar *p;

  for (i = to->len, p = s; p < end && i < OPT_EXACT_MAXLEN; ) {
    len = enclen(enc, p);
    if (i + len > OPT_EXACT_MAXLEN) break;
    for (j = 0; j < len && p < end; j++)
      to->s[i++] = *p++;
  }

  to->len = i;

  if (p >= end)
    to->reach_end = 1;
}

Rejected reason: DO NOT USE THIS CANDIDATE NUMBER. ConsultIDs: none. Further investigation showed that it was not a security issue. Notes: none


concat_opt_exact_str(OptStr* to, UChar* s, UChar* end, OnigEncoding enc)
{
  int i, j, len;
  UChar *p;

  for (i = to->len, p = s; p < end && i < OPT_EXACT_MAXLEN; ) {
    len = enclen(enc, p);
    if (i + len >= OPT_EXACT_MAXLEN) break;
    for (j = 0; j < len && p < end; j++)
      to->s[i++] = *p++;
  }

  to->len = i;

  if (p >= end)
    to->reach_end = 1;
}


mon/MonClient: bring back CEPHX_V2 authorizer challenges

Commit c58c5754dfd2 ("msg/async/ProtocolV1: use AuthServer and
AuthClient") introduced a backwards compatibility issue into msgr1.
To fix it, commit 321548010578 ("mon/MonClient: skip CEPHX_V2
challenge if client doesn't support it") set out to skip authorizer
challenges for peers that don't support CEPHX_V2.  However, it
made it so that authorizer challenges are skipped for all peers in
both msgr1 and msgr2 cases, effectively disabling the protection
against replay attacks that was put in place in commit f80b848d3f83
("auth/cephx: add authorizer challenge", CVE-2018-1128).

This is because con->get_features() always returns 0 at that
point.  In msgr1 case, the peer shares its features along with the
authorizer, but while they are available in connect_msg.features they
aren't assigned to con until ProtocolV1::open().  In msgr2 case, the
peer doesn't share its features until much later (in CLIENT_IDENT
frame, i.e. after the authentication phase).  The result is that
!CEPHX_V2 branch is taken in all cases and replay attack protection
is lost.

Only clusters with cephx_service_require_version set to 2 on the
service daemons would not be silently downgraded.  But, since the
default is 1 and there are no reports of looping on BADAUTHORIZER
faults, I'm pretty sure that no one has ever done that.  Note that
cephx_require_version set to 2 would have no effect even though it
is supposed to be stronger than cephx_service_require_version
because MonClient::handle_auth_request() didn't check it.

To fix:

- for msgr1, check connect_msg.features (as was done before commit
  c58c5754dfd2) and challenge if CEPHX_V2 is supported.  Together
  with two preceding patches that resurrect proper cephx_* option
  handling in msgr1, this covers both "I want old clients to work"
  and "I wish to require better authentication" use cases.

- for msgr2, don't check anything and always challenge.  CEPHX_V2
  predates msgr2, anyone speaking msgr2 must support it.

Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
(cherry picked from commit 4a82c72e3bdddcb625933e83af8b50a444b961f1)

Conflicts:
	src/msg/async/ProtocolV1.cc [ commit c58c5754dfd2
	  ("msg/async/ProtocolV1: use AuthServer and AuthClient") not
	  in nautilus.  This means that only msgr2 is affected, so drop
	  ProtocolV1.cc hunk.  As a result, skip_authorizer_challenge is
	  never set, but this is fine because msgr1 still uses old ms_*
	  auth methods and tests CEPHX_V2 appropriately. ]

int MonClient::handle_auth_request(
  Connection *con,
  AuthConnectionMeta *auth_meta,
  bool more,
  uint32_t auth_method,
  const bufferlist& payload,
  bufferlist *reply)
{
  auth_meta->auth_mode = payload[0];
  if (auth_meta->auth_mode < AUTH_MODE_AUTHORIZER ||
      auth_meta->auth_mode > AUTH_MODE_AUTHORIZER_MAX) {
    return -EACCES;
  }
  AuthAuthorizeHandler *ah = get_auth_authorize_handler(con->get_peer_type(),
							auth_method);
  if (!ah) {
    lderr(cct) << __func__ << " no AuthAuthorizeHandler found for auth method "
	       << auth_method << dendl;
    return -EOPNOTSUPP;
  }

  auto ac = &auth_meta->authorizer_challenge;
  if (!HAVE_FEATURE(con->get_features(), CEPHX_V2)) {
    if (cct->_conf->cephx_service_require_version >= 2) {
      ldout(cct,10) << __func__ << " client missing CEPHX_V2 ("
		    << "cephx_service_requre_version = "
		    << cct->_conf->cephx_service_require_version << ")" << dendl;
      return -EACCES;
    }
    ac = nullptr;
  }

  bool was_challenge = (bool)auth_meta->authorizer_challenge;
  bool isvalid = ah->verify_authorizer(
    cct,
    rotating_secrets.get(),
    payload,
    auth_meta->get_connection_secret_length(),
    reply,
    &con->peer_name,
    &con->peer_global_id,
    &con->peer_caps_info,
    &auth_meta->session_key,
    &auth_meta->connection_secret,
    ac);
  if (isvalid) {
    handle_authentication_dispatcher->ms_handle_authentication(con);
    return 1;
  }
  if (!more && !was_challenge && auth_meta->authorizer_challenge) {
    ldout(cct,10) << __func__ << " added challenge on " << con << dendl;
    return 0;
  }
  ldout(cct,10) << __func__ << " bad authorizer on " << con << dendl;
  return -EACCES;
}

A flaw was found in the Cephx authentication protocol in versions before 15.2.6 and before 14.2.14, where it does not verify Ceph clients correctly and is then vulnerable to replay attacks in Nautilus. This flaw allows an attacker with access to the Ceph cluster network to authenticate with the Ceph service via a packet sniffer and perform actions allowed by the Ceph service. This issue is a reintroduction of CVE-2018-1128, affecting the msgr2 protocol. The msgr 2 protocol is used for all communication except older clients that do not support the msgr2 protocol. The msgr1 protocol is not affected. The highest threat from this vulnerability is to confidentiality, integrity, and system availability.


int MonClient::handle_auth_request(
  Connection *con,
  AuthConnectionMeta *auth_meta,
  bool more,
  uint32_t auth_method,
  const bufferlist& payload,
  bufferlist *reply)
{
  auth_meta->auth_mode = payload[0];
  if (auth_meta->auth_mode < AUTH_MODE_AUTHORIZER ||
      auth_meta->auth_mode > AUTH_MODE_AUTHORIZER_MAX) {
    return -EACCES;
  }
  AuthAuthorizeHandler *ah = get_auth_authorize_handler(con->get_peer_type(),
							auth_method);
  if (!ah) {
    lderr(cct) << __func__ << " no AuthAuthorizeHandler found for auth method "
	       << auth_method << dendl;
    return -EOPNOTSUPP;
  }

  auto ac = &auth_meta->authorizer_challenge;
  if (auth_meta->skip_authorizer_challenge) {
    ldout(cct, 10) << __func__ << " skipping challenge on " << con << dendl;
    ac = nullptr;
  }

  bool was_challenge = (bool)auth_meta->authorizer_challenge;
  bool isvalid = ah->verify_authorizer(
    cct,
    rotating_secrets.get(),
    payload,
    auth_meta->get_connection_secret_length(),
    reply,
    &con->peer_name,
    &con->peer_global_id,
    &con->peer_caps_info,
    &auth_meta->session_key,
    &auth_meta->connection_secret,
    ac);
  if (isvalid) {
    handle_authentication_dispatcher->ms_handle_authentication(con);
    return 1;
  }
  if (!more && !was_challenge && auth_meta->authorizer_challenge) {
    ldout(cct,10) << __func__ << " added challenge on " << con << dendl;
    return 0;
  }
  ldout(cct,10) << __func__ << " bad authorizer on " << con << dendl;
  return -EACCES;
}


issue #121: NULL pointer dereference in wvunpack.c

* check for NULL pointer before dereferencing in wvunpack.c
* sanitize custom extensions to be alphanumeric only

int main(int argc, char **argv)
#endif
{
#ifdef __EMX__ /* OS/2 */
    _wildcard (&argc, &argv);
#endif
    int verify_only = 0, error_count = 0, add_extension = 0, output_spec = 0, c_count = 0, x_count = 0;
    char outpath, **matches = NULL, *outfilename = NULL, **argv_fn = NULL, selfname [PATH_MAX];
    int use_stdin = 0, use_stdout = 0, argc_fn = 0, argi, result;

#if defined(_WIN32)
    if (!GetModuleFileName (NULL, selfname, sizeof (selfname)))
#endif
    strncpy (selfname, *argv, sizeof (selfname));

    if (filespec_name (selfname)) {
        char *filename = filespec_name (selfname);

        if (strstr (filename, "ebug") || strstr (filename, "DEBUG"))
            debug_logging_mode = TRUE;

        while (strchr (filename, '{')) {
            char *open_brace = strchr (filename, '{');
            char *close_brace = strchr (open_brace, '}');

            if (!close_brace)
                break;

            if (close_brace - open_brace > 1) {
                int option_len = (int)(close_brace - open_brace) - 1;
                char *option = malloc (option_len + 1);

                argv_fn = realloc (argv_fn, sizeof (char *) * ++argc_fn);
                memcpy (option, open_brace + 1, option_len);
                argv_fn [argc_fn - 1] = option;
                option [option_len] = 0;

                if (debug_logging_mode)
                    error_line ("file arg %d: %s", argc_fn, option);
            }

            filename = close_brace;
        }
    }

    if (debug_logging_mode) {
        char **argv_t = argv;
        int argc_t = argc;

        while (--argc_t)
            error_line ("cli arg %d: %s", argc - argc_t, *++argv_t);
    }

#if defined (_WIN32)
    set_console_title = 1;      // on Windows, we default to messing with the console title
#endif                          // on Linux, this is considered uncool to do by default

    // loop through command-line arguments

    for (argi = 0; argi < argc + argc_fn - 1; ++argi) {
        char *argcp;

        if (argi < argc_fn)
            argcp = argv_fn [argi];
        else
            argcp = argv [argi - argc_fn + 1];

        if (argcp [0] == '-' && argcp [1] == '-' && argcp [2]) {
            char *long_option = argcp + 2, *long_param = long_option;

            while (*long_param)
                if (*long_param++ == '=')
                    break;

            if (!strcmp (long_option, "help")) {                        // --help
                printf ("%s", help);
                return 0;
            }
            else if (!strcmp (long_option, "version")) {                // --version
                printf ("wvunpack %s\n", PACKAGE_VERSION);
                printf ("libwavpack %s\n", WavpackGetLibraryVersionString ());
                return 0;
            }
#ifdef _WIN32
            else if (!strcmp (long_option, "pause"))                    // --pause
                pause_mode = 1;
            else if (!strcmp (long_option, "drop"))                     // --drop
                drop_mode = 1;
#endif
            else if (!strcmp (long_option, "normalize-floats"))         // --normalize-floats
                normalize_floats = 1;
            else if (!strcmp (long_option, "no-utf8-convert"))          // --no-utf8-convert
                no_utf8_convert = 1;
            else if (!strncmp (long_option, "skip", 4)) {               // --skip
                parse_sample_time_index (&skip, long_param);

                if (!skip.value_is_valid) {
                    error_line ("invalid --skip parameter!");
                    ++error_count;
                }
            }
            else if (!strncmp (long_option, "until", 5)) {              // --until
                parse_sample_time_index (&until, long_param);

                if (!until.value_is_valid) {
                    error_line ("invalid --until parameter!");
                    ++error_count;
                }
            }
            else if (!strcmp (long_option, "caf-be")) {                 // --caf-be
                decode_format = WP_FORMAT_CAF;
                caf_be = format_specified = 1;
            }
            else if (!strcmp (long_option, "caf-le")) {                 // --caf-le
                decode_format = WP_FORMAT_CAF;
                format_specified = 1;
            }
            else if (!strcmp (long_option, "dsf")) {                    // --dsf
                decode_format = WP_FORMAT_DSF;
                format_specified = 1;
            }
            else if (!strcmp (long_option, "dsdiff") || !strcmp (long_option, "dff")) {
                decode_format = WP_FORMAT_DFF;                          // --dsdiff or --dff
                format_specified = 1;
            }
            else if (!strcmp (long_option, "w64")) {                    // --w64
                decode_format = WP_FORMAT_W64;
                format_specified = 1;
            }
            else if (!strcmp (long_option, "wav")) {                    // --wav
                decode_format = WP_FORMAT_WAV;
                format_specified = 1;
            }
            else if (!strcmp (long_option, "raw-pcm"))                  // --raw-pcm
                raw_pcm = raw_decode = 1;
            else if (!strcmp (long_option, "raw"))                      // --raw
                raw_decode = 1;
            else {
                error_line ("unknown option: %s !", long_option);
                ++error_count;
            }
        }
#if defined (_WIN32)
        else if ((argcp [0] == '-' || argcp [0] == '/') && argcp [1])
#else
        else if (argcp [0] == '-' && argcp [1])
#endif
            while (*++argcp)
                switch (*argcp) {
                    case 'Y': case 'y':
                        overwrite_all = 1;
                        break;

                    case 'C': case 'c':
                        if (++c_count == 2) {
                            add_tag_extraction_to_list ("cuesheet=%a.cue");
                            c_count = 0;
                        }

                        break;

                    case 'D': case 'd':
                        delete_source = 1;
                        break;

#if defined (_WIN32)
                    case 'L': case 'l':
                        SetPriorityClass (GetCurrentProcess(), IDLE_PRIORITY_CLASS);
                        break;
#elif defined (__OS2__)
                    case 'L': case 'l':
                        DosSetPriority (0, PRTYC_IDLETIME, 0, 0);
                        break;
#endif
#if defined (_WIN32)
                    case 'O': case 'o':  // ignore -o in Windows to be Linux compatible
                        break;
#else
                    case 'O': case 'o':
                        output_spec = 1;
                        break;
#endif
                    case 'T': case 't':
                        copy_time = 1;
                        break;

                    case 'V': case 'v':
                        ++verify_only;
                        break;

                    case 'F': case 'f':
                        file_info = (char) strtol (++argcp, &argcp, 10);

                        if (file_info < 0 || file_info > 10) {
                            error_line ("-f option must be 1-10, or omit (or 0) for all!");
                            ++error_count;
                        }
                        else {
                            quiet_mode = no_audio_decode = 1;
                            file_info++;
                        }

                        --argcp;
                        break;

                    case 'S': case 's':
                        no_audio_decode = 1;
                        ++summary;
                        break;

                    case 'K': case 'k':
                        outbuf_k = strtol (++argcp, &argcp, 10);

                        if (outbuf_k < 1 || outbuf_k > 16384)       // range-check for reasonable values
                            outbuf_k = 0;

                        --argcp;
                        break;

                    case 'M': case 'm':
                        calc_md5 = 1;
                        break;

                    case 'B': case 'b':
                        blind_decode = 1;
                        break;

                    case 'N': case 'n':
                        no_audio_decode = 1;
                        break;

                    case 'R': case 'r':
                        raw_decode = 1;
                        break;

                    case 'W': case 'w':
                        decode_format = WP_FORMAT_WAV;
                        format_specified = 1;
                        break;

                    case 'Q': case 'q':
                        quiet_mode = 1;
                        break;

                    case 'Z': case 'z':
                        set_console_title = (char) strtol (++argcp, &argcp, 10);
                        --argcp;
                        break;

                    case 'X': case 'x':
                        if (++x_count == 3) {
                            error_line ("illegal option: %s !", argcp);
                            ++error_count;
                            x_count = 0;
                        }

                        break;

                    case 'I': case 'i':
                        ignore_wvc = 1;
                        break;

                    default:
                        error_line ("illegal option: %c !", *argcp);
                        ++error_count;
                }
        else if (argi < argc_fn) {
            error_line ("invalid use of filename-embedded args: %s !", argcp);
            ++error_count;
        }
        else {
            if (x_count) {
                if (x_count == 1) {
                    if (tag_extract_stdout) {
                        error_line ("can't extract more than 1 tag item to stdout at a time!");
                        ++error_count;
                    }
                    else {
                        tag_extract_stdout = argcp;
                        no_audio_decode = 1;
                    }
                }
                else if (x_count == 2)
                    add_tag_extraction_to_list (argcp);

                x_count = 0;
            }
#if defined (_WIN32)
            else if (drop_mode || !num_files) {
                matches = realloc (matches, (num_files + 1) * sizeof (*matches));
                matches [num_files] = malloc (strlen (argcp) + 10);
                strcpy (matches [num_files], argcp);
                use_stdin |= (*argcp == '-');

                if (*(matches [num_files]) != '-' && *(matches [num_files]) != '@' &&
                    !filespec_ext (matches [num_files]))
                        strcat (matches [num_files], ".wv");

                num_files++;
            }
            else if (!outfilename) {
                outfilename = malloc (strlen (argcp) + PATH_MAX);
                strcpy (outfilename, argcp);
                use_stdout = (*argcp == '-');
            }
            else {
                error_line ("extra unknown argument: %s !", argcp);
                ++error_count;
            }
#else
            else if (output_spec) {
                outfilename = malloc (strlen (argcp) + PATH_MAX);
                strcpy (outfilename, argcp);
                use_stdout = (*argcp == '-');
                output_spec = 0;
            }
            else {
                matches = realloc (matches, (num_files + 1) * sizeof (*matches));
                matches [num_files] = malloc (strlen (argcp) + 10);
                strcpy (matches [num_files], argcp);
                use_stdin |= (*argcp == '-');

                if (*(matches [num_files]) != '-' && *(matches [num_files]) != '@' &&
                    !filespec_ext (matches [num_files]))
                        strcat (matches [num_files], ".wv");

                num_files++;
            }
#endif
        }

        if (argi < argc_fn)
            free (argv_fn [argi]);
    }

    free (argv_fn);

   // check for various command-line argument problems

    if (output_spec) {
        error_line ("no output filename or path specified with -o option!");
        ++error_count;
    }

    if (use_stdin && num_files > 1) {
        error_line ("when stdin is used for input, it must be the only file!");
        ++error_count;
    }

    if (use_stdin && !outfilename)  // for stdin source, no output specification implies stdout
        use_stdout = 1;

    if (delete_source && (verify_only || skip.value_is_valid || until.value_is_valid)) {
        error_line ("can't delete in verify mode or when --skip or --until are used!");
        delete_source = 0;
    }

    if (raw_decode && format_specified) {
        error_line ("-r (raw decode) and specifying a format (like -w) are incompatible!");
        ++error_count;
    }

    if (verify_only && (format_specified || outfilename)) {
        error_line ("specifying output file or format and verify mode are incompatible!");
        ++error_count;
    }

    if (verify_only > 1 && calc_md5) {
        error_line ("can't calculate MD5s in quick verify mode!");
        ++error_count;
    }

    if (c_count == 1) {
        if (tag_extract_stdout) {
            error_line ("can't extract more than 1 tag item to stdout at a time!");
            error_count++;
        }
        else {
            tag_extract_stdout = "cuesheet";
            no_audio_decode = 1;
        }
    }

    if ((summary || file_info) && (num_tag_extractions || outfilename || verify_only || delete_source || format_specified)) {
        error_line ("can't display file information and do anything else!");
        ++error_count;
    }

    if (tag_extract_stdout && (num_tag_extractions || outfilename || verify_only || delete_source || format_specified || raw_decode)) {
        error_line ("can't extract a tag to stdout and do anything else!");
        ++error_count;
    }

    if ((tag_extract_stdout || num_tag_extractions) && use_stdout) {
        error_line ("can't extract tags when unpacking audio to stdout!");
        ++error_count;
    }

    if (strcmp (WavpackGetLibraryVersionString (), PACKAGE_VERSION)) {
        fprintf (stderr, version_warning, WavpackGetLibraryVersionString (), PACKAGE_VERSION);
        fflush (stderr);
    }
    else if (!quiet_mode && !error_count) {
        fprintf (stderr, sign_on, VERSION_OS, WavpackGetLibraryVersionString ());
        fflush (stderr);
    }

    if (error_count) {
        fprintf (stderr, "\ntype 'wvunpack' for short help or 'wvunpack --help' for full help\n");
        fflush (stderr);
        return 1;
    }

    if (!num_files) {
        printf ("%s", usage);
        return 1;
    }

    setup_break ();

    for (file_index = 0; file_index < num_files; ++file_index) {
        char *infilename = matches [file_index];

        // If the single infile specification begins with a '@', then it
        // actually points to a file that contains the names of the files
        // to be converted. This was included for use by Wim Speekenbrink's
        // frontends, but could be used for other purposes.

        if (*infilename == '@') {
            FILE *list = fopen (infilename+1, "rb");
            char *listbuff = NULL, *cp;
            int listbytes = 0, di, c;

            for (di = file_index; di < num_files - 1; di++)
                matches [di] = matches [di + 1];

            file_index--;
            num_files--;

            if (list == NULL) {
                error_line ("file %s not found!", infilename+1);
                free (infilename);
                return 1;
            }

            while (1) {
                int bytes_read;

                listbuff = realloc (listbuff, listbytes + 1024);
                memset (listbuff + listbytes, 0, 1024);
                listbytes += bytes_read = (int) fread (listbuff + listbytes, 1, 1024, list);

                if (bytes_read < 1024)
                    break;
            }

#if defined (_WIN32)
            listbuff = realloc (listbuff, listbytes *= 2);
            TextToUTF8 (listbuff, listbytes);
#endif
            cp = listbuff;

            while ((c = *cp++)) {

                while (c == '\n' || c == '\r')
                    c = *cp++;

                if (c) {
                    char *fname = malloc (PATH_MAX);
                    int ci = 0;

                    do
                        fname [ci++] = c;
                    while ((c = *cp++) != '\n' && c != '\r' && c && ci < PATH_MAX);

                    fname [ci++] = '\0';
                    matches = realloc (matches, ++num_files * sizeof (*matches));

                    for (di = num_files - 1; di > file_index + 1; di--)
                        matches [di] = matches [di - 1];

                    matches [++file_index] = fname;
                }

                if (!c)
                    break;
            }

            fclose (list);
            free (listbuff);
            free (infilename);
        }
#if defined (_WIN32)
        else if (filespec_wild (infilename)) {
            wchar_t *winfilename = utf8_to_utf16(infilename);
            struct _wfinddata_t _wfinddata_t;
            intptr_t file;
            int di;

            for (di = file_index; di < num_files - 1; di++)
                matches [di] = matches [di + 1];

            file_index--;
            num_files--;

            if ((file = _wfindfirst (winfilename, &_wfinddata_t)) != (intptr_t) -1) {
                do {
                    char *name_utf8;

                    if (!(_wfinddata_t.attrib & _A_SUBDIR) && (name_utf8 = utf16_to_utf8(_wfinddata_t.name))) {
                        matches = realloc (matches, ++num_files * sizeof (*matches));

                        for (di = num_files - 1; di > file_index + 1; di--)
                            matches [di] = matches [di - 1];

                        matches [++file_index] = malloc (strlen (infilename) + strlen (name_utf8) + 10);
                        strcpy (matches [file_index], infilename);
                        *filespec_name (matches [file_index]) = '\0';
                        strcat (matches [file_index], name_utf8);
                        free (name_utf8);
                    }
                } while (_wfindnext (file, &_wfinddata_t) == 0);

                _findclose (file);
            }

            free (winfilename);
            free (infilename);
        }
#endif
    }

    // If the outfile specification begins with a '@', then it actually points
    // to a file that contains the output specification. This was included for
    // use by Wim Speekenbrink's frontends because certain filenames could not
    // be passed on the command-line, but could be used for other purposes.

    if (outfilename && outfilename [0] == '@') {
        char listbuff [PATH_MAX * 2], *lp = listbuff;
        FILE *list = fopen (outfilename+1, "rb");
        int c;

        if (list == NULL) {
            error_line ("file %s not found!", outfilename+1);
            free(outfilename);
            return 1;
        }

        memset (listbuff, 0, sizeof (listbuff));
        c = (int) fread (listbuff, 1, sizeof (listbuff) - 1, list);   // assign c only to suppress warning

#if defined (_WIN32)
        TextToUTF8 (listbuff, PATH_MAX * 2);
#endif

        while ((c = *lp++) == '\n' || c == '\r');

        if (c) {
            int ci = 0;

            do
                outfilename [ci++] = c;
            while ((c = *lp++) != '\n' && c != '\r' && c && ci < PATH_MAX);

            outfilename [ci] = '\0';
        }
        else {
            error_line ("output spec file is empty!");
            free(outfilename);
            fclose (list);
            return 1;
        }

        fclose (list);
    }

    // if we found any files to process, this is where we start

    if (num_files) {
        if (outfilename && *outfilename != '-') {
            outpath = (filespec_path (outfilename) != NULL);

            if (num_files > 1 && !outpath) {
                error_line ("%s is not a valid output path", outfilename);
                free (outfilename);
                return 1;
            }
        }
        else
            outpath = 0;

        add_extension = !outfilename || outpath || !filespec_ext (outfilename);

        // loop through and process files in list

        for (file_index = 0; file_index < num_files; ++file_index) {
            if (check_break ())
                break;

            // generate output filename

            if (outpath) {
                strcat (outfilename, filespec_name (matches [file_index]));

                if (filespec_ext (outfilename))
                    *filespec_ext (outfilename) = '\0';
            }
            else if (!outfilename) {
                outfilename = malloc (strlen (matches [file_index]) + 10);
                strcpy (outfilename, matches [file_index]);

                if (filespec_ext (outfilename))
                    *filespec_ext (outfilename) = '\0';
            }

            if (num_files > 1 && !quiet_mode) {
                fprintf (stderr, "\n%s:\n", matches [file_index]);
                fflush (stderr);
            }

            if (verify_only > 1) {
                result = quick_verify_file (matches [file_index], verify_only > 2);

                // quick_verify_file() returns hard error to mean file cannot be quickly verified
                // because it has no block checksums, so fall back to standard slow verify

                if (result == WAVPACK_HARD_ERROR)
                    result = unpack_file (matches [file_index], NULL, 0);
            }
            else
                result = unpack_file (matches [file_index], verify_only ? NULL : outfilename, add_extension);

            if (result != WAVPACK_NO_ERROR)
                ++error_count;

            if (result == WAVPACK_HARD_ERROR)
                break;

            // clean up in preparation for potentially another file

            if (outpath)
                *filespec_name (outfilename) = '\0';
            else if (*outfilename != '-') {
                free (outfilename);
                outfilename = NULL;
            }

            free (matches [file_index]);
        }

        if (num_files > 1) {
            if (error_count) {
                fprintf (stderr, "\n **** warning: errors occurred in %d of %d files! ****\n", error_count, num_files);
                fflush (stderr);
            }
            else if (!quiet_mode) {
                fprintf (stderr, "\n **** %d files successfully processed ****\n", num_files);
                fflush (stderr);
            }
        }

        free (matches);
    }
    else {
        error_line ("nothing to do!");
        ++error_count;
    }

    if (outfilename)
        free (outfilename);

    if (set_console_title)
        DoSetConsoleTitle ("WvUnpack Completed");

    return error_count ? 1 : 0;
}

A null pointer dereference bug was found in wavpack-5.4.0 The results from the ASAN log: AddressSanitizer:DEADLYSIGNAL ===================================================================84257==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0x561b47a970c6 bp 0x7fff13952fb0 sp 0x7fff1394fca0 T0) ==84257==The signal is caused by a WRITE memory access. ==84257==Hint: address points to the zero page. #0 0x561b47a970c5 in main cli/wvunpack.c:834 #1 0x7efc4f5c0082 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x24082) #2 0x561b47a945ed in _start (/usr/local/bin/wvunpack+0xa5ed) AddressSanitizer can not provide additional info. SUMMARY: AddressSanitizer: SEGV cli/wvunpack.c:834 in main ==84257==ABORTING