Commit message: HTMLEncode strings in wddx_serialize_value()

Summary: Strings returned through wddx_serialize_value should be HTMLEncode()'d during
serialization.

Fixes #4283

{sync, type="child", parent="internal", parentrevid="1691695", parentrevfbid="1537976659780590", parentdiffid="5726084"}

Reviewed By: @JoelMarcey

Differential Revision: D1691695

Signature: t1:1691695:1416530595:722bfcdaf7c0dbee379bea886cd4c43d997ca7dd
CVE: CVE-2014-9714
CWE: CWE-79
Vulnerability description: Cross-site scripting (XSS) vulnerability in the WddxPacket::recursiveAddVar function in HHVM (aka the HipHop Virtual Machine) before 3.5.0 allows remote attackers to inject arbitrary web script or HTML via a crafted string to the wddx_serialize_value function.

Project: hhvm
File: ext_wddx.cpp

  virtual void moduleInit() {
    HHVM_FE(wddx_add_vars);
    HHVM_FE(wddx_packet_end);
    HHVM_FE(wddx_packet_start);
    HHVM_FE(wddx_serialize_value);
    HHVM_FE(wddx_serialize_vars);
    loadSystemlib();
  }

Project: hhvm
File: ext_wddx.cpp

static String HHVM_FUNCTION(wddx_serialize_value, const Variant& var,
                            const Variant& comment) {
  WddxPacket* wddxPacket = newres<WddxPacket>(comment, false, false);
  wddxPacket->serialize_value(var);
  const std::string packetString = wddxPacket->packet_end();
  return String(packetString);
}

Project: hhvm
File: ext_wddx.cpp

static String HHVM_FUNCTION(wddx_packet_end, const Resource& packet_id) {
  auto wddxPacket = packet_id.getTyped<WddxPacket>();
  std::string packetString = wddxPacket->packet_end();
  return String(packetString);
}

Project: hhvm
File: ext_wddx.cpp

bool WddxPacket::recursiveAddVar(const String& varName,
                                 const Variant& varVariant,
                                 bool hasVarTag) {

  bool isArray = varVariant.isArray();
  bool isObject = varVariant.isObject();

  if (isArray || isObject) {
    if (hasVarTag) {
      m_packetString += "<var name='";
      m_packetString += varName.data();
      m_packetString += "'>";
    }

    Array varAsArray;
    Object varAsObject = varVariant.toObject();
    if (isArray) varAsArray = varVariant.toArray();
    if (isObject) varAsArray = varAsObject.toArray();

    int length = varAsArray.length();
    if (length > 0) {
      ArrayIter it = ArrayIter(varAsArray);
      if (it.first().isString()) isObject = true;
      if (isObject) {
        m_packetString += "<struct>";
        if (!isArray) {
          m_packetString += "<var name='php_class_name'><string>";
          m_packetString += varAsObject->o_getClassName().c_str();
          m_packetString += "</string></var>";
        }
      } else {
        m_packetString += "<array length='";
        m_packetString += std::to_string(length);
        m_packetString += "'>";
      }
      for (ArrayIter it(varAsArray); it; ++it) {
        Variant key = it.first();
        Variant value = it.second();
        recursiveAddVar(key.toString(), value, isObject);
      }
      if (isObject) {
        m_packetString += "</struct>";
      }
      else {
        m_packetString += "</array>";
      }
    }
    else {
      //empty object
      if (isObject) {
        m_packetString += "<struct>";
        if (!isArray) {
          m_packetString += "<var name='php_class_name'><string>";
          m_packetString += varAsObject->o_getClassName().c_str();
          m_packetString += "</string></var>";
        }
        m_packetString += "</struct>";
      }
    }
    if (hasVarTag) {
      m_packetString += "</var>";
    }
    return true;
  }

  std::string varType = getDataTypeString(varVariant.getType()).data();
  if (!getWddxEncoded(varType, "", varName, false).empty()) {
    std::string varValue;
    if (varType.compare("boolean") == 0) {
      varValue = varVariant.toBoolean() ? "true" : "false";
    } else {
      varValue = StringUtil::HtmlEncode(varVariant.toString(),
                                        StringUtil::QuoteStyle::Double,
                                        "UTF-8", false, false).toCppString();
    }
    m_packetString += getWddxEncoded(varType, varValue, varName, hasVarTag);
    return true;
  }

  return false;
}

Project: hhvm
File: ext_wddx.cpp

std::string WddxPacket::wrapValue(const std::string& start,
                                  const std::string& end,
                                  const std::string& varValue,
                                  const String& varName,
                                  bool hasVarTag) {
  std::string startVar = "";
  std::string endVar = "";
  if (hasVarTag) {
    startVar += "<var name='";
    startVar += varName.data();
    startVar += "'>";
    endVar = "</var>";
  }
  return startVar + start + varValue + end + endVar;
}

Project: hhvm
File: ext_wddx.cpp

  wddxExtension() : Extension("wddx") {}

Project: hhvm
File: ext_wddx.cpp

bool WddxPacket::serialize_value(const Variant& varVariant) {
  return recursiveAddVar(empty_string_ref, varVariant, false);
}

Project: hhvm
File: ext_wddx.cpp

static TypedValue* serialize_vars_helper(ActRec* ar) {
  WddxPacket* wddxPacket = newres<WddxPacket>(empty_string_variant_ref,
                                              true, true);
  int start_index = 0;
  for (int i = start_index; i < ar->numArgs(); i++) {
    auto const tv = getArg(ar, i);
    find_var_recursive(tv, wddxPacket);
  }
  Variant packet = wddxPacket->packet_end();
  return arReturn(ar, std::move(packet));
}

Project: hhvm
File: ext_wddx.cpp

static TypedValue* HHVM_FN(wddx_serialize_vars)(ActRec* ar) {
  return serialize_vars_helper(ar);
}

Project: hhvm
File: ext_wddx.cpp

static TypedValue* add_vars_helper(ActRec* ar) {
  int start_index = 1;
  Resource packet_id = getArg<KindOfResource>(ar, 0);
  auto wddxPacket = packet_id.getTyped<WddxPacket>();

  for (int i = start_index; i < ar->numArgs(); i++) {
    auto const tv = getArg(ar, i);
    find_var_recursive(tv, wddxPacket);
  }
  return arReturn(ar, true);
}

Project: hhvm
File: ext_wddx.cpp

static TypedValue* HHVM_FN(wddx_add_vars)(ActRec* ar) {
  return add_vars_helper(ar);
}

Project: hhvm
File: ext_wddx.cpp

std::string WddxPacket::getWddxEncoded(const std::string& varType,
                                       const std::string& varValue,
                                       const String& varName,
                                       bool hasVarTag) {
  if (varType.compare("NULL") == 0) {
    return wrapValue("<null/>", "", "", varName, hasVarTag);
  }
  if (varType.compare("boolean") == 0) {
    return wrapValue("<boolean value='", "'/>", varValue, varName, hasVarTag);
  }
  if (varType.compare("integer") == 0 || varType.compare("double") == 0) {
    return wrapValue("<number>", "</number>", varValue, varName, hasVarTag);
  }
  if (varType.compare("string") == 0) {
    return wrapValue("<string>", "</string>", varValue, varName, hasVarTag);
  }
  return "";
}

Project: hhvm
File: ext_wddx.cpp

std::string WddxPacket::packet_end() {
  if (!m_packetClosed) {
    if (m_manualPacketCreation) {
      m_packetString += "</struct>";
    }
    m_packetString += "</data></wddxPacket>";
  }
  m_packetClosed = true;
  return m_packetString;
}

Project: hhvm
File: ext_wddx.cpp

bool WddxPacket::add_var(const String& varName, bool hasVarTag) {
  VarEnv* v = g_context->getVarEnv();
  if (!v) return false;
  Variant varVariant = *reinterpret_cast<Variant*>(v->lookup(varName.get()));
  return recursiveAddVar(varName, varVariant, hasVarTag);
}

Project: hhvm
File: ext_wddx.cpp

WddxPacket::WddxPacket(const Variant& comment, bool manualPacket, bool sVar) :
                       m_packetString(""), m_packetClosed(false),
                       m_manualPacketCreation(manualPacket) {
  std::string header = "<header/>";

  if (!comment.isNull() && !sVar) {
    std::string scomment = comment.toString().data();
    header = "<header><comment>" + scomment + "</comment></header>";
  }
  m_packetString = "<wddxPacket version='1.0'>" + header + "<data>";

  if (m_manualPacketCreation) {
    m_packetString = m_packetString + "<struct>";
  }
}

Project: hhvm
File: ext_wddx.cpp

void find_var_recursive(const TypedValue* tv, WddxPacket* wddxPacket) {
  if (tvIsString(tv)) {
    String var_name = tvCastToString(tv);
    wddxPacket->add_var(var_name, true);
  }
  if (tv->m_type == KindOfArray) {
    for (ArrayIter iter(tv->m_data.parr); iter; ++iter) {
      find_var_recursive(iter.secondRef().asTypedValue(), wddxPacket);
    }
  }
}

Project: hhvm
File: ext_wddx.cpp

static Resource HHVM_FUNCTION(wddx_packet_start, const Variant& comment) {
  auto wddxPacket = newres<WddxPacket>(comment, true, false);
  return Resource(wddxPacket);
}