Commit message: Fix infinite recursion in wddx

Summary:
It wasn't checking for infinite recursion due to references or self-referential
objects. As it turns out closures always return themselves when converted to an
array. Raising a warning and returning is how PHP-src deals with this problem,
nothing special is done for closures.

Reviewed By: alexmalyshev

Differential Revision: D3465655

fbshipit-source-id: a42bc34d30cf4825faf33596139c0c05f8e4f5f1
CVE: CVE-2016-6875
Vulnerability description: Infinite recursion in wddx in Facebook HHVM before 3.15.0 allows attackers to have unspecified impact via unknown vectors.

Project: hhvm
File: ext_wddx.cpp

static String HHVM_FUNCTION(wddx_serialize_value, const Variant& var,
                            const Variant& comment) {
  auto wddxPacket = req::make<WddxPacket>(comment, false, false);
  wddxPacket->serialize_value(var);
  return wddxPacket->packet_end();
}

Project: hhvm
File: ext_wddx.cpp

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

Project: hhvm
File: ext_wddx.cpp

static Resource HHVM_FUNCTION(wddx_packet_start, const Variant& comment) {
  return Resource(req::make<WddxPacket>(comment, true, false));
}

Project: hhvm
File: ext_wddx.h

    size_t operator()(const ArrayOrObject data) const {
      return data.toOpaque();
    }

Project: hhvm
File: ext_wddx.cpp

WddxPacket::WddxPacket(const Variant& comment, bool manualPacket, bool sVar) :
                       m_packetClosed(false),
                       m_manualPacketCreation(manualPacket) {
  m_packetString.append("<wddxPacket version='1.0'>");

  if (!comment.isNull() && !sVar) {
    m_packetString.append("<header><comment>");
    m_packetString.append(comment);
    m_packetString.append("</comment></header>");
  } else {
    m_packetString.append("<header/>");
  }

  m_packetString.append("<data>");

  if (m_manualPacketCreation) {
    m_packetString.append("<struct>");
  }
}

Project: hhvm
File: ext_wddx.cpp

bool WddxPacket::recursiveAddVarImpl(const String& varName,
                                     const Variant& varVariant,
                                     bool hasVarTag,
                                     SeenContainers& seen) {
  bool isArray = varVariant.isArray();
  bool isObject = varVariant.isObject();

  if (isArray || isObject) {
    Array varAsArray;
    Object varAsObject;
    ArrayOrObject ptr;
    if (isArray) {
      varAsArray = varVariant.toArray();
      ptr = varAsArray.get();
    }
    if (isObject) {
      varAsObject = varVariant.toObject();
      varAsArray = varAsObject.toArray();
      ptr = varAsObject.get();
    }
    assert(!ptr.isNull());
    if (!seen.emplace(ptr).second) {
      raise_warning("recursion detected");
      return false;
    }
    SCOPE_EXIT { seen.erase(ptr); };

    if (hasVarTag) {
      m_packetString.append("<var name='");
      m_packetString.append(varName.data());
      m_packetString.append("'>");
    }

    int length = varAsArray.length();
    if (length > 0) {
      ArrayIter it = ArrayIter(varAsArray);
      if (it.first().isString()) isObject = true;
      if (isObject) {
        m_packetString.append("<struct>");
        if (!isArray) {
          m_packetString.append("<var name='php_class_name'><string>");
          m_packetString.append(varAsObject->getClassName());
          m_packetString.append("</string></var>");
        }
      } else {
        m_packetString.append("<array length='");
        m_packetString.append(std::to_string(length));
        m_packetString.append("'>");
      }
      for (ArrayIter it(varAsArray); it; ++it) {
        auto key = it.first();
        auto const& value = it.secondRef();
        recursiveAddVarImpl(key.toString(), value, isObject, seen);
      }
      if (isObject) {
        m_packetString.append("</struct>");
      }
      else {
        m_packetString.append("</array>");
      }
    }
    else {
      //empty object
      if (isObject) {
        m_packetString.append("<struct>");
        if (!isArray) {
          m_packetString.append("<var name='php_class_name'><string>");
          m_packetString.append(varAsObject->getClassName());
          m_packetString.append("</string></var>");
        }
        m_packetString.append("</struct>");
      }
    }
    if (hasVarTag) {
      m_packetString.append("</var>");
    }
    return true;
  }

  String varType = getDataTypeString(varVariant.getType());
  if (!getWddxEncoded(varType, "", varName, false).empty()) {
    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.append(
      getWddxEncoded(varType, varValue, varName, hasVarTag));
    return true;
  }

  return false;
}

Project: hhvm
File: ext_wddx.cpp

static String HHVM_FUNCTION(wddx_packet_end, const Resource& packet_id) {
  return cast<WddxPacket>(packet_id)->packet_end();
}

Project: hhvm
File: ext_wddx.cpp

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

Project: hhvm
File: ext_wddx.cpp

String WddxPacket::packet_end() {
  if (!m_packetClosed) {
    if (m_manualPacketCreation) {
      m_packetString.append("</struct>");
    }
    m_packetString.append("</data></wddxPacket>");
  }
  m_packetClosed = true;
  return m_packetString.detach();
}

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 = cast<WddxPacket>(packet_id);

  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

String WddxPacket::getWddxEncoded(const String& varType,
                                  const String& varValue,
                                  const String& varName,
                                  bool hasVarTag) {
  if (varType.compare("NULL") == 0 || 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

static TypedValue* serialize_vars_helper(ActRec* ar) {
  auto wddxPacket = req::make<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

bool WddxPacket::recursiveAddVar(const String& varName,
                                 const Variant& varVariant,
                                 bool hasVarTag) {
  SeenContainers seen;
  return recursiveAddVarImpl(varName, varVariant, hasVarTag, seen);
}

Project: hhvm
File: ext_wddx.cpp

String WddxPacket::wrapValue(const String& start,
                             const String& end,
                             const String& varValue,
                             const String& varName,
                             bool hasVarTag) {
  StringBuffer valueStr;

  if (hasVarTag) {
    valueStr.append("<var name='");
    valueStr.append(varName);
    valueStr.append("'>");
  }

  valueStr.append(start);
  valueStr.append(varValue);
  valueStr.append(end);

  if (hasVarTag) {
    valueStr.append("</var>");
  }

  return valueStr.detach();
}

Project: hhvm
File: ext_wddx.cpp

  void moduleInit() override {
    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();
  }