Fix vulnerability around urls crafter as http://host/../secret.foo (#24)

PLT_HttpServer::ServeFile(const NPT_HttpRequest&        request,
                          const NPT_HttpRequestContext& context,
                          NPT_HttpResponse&             response,
                          NPT_String                    file_path)
{
    NPT_InputStreamReference stream;
    NPT_File                 file(file_path);
    NPT_FileInfo             file_info;

    // prevent hackers from accessing files outside of our root
    if ((file_path.Find("/..") >= 0) || (file_path.Find("\\..") >= 0) ||
        NPT_FAILED(NPT_File::GetInfo(file_path, &file_info))) {
        return NPT_ERROR_NO_SUCH_ITEM;
    }

    // check for range requests
    const NPT_String* range_spec = request.GetHeaders().GetHeaderValue(NPT_HTTP_HEADER_RANGE);

    // handle potential 304 only if range header not set
    NPT_DateTime  date;
    NPT_TimeStamp timestamp;
    if (NPT_SUCCEEDED(PLT_UPnPMessageHelper::GetIfModifiedSince((NPT_HttpMessage&)request, date)) &&
        !range_spec) {
        date.ToTimeStamp(timestamp);

        NPT_LOG_INFO_5("File %s timestamps: request=%d (%s) vs file=%d (%s)",
                       (const char*)request.GetUrl().GetPath(),
                       (NPT_UInt32)timestamp.ToSeconds(),
                       (const char*)date.ToString(),
                       (NPT_UInt32)file_info.m_ModificationTime,
                       (const char*)NPT_DateTime(file_info.m_ModificationTime).ToString());

        if (timestamp >= file_info.m_ModificationTime) {
            // it's a match
            NPT_LOG_FINE_1("Returning 304 for %s", request.GetUrl().GetPath().GetChars());
            response.SetStatus(304, "Not Modified", NPT_HTTP_PROTOCOL_1_1);
            return NPT_SUCCESS;
        }
    }

    // open file
    if (NPT_FAILED(file.Open(NPT_FILE_OPEN_MODE_READ)) ||
        NPT_FAILED(file.GetInputStream(stream))        ||
        stream.IsNull()) {
        return NPT_ERROR_NO_SUCH_ITEM;
    }

    // set Last-Modified and Cache-Control headers
    if (file_info.m_ModificationTime) {
        NPT_DateTime last_modified = NPT_DateTime(file_info.m_ModificationTime);
        response.GetHeaders().SetHeader("Last-Modified", last_modified.ToString(NPT_DateTime::FORMAT_RFC_1123), true);
        response.GetHeaders().SetHeader("Cache-Control", "max-age=0,must-revalidate", true);
        //response.GetHeaders().SetHeader("Cache-Control", "max-age=1800", true);
    }

    PLT_HttpRequestContext tmp_context(request, context);
    return ServeStream(request, context, response, stream, PLT_MimeType::GetMimeType(file_path, &tmp_context));
}

Platinum Upnp SDK through 1.2.0 has a directory traversal vulnerability. The attack could remote attack victim by sending http://ip:port/../privacy.avi URL to compromise a victim's privacy.


PLT_HttpServer::ServeFile(const NPT_HttpRequest&        request,
                          const NPT_HttpRequestContext& context,
                          NPT_HttpResponse&             response,
                          NPT_String                    file_path)
{
    NPT_InputStreamReference stream;
    NPT_File                 file(file_path);
    NPT_FileInfo             file_info;

    // prevent hackers from accessing files outside of our root
    if ((file_path.Find("../") >= 0) || (file_path.Find("..\\") >= 0) ||
        NPT_FAILED(NPT_File::GetInfo(file_path, &file_info))) {
        return NPT_ERROR_NO_SUCH_ITEM;
    }

    // check for range requests
    const NPT_String* range_spec = request.GetHeaders().GetHeaderValue(NPT_HTTP_HEADER_RANGE);

    // handle potential 304 only if range header not set
    NPT_DateTime  date;
    NPT_TimeStamp timestamp;
    if (NPT_SUCCEEDED(PLT_UPnPMessageHelper::GetIfModifiedSince((NPT_HttpMessage&)request, date)) &&
        !range_spec) {
        date.ToTimeStamp(timestamp);

        NPT_LOG_INFO_5("File %s timestamps: request=%d (%s) vs file=%d (%s)",
                       (const char*)request.GetUrl().GetPath(),
                       (NPT_UInt32)timestamp.ToSeconds(),
                       (const char*)date.ToString(),
                       (NPT_UInt32)file_info.m_ModificationTime,
                       (const char*)NPT_DateTime(file_info.m_ModificationTime).ToString());

        if (timestamp >= file_info.m_ModificationTime) {
            // it's a match
            NPT_LOG_FINE_1("Returning 304 for %s", request.GetUrl().GetPath().GetChars());
            response.SetStatus(304, "Not Modified", NPT_HTTP_PROTOCOL_1_1);
            return NPT_SUCCESS;
        }
    }

    // open file
    if (NPT_FAILED(file.Open(NPT_FILE_OPEN_MODE_READ)) ||
        NPT_FAILED(file.GetInputStream(stream))        ||
        stream.IsNull()) {
        return NPT_ERROR_NO_SUCH_ITEM;
    }

    // set Last-Modified and Cache-Control headers
    if (file_info.m_ModificationTime) {
        NPT_DateTime last_modified = NPT_DateTime(file_info.m_ModificationTime);
        response.GetHeaders().SetHeader("Last-Modified", last_modified.ToString(NPT_DateTime::FORMAT_RFC_1123), true);
        response.GetHeaders().SetHeader("Cache-Control", "max-age=0,must-revalidate", true);
        //response.GetHeaders().SetHeader("Cache-Control", "max-age=1800", true);
    }

    PLT_HttpRequestContext tmp_context(request, context);
    return ServeStream(request, context, response, stream, PLT_MimeType::GetMimeType(file_path, &tmp_context));
}


Release "3.0.7": Fix toPrecision bug "stack overflow #4".

FossilOrigin-Name: 6c7f0c37027d7f890b57cb38f776af39b8f81f03e60ceeb0a231a1d21e24b5de

static Jsi_RC NumberToPrecisionCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    char buf[100];
    int prec = 0, skip = 0;
    Jsi_Number num;
    Jsi_Value *v;
    ChkStringN(_this, funcPtr, v);
    if (Jsi_GetIntFromValue(interp, Jsi_ValueArrayIndex(interp, args, skip), &prec) != JSI_OK)
        return JSI_ERROR;
    if (prec<=0) return JSI_ERROR;
    Jsi_GetDoubleFromValue(interp, v, &num);
    snprintf(buf, sizeof(buf),"%.*" JSI_NUMFFMT, prec, num);
    if (num<0)
        prec++;
    buf[prec+1] = 0;
    if (buf[prec] == '.')
        buf[prec] = 0;
    Jsi_ValueMakeStringDup(interp, ret, buf);
    return JSI_OK;
}

Buffer overflow vulnerability in function NumberToPrecisionCmd in jsish before 3.0.7, allows remote attackers to execute arbitrary code.


static Jsi_RC NumberToPrecisionCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    char buf[JSI_MAX_NUMBER_STRING*2];
    int prec = 0, skip = 0;
    Jsi_Number num;
    Jsi_Value *v;
    ChkStringN(_this, funcPtr, v);
    if (Jsi_GetIntFromValue(interp, Jsi_ValueArrayIndex(interp, args, skip), &prec) != JSI_OK)
        return JSI_ERROR;
    if (prec<=0 || prec>JSI_MAX_NUMBER_STRING) return Jsi_LogError("precision must be between 1 and %d", JSI_MAX_NUMBER_STRING);
    Jsi_GetDoubleFromValue(interp, v, &num);
    snprintf(buf, sizeof(buf),"%.*" JSI_NUMFFMT, prec, num);
    if (num<0)
        prec++;
    buf[prec+1] = 0;
    if (buf[prec] == '.')
        buf[prec] = 0;
    Jsi_ValueMakeStringDup(interp, ret, buf);
    return JSI_OK;
}


Try to fix issue #637

Reference: https://github.com/avast/retdec/issues/637

bool Decoder::canSplitFunctionOn(
		utils::Address addr,
		llvm::BasicBlock* splitBb,
		std::set<llvm::BasicBlock*>& newFncStarts)
{
	newFncStarts.insert(splitBb);

	auto* f = splitBb->getParent();
	auto fAddr = getFunctionAddress(f);

	auto fSzIt = _fnc2sz.find(f);
	if (fSzIt != _fnc2sz.end())
	{
		if (fAddr <= addr && addr < (fAddr+fSzIt->second))
		{
			LOG << "\t\t\t\t\t" << "!CAN S: addr cond @ " << addr << std::endl;
			return false;
		}
	}

	std::set<Address> fncStarts;
	fncStarts.insert(fAddr);
	fncStarts.insert(addr);

	LOG << "\t\t\t\t\t" << "CAN S: split @ " << fAddr << std::endl;
	LOG << "\t\t\t\t\t" << "CAN S: split @ " << addr << std::endl;

	bool changed = true;
	while (changed)
	{
		changed = false;
		for (BasicBlock& b : *f)
		{
//			Address bAddr = getBasicBlockAddress(&b);
			Address bAddr;
			// TODO: shitty
			BasicBlock* bPrev = &b;
			while (bAddr.isUndefined() && bPrev)
			{
				bAddr = getBasicBlockAddress(bPrev);
				bPrev = bPrev->getPrevNode();
			}
			if (bAddr.isUndefined())
			{
				continue;
			}
			auto up = fncStarts.upper_bound(bAddr);
			--up;
			Address bFnc = *up;

			for (auto* p : predecessors(&b))
			{
//				Address pAddr = getBasicBlockAddress(p);
				Address pAddr;
				// TODO: shitty
				BasicBlock* pPrev = p;
				while (pAddr.isUndefined() && pPrev)
				{
					pAddr = getBasicBlockAddress(pPrev);
					pPrev = pPrev->getPrevNode();
				}
				if (pAddr.isUndefined())
				{
					continue;
				}
				auto up = fncStarts.upper_bound(pAddr);
				--up;
				Address pFnc = *up;

				if (bFnc != pFnc)
				{
					if (!canSplitFunctionOn(&b))
					{
						return false;
					}

					changed |= newFncStarts.insert(&b).second;
					changed |= fncStarts.insert(bAddr).second;

					LOG << "\t\t\t\t\t" << "CAN S: split @ " << bAddr << std::endl;
				}
			}
		}
	}

	return true;
}

An issue was discovered in retdec v3.3. In function canSplitFunctionOn() of ir_modifications.cpp, there is a possible out of bounds read due to a heap buffer overflow. The impact is: Deny of Service, Memory Disclosure, and Possible Code Execution.


bool Decoder::canSplitFunctionOn(
		utils::Address addr,
		llvm::BasicBlock* splitBb,
		std::set<llvm::BasicBlock*>& newFncStarts)
{
	newFncStarts.insert(splitBb);

	auto* f = splitBb->getParent();
	auto fAddr = getFunctionAddress(f);

	auto fSzIt = _fnc2sz.find(f);
	if (fSzIt != _fnc2sz.end())
	{
		if (fAddr <= addr && addr < (fAddr+fSzIt->second))
		{
			LOG << "\t\t\t\t\t" << "!CAN S: addr cond @ " << addr << std::endl;
			return false;
		}
	}

	std::set<Address> fncStarts;
	fncStarts.insert(fAddr);
	fncStarts.insert(addr);

	LOG << "\t\t\t\t\t" << "CAN S: split @ " << fAddr << std::endl;
	LOG << "\t\t\t\t\t" << "CAN S: split @ " << addr << std::endl;

	bool changed = true;
	while (changed)
	{
		changed = false;
		for (BasicBlock& b : *f)
		{
//			Address bAddr = getBasicBlockAddress(&b);
			Address bAddr;
			// TODO: shitty
			BasicBlock* bPrev = &b;
			while (bAddr.isUndefined() && bPrev)
			{
				bAddr = getBasicBlockAddress(bPrev);
				bPrev = bPrev->getPrevNode();
			}
			if (bAddr.isUndefined())
			{
				continue;
			}
			auto up = fncStarts.upper_bound(bAddr);
			if (up == fncStarts.begin()) {
				return false;
			}
			--up;
			Address bFnc = *up;

			for (auto* p : predecessors(&b))
			{
//				Address pAddr = getBasicBlockAddress(p);
				Address pAddr;
				// TODO: shitty
				BasicBlock* pPrev = p;
				while (pAddr.isUndefined() && pPrev)
				{
					pAddr = getBasicBlockAddress(pPrev);
					pPrev = pPrev->getPrevNode();
				}
				if (pAddr.isUndefined())
				{
					continue;
				}
				auto up = fncStarts.upper_bound(pAddr);
				if (up == fncStarts.begin()) {
					return false;
				}
				--up;
				Address pFnc = *up;

				if (bFnc != pFnc)
				{
					if (!canSplitFunctionOn(&b))
					{
						return false;
					}

					changed |= newFncStarts.insert(&b).second;
					changed |= fncStarts.insert(bAddr).second;

					LOG << "\t\t\t\t\t" << "CAN S: split @ " << bAddr << std::endl;
				}
			}
		}
	}

	return true;
}


static int cbor2json (OSCTXT* pCborCtxt, OSCTXT* pJsonCtxt)
{
   int ret = 0;
   OSOCTET tag, ub;

   /* Read byte from stream */
   ret = rtxReadBytes (pCborCtxt, &ub, 1);
   if (0 != ret) return LOG_RTERR (pCborCtxt, ret);
   tag = ub >> 5;

   /* Switch on tag value */
   switch (tag) {
   case OSRTCBOR_UINT: {
      OSUINTTYPE value;
      ret = rtCborDecUInt (pCborCtxt, ub, &value);
      if (0 != ret) return LOG_RTERR (pCborCtxt, ret);

      /* Encode JSON */
#ifndef _NO_INT64_SUPPORT
      ret = rtJsonEncUInt64Value (pJsonCtxt, value);
#else
      ret = rtJsonEncUIntValue (pJsonCtxt, value);
#endif
      if (0 != ret) return LOG_RTERR (pJsonCtxt, ret);
      break;
   }
   case OSRTCBOR_NEGINT: {
      OSINTTYPE value;
      ret = rtCborDecInt (pCborCtxt, ub, &value);
      if (0 != ret) return LOG_RTERR (pCborCtxt, ret);

      /* Encode JSON */
#ifndef _NO_INT64_SUPPORT
      ret = rtJsonEncInt64Value (pJsonCtxt, value);
#else
      ret = rtJsonEncIntValue (pJsonCtxt, value);
#endif
      if (0 != ret) return LOG_RTERR (pJsonCtxt, ret);
      break;
   }
   case OSRTCBOR_BYTESTR: {
      OSDynOctStr64 byteStr;
      ret = rtCborDecDynByteStr (pCborCtxt, ub, &byteStr);
      if (0 != ret) return LOG_RTERR (pCborCtxt, ret);

      /* Encode JSON */
      ret = rtJsonEncHexStr (pJsonCtxt, byteStr.numocts, byteStr.data);
      rtxMemFreePtr (pCborCtxt, byteStr.data);
      if (0 != ret) return LOG_RTERR (pJsonCtxt, ret);

      break;
   }
   case OSRTCBOR_UTF8STR: {
      OSUTF8CHAR* utf8str;
      ret = rtCborDecDynUTF8Str (pCborCtxt, ub, (char**)&utf8str);

      ret = rtJsonEncStringValue (pJsonCtxt, utf8str);
      rtxMemFreePtr (pCborCtxt, utf8str);
      if (0 != ret) return LOG_RTERR (pJsonCtxt, ret);

      break;
   }
   case OSRTCBOR_ARRAY:
   case OSRTCBOR_MAP: {
      OSOCTET len = ub & 0x1F;
      char startChar = (tag == OSRTCBOR_ARRAY) ? '[' : '{';
      char endChar = (tag == OSRTCBOR_ARRAY) ? ']' : '}';

      OSRTSAFEPUTCHAR (pJsonCtxt, startChar);

      if (len == OSRTCBOR_INDEF) {
         OSBOOL first = TRUE;
         for (;;) {
            if (OSRTCBOR_MATCHEOC (pCborCtxt)) {
               pCborCtxt->buffer.byteIndex++;
               break;
            }

            if (!first)
               OSRTSAFEPUTCHAR (pJsonCtxt, ',');
            else
               first = FALSE;

            /* If map, decode object name */
            if (tag == OSRTCBOR_MAP) {
               ret = cborElemNameToJson (pCborCtxt, pJsonCtxt);
            }

            /* Make recursive call */
            if (0 == ret)
               ret = cbor2json (pCborCtxt, pJsonCtxt);
            if (0 != ret) {
               OSCTXT* pctxt =
                  (rtxErrGetErrorCnt(pJsonCtxt) > 0) ? pJsonCtxt : pCborCtxt;
               return LOG_RTERR (pctxt, ret);
            }
         }
      }
      else { /* definite length */
         OSSIZE nitems;

         /* Decode tag and number of items */
         ret = rtCborDecSize (pCborCtxt, len, &nitems);
         if (0 == ret) {
            OSSIZE i;

            /* Loop to decode array items */
            for (i = 0; i < nitems; i++) {
               if (0 != i) OSRTSAFEPUTCHAR (pJsonCtxt, ',');

               /* If map, decode object name */
               if (tag == OSRTCBOR_MAP) {
                  ret = cborElemNameToJson (pCborCtxt, pJsonCtxt);
               }

               /* Make recursive call */
               if (0 == ret)
                  ret = cbor2json (pCborCtxt, pJsonCtxt);
               if (0 != ret) {
                  OSCTXT* pctxt =
                  (rtxErrGetErrorCnt(pJsonCtxt) > 0) ? pJsonCtxt : pCborCtxt;
                  return LOG_RTERR (pctxt, ret);
               }
            }
         }
      }
      OSRTSAFEPUTCHAR (pJsonCtxt, endChar);
      break;
   }

   case OSRTCBOR_FLOAT:
      if (tag == OSRTCBOR_FALSEENC || tag == OSRTCBOR_TRUEENC) {
         OSBOOL boolval = (ub == OSRTCBOR_TRUEENC) ? TRUE : FALSE;
         ret = rtJsonEncBoolValue (pJsonCtxt, boolval);
         if (0 != ret) return LOG_RTERR (pJsonCtxt, ret);
      }
      else if (tag == OSRTCBOR_FLT16ENC ||
               tag == OSRTCBOR_FLT32ENC ||
               tag == OSRTCBOR_FLT64ENC) {
         OSDOUBLE fltval;
         ret = rtCborDecFloat (pCborCtxt, ub, &fltval);
         if (0 != ret) return LOG_RTERR (pCborCtxt, ret);

         /* Encode JSON */
         ret = rtJsonEncDoubleValue (pJsonCtxt, fltval, 0);
         if (0 != ret) return LOG_RTERR (pJsonCtxt, ret);
      }
      else {
         ret = cborTagNotSupp (pCborCtxt, tag);
      }
      break;

   default:
      ret = cborTagNotSupp (pCborCtxt, tag);
   }

   return ret;
}

A memory corruption vulnerability in Objective Open CBOR Run-time (oocborrt) in versions before 2020-08-12 could allow an attacker to execute code via crafted Concise Binary Object Representation (CBOR) input to the cbor2json decoder. An uncaught error while decoding CBOR Major Type 3 text strings leads to the use of an attacker-controllable uninitialized stack value. This can be used to modify memory, causing a crash or potentially exploitable heap corruption.


static int cbor2json (OSCTXT* pCborCtxt, OSCTXT* pJsonCtxt)
{
   int ret = 0;
   OSOCTET tag, ub;

   /* Read byte from stream */
   ret = rtxReadBytes (pCborCtxt, &ub, 1);
   if (0 != ret) return LOG_RTERR (pCborCtxt, ret);
   tag = ub >> 5;

   /* Switch on tag value */
   switch (tag) {
   case OSRTCBOR_UINT: {
      OSUINTTYPE value;
      ret = rtCborDecUInt (pCborCtxt, ub, &value);
      if (0 != ret) return LOG_RTERR (pCborCtxt, ret);

      /* Encode JSON */
#ifndef _NO_INT64_SUPPORT
      ret = rtJsonEncUInt64Value (pJsonCtxt, value);
#else
      ret = rtJsonEncUIntValue (pJsonCtxt, value);
#endif
      if (0 != ret) return LOG_RTERR (pJsonCtxt, ret);
      break;
   }
   case OSRTCBOR_NEGINT: {
      OSINTTYPE value;
      ret = rtCborDecInt (pCborCtxt, ub, &value);
      if (0 != ret) return LOG_RTERR (pCborCtxt, ret);

      /* Encode JSON */
#ifndef _NO_INT64_SUPPORT
      ret = rtJsonEncInt64Value (pJsonCtxt, value);
#else
      ret = rtJsonEncIntValue (pJsonCtxt, value);
#endif
      if (0 != ret) return LOG_RTERR (pJsonCtxt, ret);
      break;
   }
   case OSRTCBOR_BYTESTR: {
      OSDynOctStr64 byteStr;
      ret = rtCborDecDynByteStr (pCborCtxt, ub, &byteStr);
      if (0 != ret) return LOG_RTERR (pCborCtxt, ret);

      /* Encode JSON */
      ret = rtJsonEncHexStr (pJsonCtxt, byteStr.numocts, byteStr.data);
      rtxMemFreePtr (pCborCtxt, byteStr.data);
      if (0 != ret) return LOG_RTERR (pJsonCtxt, ret);

      break;
   }
   case OSRTCBOR_UTF8STR: {
      OSUTF8CHAR* utf8str;
      ret = rtCborDecDynUTF8Str (pCborCtxt, ub, (char**)&utf8str);
      if (0 != ret) return LOG_RTERR (pCborCtxt, ret);

      ret = rtJsonEncStringValue (pJsonCtxt, utf8str);
      rtxMemFreePtr (pCborCtxt, utf8str);
      if (0 != ret) return LOG_RTERR (pJsonCtxt, ret);

      break;
   }
   case OSRTCBOR_ARRAY:
   case OSRTCBOR_MAP: {
      OSOCTET len = ub & 0x1F;
      char startChar = (tag == OSRTCBOR_ARRAY) ? '[' : '{';
      char endChar = (tag == OSRTCBOR_ARRAY) ? ']' : '}';

      OSRTSAFEPUTCHAR (pJsonCtxt, startChar);

      if (len == OSRTCBOR_INDEF) {
         OSBOOL first = TRUE;
         for (;;) {
            if (OSRTCBOR_MATCHEOC (pCborCtxt)) {
               pCborCtxt->buffer.byteIndex++;
               break;
            }

            if (!first)
               OSRTSAFEPUTCHAR (pJsonCtxt, ',');
            else
               first = FALSE;

            /* If map, decode object name */
            if (tag == OSRTCBOR_MAP) {
               ret = cborElemNameToJson (pCborCtxt, pJsonCtxt);
            }

            /* Make recursive call */
            if (0 == ret)
               ret = cbor2json (pCborCtxt, pJsonCtxt);
            if (0 != ret) {
               OSCTXT* pctxt =
                  (rtxErrGetErrorCnt(pJsonCtxt) > 0) ? pJsonCtxt : pCborCtxt;
               return LOG_RTERR (pctxt, ret);
            }
         }
      }
      else { /* definite length */
         OSSIZE nitems;

         /* Decode tag and number of items */
         ret = rtCborDecSize (pCborCtxt, len, &nitems);
         if (0 == ret) {
            OSSIZE i;

            /* Loop to decode array items */
            for (i = 0; i < nitems; i++) {
               if (0 != i) OSRTSAFEPUTCHAR (pJsonCtxt, ',');

               /* If map, decode object name */
               if (tag == OSRTCBOR_MAP) {
                  ret = cborElemNameToJson (pCborCtxt, pJsonCtxt);
               }

               /* Make recursive call */
               if (0 == ret)
                  ret = cbor2json (pCborCtxt, pJsonCtxt);
               if (0 != ret) {
                  OSCTXT* pctxt =
                  (rtxErrGetErrorCnt(pJsonCtxt) > 0) ? pJsonCtxt : pCborCtxt;
                  return LOG_RTERR (pctxt, ret);
               }
            }
         }
      }
      OSRTSAFEPUTCHAR (pJsonCtxt, endChar);
      break;
   }

   case OSRTCBOR_FLOAT:
      if (tag == OSRTCBOR_FALSEENC || tag == OSRTCBOR_TRUEENC) {
         OSBOOL boolval = (ub == OSRTCBOR_TRUEENC) ? TRUE : FALSE;
         ret = rtJsonEncBoolValue (pJsonCtxt, boolval);
         if (0 != ret) return LOG_RTERR (pJsonCtxt, ret);
      }
      else if (tag == OSRTCBOR_FLT16ENC ||
               tag == OSRTCBOR_FLT32ENC ||
               tag == OSRTCBOR_FLT64ENC) {
         OSDOUBLE fltval;
         ret = rtCborDecFloat (pCborCtxt, ub, &fltval);
         if (0 != ret) return LOG_RTERR (pCborCtxt, ret);

         /* Encode JSON */
         ret = rtJsonEncDoubleValue (pJsonCtxt, fltval, 0);
         if (0 != ret) return LOG_RTERR (pJsonCtxt, ret);
      }
      else {
         ret = cborTagNotSupp (pCborCtxt, tag);
      }
      break;

   default:
      ret = cborTagNotSupp (pCborCtxt, tag);
   }

   return ret;
}


fix sqlcipher_export handling of NULL parameters

void sqlcipher_exportFunc(sqlite3_context *context, int argc, sqlite3_value **argv) {
  sqlite3 *db = sqlite3_context_db_handle(context);
  const char* targetDb, *sourceDb;
  int targetDb_idx = 0;
  u64 saved_flags = db->flags;        /* Saved value of the db->flags */
  u32 saved_mDbFlags = db->mDbFlags;        /* Saved value of the db->mDbFlags */
  int saved_nChange = db->nChange;      /* Saved value of db->nChange */
  int saved_nTotalChange = db->nTotalChange; /* Saved value of db->nTotalChange */
  u8 saved_mTrace = db->mTrace;        /* Saved value of db->mTrace */
  int rc = SQLITE_OK;     /* Return code from service routines */
  char *zSql = NULL;         /* SQL statements */
  char *pzErrMsg = NULL;

  if(argc != 1 && argc != 2) {
    rc = SQLITE_ERROR;
    pzErrMsg = sqlite3_mprintf("invalid number of arguments (%d) passed to sqlcipher_export", argc);
    goto end_of_export;
  }

  targetDb = (const char*) sqlite3_value_text(argv[0]);
  sourceDb = (argc == 2) ? (char *) sqlite3_value_text(argv[1]) : "main";

  /* if the name of the target is not main, but the index returned is zero
     there is a mismatch and we should not proceed */
  targetDb_idx =  sqlcipher_find_db_index(db, targetDb);
  if(targetDb_idx == 0 && sqlite3StrICmp("main", targetDb) != 0) {
    rc = SQLITE_ERROR;
    pzErrMsg = sqlite3_mprintf("unknown database %s", targetDb);
    goto end_of_export;
  }
  db->init.iDb = targetDb_idx;

  db->flags |= SQLITE_WriteSchema | SQLITE_IgnoreChecks;
  db->mDbFlags |= DBFLAG_PreferBuiltin | DBFLAG_Vacuum;
  db->flags &= ~(u64)(SQLITE_ForeignKeys | SQLITE_ReverseOrder | SQLITE_Defensive | SQLITE_CountRows);
  db->mTrace = 0;

  /* Query the schema of the main database. Create a mirror schema
  ** in the temporary database.
  */
  zSql = sqlite3_mprintf(
    "SELECT sql "
    "  FROM %s.sqlite_master WHERE type='table' AND name!='sqlite_sequence'"
    "   AND rootpage>0"
  , sourceDb);
  rc = (zSql == NULL) ? SQLITE_NOMEM : sqlcipher_execExecSql(db, &pzErrMsg, zSql);
  if( rc!=SQLITE_OK ) goto end_of_export;
  sqlite3_free(zSql);

  zSql = sqlite3_mprintf(
    "SELECT sql "
    "  FROM %s.sqlite_master WHERE sql LIKE 'CREATE INDEX %%' "
  , sourceDb);
  rc = (zSql == NULL) ? SQLITE_NOMEM : sqlcipher_execExecSql(db, &pzErrMsg, zSql);
  if( rc!=SQLITE_OK ) goto end_of_export;
  sqlite3_free(zSql);

  zSql = sqlite3_mprintf(
    "SELECT sql "
    "  FROM %s.sqlite_master WHERE sql LIKE 'CREATE UNIQUE INDEX %%'"
  , sourceDb);
  rc = (zSql == NULL) ? SQLITE_NOMEM : sqlcipher_execExecSql(db, &pzErrMsg, zSql);
  if( rc!=SQLITE_OK ) goto end_of_export;
  sqlite3_free(zSql);

  /* Loop through the tables in the main database. For each, do
  ** an "INSERT INTO rekey_db.xxx SELECT * FROM main.xxx;" to copy
  ** the contents to the temporary database.
  */
  zSql = sqlite3_mprintf(
    "SELECT 'INSERT INTO %s.' || quote(name) "
    "|| ' SELECT * FROM %s.' || quote(name) || ';'"
    "FROM %s.sqlite_master "
    "WHERE type = 'table' AND name!='sqlite_sequence' "
    "  AND rootpage>0"
  , targetDb, sourceDb, sourceDb);
  rc = (zSql == NULL) ? SQLITE_NOMEM : sqlcipher_execExecSql(db, &pzErrMsg, zSql);
  if( rc!=SQLITE_OK ) goto end_of_export;
  sqlite3_free(zSql);

  /* Copy over the contents of the sequence table
  */
  zSql = sqlite3_mprintf(
    "SELECT 'INSERT INTO %s.' || quote(name) "
    "|| ' SELECT * FROM %s.' || quote(name) || ';' "
    "FROM %s.sqlite_master WHERE name=='sqlite_sequence';"
  , targetDb, sourceDb, targetDb);
  rc = (zSql == NULL) ? SQLITE_NOMEM : sqlcipher_execExecSql(db, &pzErrMsg, zSql);
  if( rc!=SQLITE_OK ) goto end_of_export;
  sqlite3_free(zSql);

  /* Copy the triggers, views, and virtual tables from the main database
  ** over to the temporary database.  None of these objects has any
  ** associated storage, so all we have to do is copy their entries
  ** from the SQLITE_MASTER table.
  */
  zSql = sqlite3_mprintf(
    "INSERT INTO %s.sqlite_master "
    "  SELECT type, name, tbl_name, rootpage, sql"
    "    FROM %s.sqlite_master"
    "   WHERE type='view' OR type='trigger'"
    "      OR (type='table' AND rootpage=0)"
  , targetDb, sourceDb);
  rc = (zSql == NULL) ? SQLITE_NOMEM : sqlcipher_execSql(db, &pzErrMsg, zSql);
  if( rc!=SQLITE_OK ) goto end_of_export;
  sqlite3_free(zSql);

  zSql = NULL;
end_of_export:
  db->init.iDb = 0;
  db->flags = saved_flags;
  db->mDbFlags = saved_mDbFlags;
  db->nChange = saved_nChange;
  db->nTotalChange = saved_nTotalChange;
  db->mTrace = saved_mTrace;

  if(zSql) sqlite3_free(zSql);

  if(rc) {
    if(pzErrMsg != NULL) {
      sqlite3_result_error(context, pzErrMsg, -1);
      sqlite3DbFree(db, pzErrMsg);
    } else {
      sqlite3_result_error(context, sqlite3ErrStr(rc), -1);
    }
  }
}

Zetetic SQLCipher 4.x before 4.4.3 has a NULL pointer dereferencing issue related to sqlcipher_export in crypto.c and sqlite3StrICmp in sqlite3.c. This may allow an attacker to perform a remote denial of service attack. For example, an SQL injection can be used to execute the crafted SQL command sequence, which causes a segmentation fault.


void sqlcipher_exportFunc(sqlite3_context *context, int argc, sqlite3_value **argv) {
  sqlite3 *db = sqlite3_context_db_handle(context);
  const char* targetDb, *sourceDb;
  int targetDb_idx = 0;
  u64 saved_flags = db->flags;        /* Saved value of the db->flags */
  u32 saved_mDbFlags = db->mDbFlags;        /* Saved value of the db->mDbFlags */
  int saved_nChange = db->nChange;      /* Saved value of db->nChange */
  int saved_nTotalChange = db->nTotalChange; /* Saved value of db->nTotalChange */
  u8 saved_mTrace = db->mTrace;        /* Saved value of db->mTrace */
  int rc = SQLITE_OK;     /* Return code from service routines */
  char *zSql = NULL;         /* SQL statements */
  char *pzErrMsg = NULL;

  if(argc != 1 && argc != 2) {
    rc = SQLITE_ERROR;
    pzErrMsg = sqlite3_mprintf("invalid number of arguments (%d) passed to sqlcipher_export", argc);
    goto end_of_export;
  }

  if(sqlite3_value_type(argv[0]) == SQLITE_NULL) {
    rc = SQLITE_ERROR;
    pzErrMsg = sqlite3_mprintf("target database can't be NULL");
    goto end_of_export;
  }

  targetDb = (const char*) sqlite3_value_text(argv[0]);
  sourceDb = "main";

  if(argc == 2) {
    if(sqlite3_value_type(argv[1]) == SQLITE_NULL) {
      rc = SQLITE_ERROR;
      pzErrMsg = sqlite3_mprintf("target database can't be NULL");
      goto end_of_export;
    }
    sourceDb = (char *) sqlite3_value_text(argv[1]);
  }

  /* if the name of the target is not main, but the index returned is zero
     there is a mismatch and we should not proceed */
  targetDb_idx =  sqlcipher_find_db_index(db, targetDb);
  if(targetDb_idx == 0 && targetDb != NULL && sqlite3StrICmp("main", targetDb) != 0) {
    rc = SQLITE_ERROR;
    pzErrMsg = sqlite3_mprintf("unknown database %s", targetDb);
    goto end_of_export;
  }
  db->init.iDb = targetDb_idx;

  db->flags |= SQLITE_WriteSchema | SQLITE_IgnoreChecks;
  db->mDbFlags |= DBFLAG_PreferBuiltin | DBFLAG_Vacuum;
  db->flags &= ~(u64)(SQLITE_ForeignKeys | SQLITE_ReverseOrder | SQLITE_Defensive | SQLITE_CountRows);
  db->mTrace = 0;

  /* Query the schema of the main database. Create a mirror schema
  ** in the temporary database.
  */
  zSql = sqlite3_mprintf(
    "SELECT sql "
    "  FROM %s.sqlite_master WHERE type='table' AND name!='sqlite_sequence'"
    "   AND rootpage>0"
  , sourceDb);
  rc = (zSql == NULL) ? SQLITE_NOMEM : sqlcipher_execExecSql(db, &pzErrMsg, zSql);
  if( rc!=SQLITE_OK ) goto end_of_export;
  sqlite3_free(zSql);

  zSql = sqlite3_mprintf(
    "SELECT sql "
    "  FROM %s.sqlite_master WHERE sql LIKE 'CREATE INDEX %%' "
  , sourceDb);
  rc = (zSql == NULL) ? SQLITE_NOMEM : sqlcipher_execExecSql(db, &pzErrMsg, zSql);
  if( rc!=SQLITE_OK ) goto end_of_export;
  sqlite3_free(zSql);

  zSql = sqlite3_mprintf(
    "SELECT sql "
    "  FROM %s.sqlite_master WHERE sql LIKE 'CREATE UNIQUE INDEX %%'"
  , sourceDb);
  rc = (zSql == NULL) ? SQLITE_NOMEM : sqlcipher_execExecSql(db, &pzErrMsg, zSql);
  if( rc!=SQLITE_OK ) goto end_of_export;
  sqlite3_free(zSql);

  /* Loop through the tables in the main database. For each, do
  ** an "INSERT INTO rekey_db.xxx SELECT * FROM main.xxx;" to copy
  ** the contents to the temporary database.
  */
  zSql = sqlite3_mprintf(
    "SELECT 'INSERT INTO %s.' || quote(name) "
    "|| ' SELECT * FROM %s.' || quote(name) || ';'"
    "FROM %s.sqlite_master "
    "WHERE type = 'table' AND name!='sqlite_sequence' "
    "  AND rootpage>0"
  , targetDb, sourceDb, sourceDb);
  rc = (zSql == NULL) ? SQLITE_NOMEM : sqlcipher_execExecSql(db, &pzErrMsg, zSql);
  if( rc!=SQLITE_OK ) goto end_of_export;
  sqlite3_free(zSql);

  /* Copy over the contents of the sequence table
  */
  zSql = sqlite3_mprintf(
    "SELECT 'INSERT INTO %s.' || quote(name) "
    "|| ' SELECT * FROM %s.' || quote(name) || ';' "
    "FROM %s.sqlite_master WHERE name=='sqlite_sequence';"
  , targetDb, sourceDb, targetDb);
  rc = (zSql == NULL) ? SQLITE_NOMEM : sqlcipher_execExecSql(db, &pzErrMsg, zSql);
  if( rc!=SQLITE_OK ) goto end_of_export;
  sqlite3_free(zSql);

  /* Copy the triggers, views, and virtual tables from the main database
  ** over to the temporary database.  None of these objects has any
  ** associated storage, so all we have to do is copy their entries
  ** from the SQLITE_MASTER table.
  */
  zSql = sqlite3_mprintf(
    "INSERT INTO %s.sqlite_master "
    "  SELECT type, name, tbl_name, rootpage, sql"
    "    FROM %s.sqlite_master"
    "   WHERE type='view' OR type='trigger'"
    "      OR (type='table' AND rootpage=0)"
  , targetDb, sourceDb);
  rc = (zSql == NULL) ? SQLITE_NOMEM : sqlcipher_execSql(db, &pzErrMsg, zSql);
  if( rc!=SQLITE_OK ) goto end_of_export;
  sqlite3_free(zSql);

  zSql = NULL;
end_of_export:
  db->init.iDb = 0;
  db->flags = saved_flags;
  db->mDbFlags = saved_mDbFlags;
  db->nChange = saved_nChange;
  db->nTotalChange = saved_nTotalChange;
  db->mTrace = saved_mTrace;

  if(zSql) sqlite3_free(zSql);

  if(rc) {
    if(pzErrMsg != NULL) {
      sqlite3_result_error(context, pzErrMsg, -1);
      sqlite3DbFree(db, pzErrMsg);
    } else {
      sqlite3_result_error(context, sqlite3ErrStr(rc), -1);
    }
  }
}


auth/pam: Check for account/password expiry

See GHSA-6cp7-g972-w9m9. Thanks Youssef Rebahi-Gilbert (ysf) for
reporting the issue.

struct error_obj run_pam_auth(const char *username, char *password) {
    // PAM frees pam_response for us.
    struct pam_response *reply = malloc(sizeof(struct pam_response));
    if (reply == NULL) {
        struct error_obj ret_val;
        ret_val.status = 2;
        ret_val.func_name = "malloc";
        ret_val.error_msg = "Out of memory";
        return ret_val;
    }
    reply->resp = password;
    reply->resp_retcode = 0;

    const struct pam_conv local_conv = { conv_func, reply };
    pam_handle_t *local_auth = NULL;
    int status = pam_start("maddy", username, &local_conv, &local_auth);
    if (status != PAM_SUCCESS) {
        struct error_obj ret_val;
        ret_val.status = 2;
        ret_val.func_name = "pam_start";
        ret_val.error_msg = pam_strerror(local_auth, status);
        return ret_val;
    }

    status = pam_authenticate(local_auth, PAM_SILENT|PAM_DISALLOW_NULL_AUTHTOK);
    if (status != PAM_SUCCESS) {
        struct error_obj ret_val;
        if (status == PAM_AUTH_ERR || status == PAM_USER_UNKNOWN) {
            ret_val.status = 1;
        } else {
            ret_val.status = 2;
        }
        ret_val.func_name = "pam_authenticate";
        ret_val.error_msg = pam_strerror(local_auth, status);
        return ret_val;
    }

    status = pam_end(local_auth, status);
    if (status != PAM_SUCCESS) {
        struct error_obj ret_val;
        ret_val.status = 2;
        ret_val.func_name = "pam_end";
        ret_val.error_msg = pam_strerror(local_auth, status);
        return ret_val;
    }

    struct error_obj ret_val;
    ret_val.status = 0;
    ret_val.func_name = NULL;
    ret_val.error_msg = NULL;
    return ret_val;
}

Maddy Mail Server is an open source SMTP compatible email server. Versions of maddy prior to 0.5.4 do not implement password expiry or account expiry checking when authenticating using PAM. Users are advised to upgrade. Users unable to upgrade should manually remove expired accounts via existing filtering mechanisms.


struct error_obj run_pam_auth(const char *username, char *password) {
    // PAM frees pam_response for us.
    struct pam_response *reply = malloc(sizeof(struct pam_response));
    if (reply == NULL) {
        struct error_obj ret_val;
        ret_val.status = 2;
        ret_val.func_name = "malloc";
        ret_val.error_msg = "Out of memory";
        return ret_val;
    }
    reply->resp = password;
    reply->resp_retcode = 0;

    const struct pam_conv local_conv = { conv_func, reply };
    pam_handle_t *local_auth = NULL;
    int status = pam_start("maddy", username, &local_conv, &local_auth);
    if (status != PAM_SUCCESS) {
        struct error_obj ret_val;
        ret_val.status = 2;
        ret_val.func_name = "pam_start";
        ret_val.error_msg = pam_strerror(local_auth, status);
        return ret_val;
    }

    status = pam_authenticate(local_auth, PAM_SILENT|PAM_DISALLOW_NULL_AUTHTOK);
    if (status != PAM_SUCCESS) {
        struct error_obj ret_val;
        if (status == PAM_AUTH_ERR || status == PAM_USER_UNKNOWN) {
            ret_val.status = 1;
        } else {
            ret_val.status = 2;
        }
        ret_val.func_name = "pam_authenticate";
        ret_val.error_msg = pam_strerror(local_auth, status);
        return ret_val;
    }

    status = pam_acct_mgmt(local_auth, PAM_SILENT|PAM_DISALLOW_NULL_AUTHTOK);
    if (status != PAM_SUCCESS) {
        struct error_obj ret_val;
        if (status == PAM_AUTH_ERR || status == PAM_USER_UNKNOWN || status == PAM_NEW_AUTHTOK_REQD) {
            ret_val.status = 1;
        } else {
            ret_val.status = 2;
        }
        ret_val.func_name = "pam_acct_mgmt";
        ret_val.error_msg = pam_strerror(local_auth, status);
        return ret_val;
    }

    status = pam_end(local_auth, status);
    if (status != PAM_SUCCESS) {
        struct error_obj ret_val;
        ret_val.status = 2;
        ret_val.func_name = "pam_end";
        ret_val.error_msg = pam_strerror(local_auth, status);
        return ret_val;
    }

    struct error_obj ret_val;
    ret_val.status = 0;
    ret_val.func_name = NULL;
    ret_val.error_msg = NULL;
    return ret_val;
}