Project: uriparser (uriparser/uriparser), language: c++
Vulnerability instance: uriparser.cve-2021-46142
Sanitizer: address

================= Bug Report (1/1) ==================
## Source: GitHub Issue
## URL: https://github.com/uriparser/uriparser/issues/122
## Description:
Issue: uriparser/uriparser#122
Title: [CVE-2021-46142] uriNormalizeSyntax* may free stack memory in out-of-memory situation when handling URIs containing empty segments
State: closed
Created by: autofuzzoss
Created at: 2021-09-29 06:38:40+00:00
Labels: bug, security

Issue Body:
A bug was found within the uriparser. Though it might not be an intended use of the relevant API, the bug can still produce critical issues within a program using uriparser. It would be best if the affected logic is checked beforehand. 
The bug was found with a fuzzer based on the test-code"NormalizeSyntaxExMm" 

_*crash log*
```
==3440==ERROR: AddressSanitizer: SEGV on unknown address 0x0000004d9be0 (pc 0x00000041ca94 bp 0x000000000000 sp 0x7ffd2468e6e0 T0)
==3440==The signal is caused by a WRITE memory access.
    #0 0x41ca94 in __asan::Allocator::Deallocate(void*, unsigned long, unsigned long, __sanitizer::BufferedStackTrace*, __asan::AllocType)
    #1 0x493d41 in free 
    #2 0x4c6892 in (anonymous namespace)::countingFree(UriMemoryManagerStruct*, void*) 
    #3 0x7faf2e1ac4b2 in uriNormalizeSyntaxExMmA_ 
```

Steps to reproduce:
1. git clone https://github.com/uriparser/uriparser.git
2. cd uriparser & mkdir build & cd build
3. Build
   cmake -DCMAKE_BUILD_TYPE=Release -DURIPARSER_BUILD_DOCS:BOOL=OFF -DBUILD_SHARED_LIBS:BOOL=ON ..
   make -j8
4. Download the attached file(2.cpp)
5. Build TEST CODE (2.cpp)
   clang++ -g -fsanitize=address,fuzzer-no-link -o 2 2.cpp -I uriparser/include/ -I uriparser/ -Luriparser/build -luriparser
6. Run
   LD_LIBRARY_PATH=uriparser/build/ ./2
   
OS:ubuntu 18.04
[uriparser_poc2.tar.gz](https://github.com/uriparser/uriparser/files/7249420/uriparser_poc2.tar.gz)




Comments:
Comment by hartwork on 2021-09-29 12:26:40+00:00:
@afosscontact thanks for the detailed report, I will have a closer look.

---

Comment by hartwork on 2021-10-15 20:41:23+00:00:
@afosscontact confirming as a bug, fixed by pull request #124 (along with issue #121). Thanks for the report again! :pray: 

---

Comment by autofuzzoss on 2021-10-17 23:30:39+00:00:
Thank you for the fix
Is it possible to issue CVE ID?
If you are not possible, should you ask MITRE?

---

Comment by hartwork on 2021-10-18 10:05:55+00:00:
@afosscontact speaking for #121 and #122, I can request a CVE (or two) once the security impact of these two bugs is clear. If you could add a security assessment to the two issues — detailed or brief — that would help. Also, giving credit to your real name both inside uriparser and/or in the mitre submission form is still an option, if you're comfortable disclosing your name to either me and mitre (by e-mail) or everyone here. Or you submit to mitre and only they get your name or no one. I'd also be curious, what the context of this work of yours was, if you'd be up for sharing that with me off-list. Thanks in advance.

---

Comment by hartwork on 2021-10-21 12:17:05+00:00:
PS: @afosscontact the two bugs are closed as fixed now. I'm still happy to team up with you on the CVE subject as mentioned above.

Attachments:
https://github.com/uriparser/uriparser/files/7249420/uriparser_poc2.tar.gz

Build script:
#!/bin/bash -eu
# Minimized build script with only core build commands
set -eu
mkdir -p build
cd build
cmake -DCMAKE_BUILD_TYPE=Release -DURIPARSER_BUILD_DOCS:BOOL=OFF -DBUILD_SHARED_LIBS:BOOL=OFF -DURIPARSER_BUILD_TESTS:BOOL=OFF ..
make -j$(nproc)
make install -j$(nproc)

Dockerfile:
FROM hwiwonlee/secb.base:latest
RUN apt-get update && apt-get install -y make autoconf automake wget sudo libtool cmake
RUN git clone https://github.com/uriparser/uriparser uriparser
RUN git -C uriparser checkout d8722e7be3eff3593819f9980ed1837c00da3bc1
WORKDIR $SRC/uriparser
COPY build.sh $SRC/

Additional files:
filename: uri_dissect_query_malloc_fuzzer.cc
content: // Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Fuzz UriQuery.c:
//   uriDissectQueryMallocA
//   uriComposeQueryA

#include <cstddef>
#include <cstdint>
#include <string>
#include <utility>
#include <vector>

using std::string;
#include "uriparser/include/uriparser/Uri.h"

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {

  const string query(reinterpret_cast<const char *>(data), size);

  UriQueryListA *query_list = nullptr;
  int item_count = -1;

  const char *query_start = query.c_str();
  const char *query_end = query_start + size;

  // Break a query like "a=b&2=3" into key/value pairs.
  int result =
      uriDissectQueryMallocA(&query_list, &item_count, query_start, query_end);

  if (query_list == nullptr || result != URI_SUCCESS || item_count < 0)
    return 0;

  int chars_required = 0;
  if (uriComposeQueryCharsRequiredA(query_list, &chars_required) != URI_SUCCESS)
    return 0;

  if (!chars_required) {
    uriFreeQueryListA(query_list);
    return 0;
  }

  std::vector<char> buf(chars_required, 0);
  int written = -1;
  // Reverse the process of uriDissectQueryMallocA.
  result = uriComposeQueryA(buf.data(), query_list, chars_required, &written);

  uriFreeQueryListA(query_list);
  return 0;
}

filename: uri_free_fuzzer.cc
content: // Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <cstddef>
#include <cstdint>
#include <string>

#include "uriparser/include/uriparser/Uri.h"

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
  std::basic_string<char> fuzz_uri(reinterpret_cast<const char *>(data), size);
  UriParserStateA state;
  UriUriA uriA;
  state.uri = &uriA;
  uriParseUriA(&state, fuzz_uri.c_str());
  uriFreeUriMembersA(&uriA);
  return 0;
}

filename: uri_parse_fuzzer.cc
content: // Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <cstddef>
#include <cstring>
#include <string>
#include <vector>
#include <fuzzer/FuzzedDataProvider.h>

using std::string;
#include "uriparser/include/uriparser/Uri.h"
#include "uriparser/include/uriparser/UriIp4.h"

class UriParserA {
 public:
  UriParserA() { memset((void *)&uri_, 0, sizeof(uri_)); }
  ~UriParserA() { uriFreeUriMembersA(&uri_); }

  UriUriA *get_mutable_uri() { return &uri_; }
  UriUriA *get_uri() const { return const_cast<UriUriA *>(&uri_); }

 private:
  UriUriA uri_;
};

void Escapes(const string &uri) {
  const char *first = uri.c_str();
  // A new line char takes 6 char to encode.
  // Use a vector to make a C string.
  std::vector<char> buf1(uri.size() * 6 + 1);
  std::vector<char> buf2(uri.size() * 3 + 1);

  char *result;
  result = uriEscapeA(first, &buf1[0], URI_TRUE, URI_TRUE);
  result = uriEscapeA(first, &buf1[0], URI_FALSE, URI_TRUE);
  if (buf1.data()) uriUnescapeInPlaceA(&buf1[0]);

  result = uriEscapeA(first, &buf2[0], URI_TRUE, URI_FALSE);
  result = uriEscapeA(first, &buf2[0], URI_FALSE, URI_FALSE);
  if (buf2.data()) uriUnescapeInPlaceA(&buf2[0]);
}

void FileNames(const string &uri) {
  const size_t size = 8 + 3 * uri.size() + 1;
  std::vector<char> buf(size);

  uriUnixFilenameToUriStringA(uri.c_str(), &buf[0]);
  uriWindowsFilenameToUriStringA(uri.c_str(), &buf[0]);
  uriUriStringToUnixFilenameA(uri.c_str(), &buf[0]);
  uriUriStringToWindowsFilenameA(uri.c_str(), &buf[0]);
}

int uriParseIpFourAddressA(unsigned char *octetOutput, const char *first,
                           const char *afterLast);

void Ipv4(const string &s) {
  const char *cstr = s.c_str();
  unsigned char result[4] = {};
  uriParseIpFourAddressA(result, cstr, &cstr[s.size()]);
}

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {

  FuzzedDataProvider stream(data, size);
  bool domainRelative = stream.ConsumeBool();
  size_t uriSize = stream.remaining_bytes() / 2;

  const string uri1 = stream.ConsumeBytesAsString(uriSize);
  const string uri2 = stream.ConsumeRemainingBytesAsString();

  Escapes(uri1);
  Escapes(uri2);

  FileNames(uri1);
  FileNames(uri2);

  Ipv4(uri1);
  Ipv4(uri2);

  UriParserA parser1;
  UriParserStateA state1;
  state1.uri = parser1.get_mutable_uri();
  if (uriParseUriA(&state1, uri1.c_str()) != URI_SUCCESS)
    return 0;

  char buf[1024 * 8] = {0};
  int written = 0;
  uriToStringA(buf, state1.uri, sizeof(buf), &written);

  UriParserA parser2;
  UriParserStateA state2;
  state2.uri = parser2.get_mutable_uri();
  if (uriParseUriA(&state2, uri2.c_str()) != URI_SUCCESS)
    return 0;

  uriEqualsUriA(state1.uri, state2.uri);

  uriNormalizeSyntaxA(state1.uri);

  UriUriA absUri;
  uriAddBaseUriA(&absUri, state1.uri, state2.uri);
  uriFreeUriMembersA(&absUri);

  UriUriA relUri;
  uriRemoveBaseUriA(&relUri, state1.uri, state2.uri, domainRelative);
  uriFreeUriMembersA(&relUri);

  return 0;
}