[security][CVE-2018-6334] kill globals for file uploads in hhvm

Code should look at _GET, _POST, _FILES, etc. directly to avoid security concerns.

This included both `$GLOBALS` and `register_globals`-like behavior.

CVE-2018-6334

void rfc1867PostHandler(Transport* transport,
                        Array& post,
                        Array& files,
                        size_t content_length,
                        const void*& data, size_t& size,
                        const std::string boundary) {
  char *s=nullptr, *start_arr=nullptr;
  std::string array_index, abuf;
  char *lbuf=nullptr;
  int total_bytes=0, cancel_upload=0, is_arr_upload=0, array_len=0;
  int max_file_size=0, skip_upload=0, anonindex=0, is_anonymous;
  std::set<std::string> &uploaded_files = s_rfc1867_data->rfc1867UploadedFiles;
  multipart_buffer *mbuff;
  int fd=-1;
  void *event_extra_data = nullptr;
  unsigned int llen = 0;
  int upload_count = RuntimeOption::MaxFileUploads;

  /* Initialize the buffer */
  if (!(mbuff = multipart_buffer_new(transport,
                                     (const char *)data, size, boundary))) {
    Logger::Warning("Unable to initialize the input buffer");
    return;
  }

  /* Initialize $_FILES[] */
  s_rfc1867_data->rfc1867ProtectedVariables.clear();

  uploaded_files.clear();

  int (*php_rfc1867_callback)(apc_rfc1867_data *rfc1867ApcData,
                              unsigned int event, void *event_data,
                              void **extra) = s_rfc1867_data->rfc1867Callback;

  if (php_rfc1867_callback != nullptr) {
    multipart_event_start event_start;

    event_start.content_length = content_length;
    if (php_rfc1867_callback(&s_rfc1867_data->rfc1867ApcData,
                             MULTIPART_EVENT_START, &event_start,
                             &event_extra_data) == FAILURE) {
      goto fileupload_done;
    }
  }

  while (!multipart_buffer_eof(mbuff)) {
    std::string temp_filename;
    char buff[FILLUNIT];
    char *cd=nullptr,*param=nullptr,*filename=nullptr, *tmp=nullptr;
    size_t blen=0, wlen=0;
    off_t offset;

    header_list header;
    if (!multipart_buffer_headers(mbuff, header)) {
      goto fileupload_done;
    }

    if ((cd = php_mime_get_hdr_value(header, "Content-Disposition"))) {
      char *pair=nullptr;
      int end=0;

      while (isspace(*cd)) {
        ++cd;
      }

      while (*cd && (pair = php_ap_getword(&cd, ';')))
      {
        char *key=nullptr, *word = pair;

        while (isspace(*cd)) {
          ++cd;
        }

        if (strchr(pair, '=')) {
          key = php_ap_getword(&pair, '=');

          if (!strcasecmp(key, "name")) {
            if (param) {
              free(param);
            }
            param = php_ap_getword_conf(&pair);
          } else if (!strcasecmp(key, "filename")) {
            if (filename) {
              free(filename);
            }
            filename = php_ap_getword_conf(&pair);
          }
        }
        if (key) free(key);
        free(word);
      }

      /* Normal form variable, safe to read all data into memory */
      if (!filename && param) {
        unsigned int value_len;
        char *value = multipart_buffer_read_body(mbuff, &value_len);
        unsigned int new_val_len; /* Dummy variable */

        if (!value) {
          value = strdup("");
        }

        new_val_len = value_len;
        if (php_rfc1867_callback != nullptr) {
          multipart_event_formdata event_formdata;
          size_t newlength = new_val_len;

          event_formdata.post_bytes_processed = mbuff->read_post_bytes;
          event_formdata.name = param;
          event_formdata.value = &value;
          event_formdata.length = new_val_len;
          event_formdata.newlength = &newlength;
          if (php_rfc1867_callback(&s_rfc1867_data->rfc1867ApcData,
                                   MULTIPART_EVENT_FORMDATA, &event_formdata,
                                   &event_extra_data) == FAILURE) {
            free(param);
            free(value);
            continue;
          }
          new_val_len = newlength;
        }

        String val(value, new_val_len, CopyString);
        safe_php_register_variable(param, val, post, 0);

        if (!strcasecmp(param, "MAX_FILE_SIZE")) {
          max_file_size = atol(value);
        }

        free(param);
        free(value);
        continue;
      }

      /* If file_uploads=off, skip the file part */
      if (!RuntimeOption::EnableFileUploads) {
        skip_upload = 1;
      } else if (upload_count <= 0) {
        Logger::Warning(
          "Maximum number of allowable file uploads has been exceeded"
        );
        skip_upload = 1;
      }

      /* Return with an error if the posted data is garbled */
      if (!param && !filename) {
        Logger::Warning("File Upload Mime headers garbled");
        goto fileupload_done;
      }

      if (!param) {
        is_anonymous = 1;
        param = (char*)malloc(MAX_SIZE_ANONNAME);
        snprintf(param, MAX_SIZE_ANONNAME, "%u", anonindex++);
      } else {
        is_anonymous = 0;
      }

      /* New Rule: never repair potential malicious user input */
      if (!skip_upload) {
        tmp = param;
        long c = 0;

        while (*tmp) {
          if (*tmp == '[') {
            c++;
          } else if (*tmp == ']') {
            c--;
            if (tmp[1] && tmp[1] != '[') {
              skip_upload = 1;
              break;
            }
          }
          if (c < 0) {
            skip_upload = 1;
            break;
          }
          tmp++;
        }
      }

      total_bytes = cancel_upload = 0;

      if (!skip_upload) {
        /* Handle file */
        char path[PATH_MAX];

        // open a temporary file
        snprintf(path, sizeof(path), "%s/XXXXXX",
                 RuntimeOption::UploadTmpDir.c_str());
        fd = mkstemp(path);
        upload_count--;
        if (fd == -1) {
          Logger::Warning("Unable to open temporary file");
          Logger::Warning("File upload error - unable to create a "
                          "temporary file");
          cancel_upload = UPLOAD_ERROR_E;
        }
        temp_filename = path;
      }

      if (!skip_upload && php_rfc1867_callback != nullptr) {
        multipart_event_file_start event_file_start;

        event_file_start.post_bytes_processed = mbuff->read_post_bytes;
        event_file_start.name = param;
        event_file_start.filename = &filename;
        if (php_rfc1867_callback(&s_rfc1867_data->rfc1867ApcData,
                                 MULTIPART_EVENT_FILE_START,
                                 &event_file_start,
                                 &event_extra_data) == FAILURE) {
          if (!temp_filename.empty()) {
            if (cancel_upload != UPLOAD_ERROR_E) { /* file creation failed */
              close(fd);
              unlink(temp_filename.c_str());
            }
          }
          temp_filename="";
          free(param);
          free(filename);
          continue;
        }
      }

      if (skip_upload) {
        free(param);
        free(filename);
        continue;
      }

      if (strlen(filename) == 0) {
        Logger::Verbose("No file uploaded");
        cancel_upload = UPLOAD_ERROR_D;
      }

      offset = 0;
      end = 0;
      while (!cancel_upload &&
             (blen = multipart_buffer_read(mbuff, buff, sizeof(buff), &end)))
      {
        if (php_rfc1867_callback != nullptr) {
          multipart_event_file_data event_file_data;

          event_file_data.post_bytes_processed = mbuff->read_post_bytes;
          event_file_data.offset = offset;
          event_file_data.data = buff;
          event_file_data.length = blen;
          event_file_data.newlength = &blen;
          if (php_rfc1867_callback(&s_rfc1867_data->rfc1867ApcData,
                                   MULTIPART_EVENT_FILE_DATA,
                                   &event_file_data,
                                   &event_extra_data) == FAILURE) {
            cancel_upload = UPLOAD_ERROR_X;
            continue;
          }
        }

        if (VirtualHost::GetUploadMaxFileSize() > 0 &&
            total_bytes > VirtualHost::GetUploadMaxFileSize()) {
          Logger::Verbose("upload_max_filesize of %" PRId64 " bytes exceeded "
                          "- file [%s=%s] not saved",
                          VirtualHost::GetUploadMaxFileSize(),
                          param, filename);
          cancel_upload = UPLOAD_ERROR_A;
        } else if (max_file_size && (total_bytes > max_file_size)) {
          Logger::Verbose("MAX_FILE_SIZE of %d bytes exceeded - "
                          "file [%s=%s] not saved",
                          max_file_size, param, filename);
          cancel_upload = UPLOAD_ERROR_B;
        } else if (blen > 0) {

          wlen = folly::writeFull(fd, buff, blen);
          if (wlen < blen) {
            Logger::Verbose("Only %zd bytes were written, expected to "
                            "write %zd", wlen, blen);
            cancel_upload = UPLOAD_ERROR_F;
          } else {
            total_bytes += wlen;
          }

          offset += wlen;
        }
      }
      if (fd!=-1) { /* may not be initialized if file could not be created */
        close(fd);
      }
      if (!cancel_upload && !end) {
        Logger::Verbose("Missing mime boundary at the end of the data for "
                        "file %s", strlen(filename) > 0 ? filename : "");
        cancel_upload = UPLOAD_ERROR_C;
      }
      if (strlen(filename) > 0 && total_bytes == 0 && !cancel_upload) {
        Logger::Verbose("Uploaded file size 0 - file [%s=%s] not saved",
                        param, filename);
        cancel_upload = 5;
      }

      if (php_rfc1867_callback != nullptr) {
        multipart_event_file_end event_file_end;

        event_file_end.post_bytes_processed = mbuff->read_post_bytes;
        event_file_end.temp_filename = temp_filename.c_str();
        event_file_end.cancel_upload = cancel_upload;
        if (php_rfc1867_callback(&s_rfc1867_data->rfc1867ApcData,
                                 MULTIPART_EVENT_FILE_END,
                                 &event_file_end,
                                 &event_extra_data) == FAILURE) {
          cancel_upload = UPLOAD_ERROR_X;
        }
      }

      if (cancel_upload && cancel_upload != UPLOAD_ERROR_C) {
        if (!temp_filename.empty()) {
          if (cancel_upload != UPLOAD_ERROR_E) { /* file creation failed */
            unlink(temp_filename.c_str());
          }
        }
        temp_filename="";
      } else {
        s_rfc1867_data->rfc1867UploadedFiles.insert(temp_filename);
      }

      /* is_arr_upload is true when name of file upload field
       * ends in [.*]
       * start_arr is set to point to 1st [
       */
      is_arr_upload = (start_arr = strchr(param,'[')) &&
                      (param[strlen(param)-1] == ']');

      if (is_arr_upload) {
        array_len = strlen(start_arr);
        array_index = std::string(start_arr+1, array_len-2);
      }

      /* Add $foo_name */
      if (llen < strlen(param) + MAX_SIZE_OF_INDEX + 1) {
        llen = strlen(param);
        lbuf = (char *) realloc(lbuf, llen + MAX_SIZE_OF_INDEX + 1);
        llen += MAX_SIZE_OF_INDEX + 1;
      }

      if (is_arr_upload) {
        abuf = std::string(param, strlen(param)-array_len);
        snprintf(lbuf, llen, "%s_name[%s]",
                 abuf.c_str(), array_index.c_str());
      } else {
        snprintf(lbuf, llen, "%s_name", param);
      }

      /* The \ check should technically be needed for win32 systems only
       * where it is a valid path separator. However, IE in all it's wisdom
       * always sends the full path of the file on the user's filesystem,
       * which means that unless the user does basename() they get a bogus
       * file name. Until IE's user base drops to nill or problem is fixed
       * this code must remain enabled for all systems.
       */
      s = strrchr(filename, '\\');
      if ((tmp = strrchr(filename, '/')) > s) {
        s = tmp;
      }

      Array globals = php_globals_as_array();
      if (!is_anonymous) {
        if (s) {
          String val(s+1, strlen(s+1), CopyString);
          safe_php_register_variable(lbuf, val, globals, 0);
        } else {
          String val(filename, strlen(filename), CopyString);
          safe_php_register_variable(lbuf, val, globals, 0);
        }
      }

      /* Add $foo[name] */
      if (is_arr_upload) {
        snprintf(lbuf, llen, "%s[name][%s]",
                 abuf.c_str(), array_index.c_str());
      } else {
        snprintf(lbuf, llen, "%s[name]", param);
      }
      if (s) {
        String val(s+1, strlen(s+1), CopyString);
        safe_php_register_variable(lbuf, val, files, 0);
      } else {
        String val(filename, strlen(filename), CopyString);
        safe_php_register_variable(lbuf, val, files, 0);
      }
      free(filename);
      s = nullptr;

      /* Possible Content-Type: */
      if ((cancel_upload && cancel_upload != UPLOAD_ERROR_C) ||
          !(cd = php_mime_get_hdr_value(header, "Content-Type"))) {
        cd = "";
      } else {
        /* fix for Opera 6.01 */
        s = strchr(cd, ';');
        if (s != nullptr) {
          *s = '\0';
        }
      }

      /* Add $foo_type */
      if (is_arr_upload) {
        snprintf(lbuf, llen, "%s_type[%s]",
                 abuf.c_str(), array_index.c_str());
      } else {
        snprintf(lbuf, llen, "%s_type", param);
      }
      if (!is_anonymous) {
        String val(cd, strlen(cd), CopyString);
        safe_php_register_variable(lbuf, val, globals, 0);
      }

      /* Add $foo[type] */
      if (is_arr_upload) {
        snprintf(lbuf, llen, "%s[type][%s]",
                 abuf.c_str(), array_index.c_str());
      } else {
        snprintf(lbuf, llen, "%s[type]", param);
      }
      String val(cd, strlen(cd), CopyString);
      safe_php_register_variable(lbuf, val, files, 0);

      /* Restore Content-Type Header */
      if (s != nullptr) {
        *s = ';';
      }
      s = "";

      /* Initialize variables */
      add_protected_variable(param);

      Variant tempFileName(temp_filename);

      /* if param is of form xxx[.*] this will cut it to xxx */
      if (!is_anonymous) {
        safe_php_register_variable(param, tempFileName, globals, 1);
      }

      /* Add $foo[tmp_name] */
      if (is_arr_upload) {
        snprintf(lbuf, llen, "%s[tmp_name][%s]",
                 abuf.c_str(), array_index.c_str());
      } else {
        snprintf(lbuf, llen, "%s[tmp_name]", param);
      }
      add_protected_variable(lbuf);
      safe_php_register_variable(lbuf, tempFileName, files, 1);

      Variant file_size, error_type;

      error_type = cancel_upload;

      /* Add $foo[error] */
      if (cancel_upload) {
        file_size = 0;
      } else {
        file_size = total_bytes;
      }

      if (is_arr_upload) {
        snprintf(lbuf, llen, "%s[error][%s]",
                 abuf.c_str(), array_index.c_str());
      } else {
        snprintf(lbuf, llen, "%s[error]", param);
      }
      safe_php_register_variable(lbuf, error_type, files, 0);

      /* Add $foo_size */
      if (is_arr_upload) {
        snprintf(lbuf, llen, "%s_size[%s]",
                 abuf.c_str(), array_index.c_str());
      } else {
        snprintf(lbuf, llen, "%s_size", param);
      }
      if (!is_anonymous) {
        safe_php_register_variable(lbuf, file_size, globals, 0);
      }

      /* Add $foo[size] */
      if (is_arr_upload) {
        snprintf(lbuf, llen, "%s[size][%s]",
                 abuf.c_str(), array_index.c_str());
      } else {
        snprintf(lbuf, llen, "%s[size]", param);
      }
      safe_php_register_variable(lbuf, file_size, files, 0);
      free(param);
    }
  }
fileupload_done:
  data = mbuff->post_data;
  size = mbuff->post_size;
  if (php_rfc1867_callback != nullptr) {
    multipart_event_end event_end;

    event_end.post_bytes_processed = mbuff->read_post_bytes;
    php_rfc1867_callback(&s_rfc1867_data->rfc1867ApcData,
                         MULTIPART_EVENT_END, &event_end, &event_extra_data);
  }
  if (lbuf) free(lbuf);
  s_rfc1867_data->rfc1867ProtectedVariables.clear();
  if (mbuff->boundary_next) free(mbuff->boundary_next);
  if (mbuff->boundary) free(mbuff->boundary);
  if (mbuff->buffer) free(mbuff->buffer);
  if (mbuff) free(mbuff);
}

Multipart-file uploads call variables to be improperly registered in the global scope. In cases where variables are not declared explicitly before being used this can lead to unexpected behavior. This affects all supported versions of HHVM prior to the patch (3.25.1, 3.24.5, and 3.21.9 and below).


void rfc1867PostHandler(Transport* transport,
                        Array& post,
                        Array& files,
                        size_t content_length,
                        const void*& data, size_t& size,
                        const std::string boundary) {
  char *s=nullptr, *start_arr=nullptr;
  std::string array_index, abuf;
  char *lbuf=nullptr;
  int total_bytes=0, cancel_upload=0, is_arr_upload=0, array_len=0;
  int max_file_size=0, skip_upload=0, anonindex=0;
  std::set<std::string> &uploaded_files = s_rfc1867_data->rfc1867UploadedFiles;
  multipart_buffer *mbuff;
  int fd=-1;
  void *event_extra_data = nullptr;
  unsigned int llen = 0;
  int upload_count = RuntimeOption::MaxFileUploads;

  /* Initialize the buffer */
  if (!(mbuff = multipart_buffer_new(transport,
                                     (const char *)data, size, boundary))) {
    Logger::Warning("Unable to initialize the input buffer");
    return;
  }

  /* Initialize $_FILES[] */
  s_rfc1867_data->rfc1867ProtectedVariables.clear();

  uploaded_files.clear();

  int (*php_rfc1867_callback)(apc_rfc1867_data *rfc1867ApcData,
                              unsigned int event, void *event_data,
                              void **extra) = s_rfc1867_data->rfc1867Callback;

  if (php_rfc1867_callback != nullptr) {
    multipart_event_start event_start;

    event_start.content_length = content_length;
    if (php_rfc1867_callback(&s_rfc1867_data->rfc1867ApcData,
                             MULTIPART_EVENT_START, &event_start,
                             &event_extra_data) == FAILURE) {
      goto fileupload_done;
    }
  }

  while (!multipart_buffer_eof(mbuff)) {
    std::string temp_filename;
    char buff[FILLUNIT];
    char *cd=nullptr,*param=nullptr,*filename=nullptr, *tmp=nullptr;
    size_t blen=0, wlen=0;
    off_t offset;

    header_list header;
    if (!multipart_buffer_headers(mbuff, header)) {
      goto fileupload_done;
    }

    if ((cd = php_mime_get_hdr_value(header, "Content-Disposition"))) {
      char *pair=nullptr;
      int end=0;

      while (isspace(*cd)) {
        ++cd;
      }

      while (*cd && (pair = php_ap_getword(&cd, ';')))
      {
        char *key=nullptr, *word = pair;

        while (isspace(*cd)) {
          ++cd;
        }

        if (strchr(pair, '=')) {
          key = php_ap_getword(&pair, '=');

          if (!strcasecmp(key, "name")) {
            if (param) {
              free(param);
            }
            param = php_ap_getword_conf(&pair);
          } else if (!strcasecmp(key, "filename")) {
            if (filename) {
              free(filename);
            }
            filename = php_ap_getword_conf(&pair);
          }
        }
        if (key) free(key);
        free(word);
      }

      /* Normal form variable, safe to read all data into memory */
      if (!filename && param) {
        unsigned int value_len;
        char *value = multipart_buffer_read_body(mbuff, &value_len);
        unsigned int new_val_len; /* Dummy variable */

        if (!value) {
          value = strdup("");
        }

        new_val_len = value_len;
        if (php_rfc1867_callback != nullptr) {
          multipart_event_formdata event_formdata;
          size_t newlength = new_val_len;

          event_formdata.post_bytes_processed = mbuff->read_post_bytes;
          event_formdata.name = param;
          event_formdata.value = &value;
          event_formdata.length = new_val_len;
          event_formdata.newlength = &newlength;
          if (php_rfc1867_callback(&s_rfc1867_data->rfc1867ApcData,
                                   MULTIPART_EVENT_FORMDATA, &event_formdata,
                                   &event_extra_data) == FAILURE) {
            free(param);
            free(value);
            continue;
          }
          new_val_len = newlength;
        }

        String val(value, new_val_len, CopyString);
        safe_php_register_variable(param, val, post, 0);

        if (!strcasecmp(param, "MAX_FILE_SIZE")) {
          max_file_size = atol(value);
        }

        free(param);
        free(value);
        continue;
      }

      /* If file_uploads=off, skip the file part */
      if (!RuntimeOption::EnableFileUploads) {
        skip_upload = 1;
      } else if (upload_count <= 0) {
        Logger::Warning(
          "Maximum number of allowable file uploads has been exceeded"
        );
        skip_upload = 1;
      }

      /* Return with an error if the posted data is garbled */
      if (!param && !filename) {
        Logger::Warning("File Upload Mime headers garbled");
        goto fileupload_done;
      }

      if (!param) {
        param = (char*)malloc(MAX_SIZE_ANONNAME);
        snprintf(param, MAX_SIZE_ANONNAME, "%u", anonindex++);
      }

      /* New Rule: never repair potential malicious user input */
      if (!skip_upload) {
        tmp = param;
        long c = 0;

        while (*tmp) {
          if (*tmp == '[') {
            c++;
          } else if (*tmp == ']') {
            c--;
            if (tmp[1] && tmp[1] != '[') {
              skip_upload = 1;
              break;
            }
          }
          if (c < 0) {
            skip_upload = 1;
            break;
          }
          tmp++;
        }
      }

      total_bytes = cancel_upload = 0;

      if (!skip_upload) {
        /* Handle file */
        char path[PATH_MAX];

        // open a temporary file
        snprintf(path, sizeof(path), "%s/XXXXXX",
                 RuntimeOption::UploadTmpDir.c_str());
        fd = mkstemp(path);
        upload_count--;
        if (fd == -1) {
          Logger::Warning("Unable to open temporary file");
          Logger::Warning("File upload error - unable to create a "
                          "temporary file");
          cancel_upload = UPLOAD_ERROR_E;
        }
        temp_filename = path;
      }

      if (!skip_upload && php_rfc1867_callback != nullptr) {
        multipart_event_file_start event_file_start;

        event_file_start.post_bytes_processed = mbuff->read_post_bytes;
        event_file_start.name = param;
        event_file_start.filename = &filename;
        if (php_rfc1867_callback(&s_rfc1867_data->rfc1867ApcData,
                                 MULTIPART_EVENT_FILE_START,
                                 &event_file_start,
                                 &event_extra_data) == FAILURE) {
          if (!temp_filename.empty()) {
            if (cancel_upload != UPLOAD_ERROR_E) { /* file creation failed */
              close(fd);
              unlink(temp_filename.c_str());
            }
          }
          temp_filename="";
          free(param);
          free(filename);
          continue;
        }
      }

      if (skip_upload) {
        free(param);
        free(filename);
        continue;
      }

      if (strlen(filename) == 0) {
        Logger::Verbose("No file uploaded");
        cancel_upload = UPLOAD_ERROR_D;
      }

      offset = 0;
      end = 0;
      while (!cancel_upload &&
             (blen = multipart_buffer_read(mbuff, buff, sizeof(buff), &end)))
      {
        if (php_rfc1867_callback != nullptr) {
          multipart_event_file_data event_file_data;

          event_file_data.post_bytes_processed = mbuff->read_post_bytes;
          event_file_data.offset = offset;
          event_file_data.data = buff;
          event_file_data.length = blen;
          event_file_data.newlength = &blen;
          if (php_rfc1867_callback(&s_rfc1867_data->rfc1867ApcData,
                                   MULTIPART_EVENT_FILE_DATA,
                                   &event_file_data,
                                   &event_extra_data) == FAILURE) {
            cancel_upload = UPLOAD_ERROR_X;
            continue;
          }
        }

        if (VirtualHost::GetUploadMaxFileSize() > 0 &&
            total_bytes > VirtualHost::GetUploadMaxFileSize()) {
          Logger::Verbose("upload_max_filesize of %" PRId64 " bytes exceeded "
                          "- file [%s=%s] not saved",
                          VirtualHost::GetUploadMaxFileSize(),
                          param, filename);
          cancel_upload = UPLOAD_ERROR_A;
        } else if (max_file_size && (total_bytes > max_file_size)) {
          Logger::Verbose("MAX_FILE_SIZE of %d bytes exceeded - "
                          "file [%s=%s] not saved",
                          max_file_size, param, filename);
          cancel_upload = UPLOAD_ERROR_B;
        } else if (blen > 0) {

          wlen = folly::writeFull(fd, buff, blen);
          if (wlen < blen) {
            Logger::Verbose("Only %zd bytes were written, expected to "
                            "write %zd", wlen, blen);
            cancel_upload = UPLOAD_ERROR_F;
          } else {
            total_bytes += wlen;
          }

          offset += wlen;
        }
      }
      if (fd!=-1) { /* may not be initialized if file could not be created */
        close(fd);
      }
      if (!cancel_upload && !end) {
        Logger::Verbose("Missing mime boundary at the end of the data for "
                        "file %s", strlen(filename) > 0 ? filename : "");
        cancel_upload = UPLOAD_ERROR_C;
      }
      if (strlen(filename) > 0 && total_bytes == 0 && !cancel_upload) {
        Logger::Verbose("Uploaded file size 0 - file [%s=%s] not saved",
                        param, filename);
        cancel_upload = 5;
      }

      if (php_rfc1867_callback != nullptr) {
        multipart_event_file_end event_file_end;

        event_file_end.post_bytes_processed = mbuff->read_post_bytes;
        event_file_end.temp_filename = temp_filename.c_str();
        event_file_end.cancel_upload = cancel_upload;
        if (php_rfc1867_callback(&s_rfc1867_data->rfc1867ApcData,
                                 MULTIPART_EVENT_FILE_END,
                                 &event_file_end,
                                 &event_extra_data) == FAILURE) {
          cancel_upload = UPLOAD_ERROR_X;
        }
      }

      if (cancel_upload && cancel_upload != UPLOAD_ERROR_C) {
        if (!temp_filename.empty()) {
          if (cancel_upload != UPLOAD_ERROR_E) { /* file creation failed */
            unlink(temp_filename.c_str());
          }
        }
        temp_filename="";
      } else {
        s_rfc1867_data->rfc1867UploadedFiles.insert(temp_filename);
      }

      /* is_arr_upload is true when name of file upload field
       * ends in [.*]
       * start_arr is set to point to 1st [
       */
      is_arr_upload = (start_arr = strchr(param,'[')) &&
                      (param[strlen(param)-1] == ']');

      if (is_arr_upload) {
        array_len = strlen(start_arr);
        array_index = std::string(start_arr+1, array_len-2);
      }

      /* Add $foo_name */
      if (llen < strlen(param) + MAX_SIZE_OF_INDEX + 1) {
        llen = strlen(param);
        lbuf = (char *) realloc(lbuf, llen + MAX_SIZE_OF_INDEX + 1);
        llen += MAX_SIZE_OF_INDEX + 1;
      }

      if (is_arr_upload) {
        abuf = std::string(param, strlen(param)-array_len);
        snprintf(lbuf, llen, "%s_name[%s]",
                 abuf.c_str(), array_index.c_str());
      } else {
        snprintf(lbuf, llen, "%s_name", param);
      }

      /* The \ check should technically be needed for win32 systems only
       * where it is a valid path separator. However, IE in all it's wisdom
       * always sends the full path of the file on the user's filesystem,
       * which means that unless the user does basename() they get a bogus
       * file name. Until IE's user base drops to nill or problem is fixed
       * this code must remain enabled for all systems.
       */
      s = strrchr(filename, '\\');
      if ((tmp = strrchr(filename, '/')) > s) {
        s = tmp;
      }

      /* Add $foo[name] */
      if (is_arr_upload) {
        snprintf(lbuf, llen, "%s[name][%s]",
                 abuf.c_str(), array_index.c_str());
      } else {
        snprintf(lbuf, llen, "%s[name]", param);
      }
      if (s) {
        String val(s+1, strlen(s+1), CopyString);
        safe_php_register_variable(lbuf, val, files, 0);
      } else {
        String val(filename, strlen(filename), CopyString);
        safe_php_register_variable(lbuf, val, files, 0);
      }
      free(filename);
      s = nullptr;

      /* Possible Content-Type: */
      if ((cancel_upload && cancel_upload != UPLOAD_ERROR_C) ||
          !(cd = php_mime_get_hdr_value(header, "Content-Type"))) {
        cd = "";
      } else {
        /* fix for Opera 6.01 */
        s = strchr(cd, ';');
        if (s != nullptr) {
          *s = '\0';
        }
      }

      /* Add $foo[type] */
      if (is_arr_upload) {
        snprintf(lbuf, llen, "%s[type][%s]",
                 abuf.c_str(), array_index.c_str());
      } else {
        snprintf(lbuf, llen, "%s[type]", param);
      }
      String val(cd, strlen(cd), CopyString);
      safe_php_register_variable(lbuf, val, files, 0);

      /* Restore Content-Type Header */
      if (s != nullptr) {
        *s = ';';
      }
      s = "";

      /* Initialize variables */
      add_protected_variable(param);

      Variant tempFileName(temp_filename);

      /* Add $foo[tmp_name] */
      if (is_arr_upload) {
        snprintf(lbuf, llen, "%s[tmp_name][%s]",
                 abuf.c_str(), array_index.c_str());
      } else {
        snprintf(lbuf, llen, "%s[tmp_name]", param);
      }
      add_protected_variable(lbuf);
      safe_php_register_variable(lbuf, tempFileName, files, 1);

      Variant file_size, error_type;

      error_type = cancel_upload;

      /* Add $foo[error] */
      if (cancel_upload) {
        file_size = 0;
      } else {
        file_size = total_bytes;
      }

      if (is_arr_upload) {
        snprintf(lbuf, llen, "%s[error][%s]",
                 abuf.c_str(), array_index.c_str());
      } else {
        snprintf(lbuf, llen, "%s[error]", param);
      }
      safe_php_register_variable(lbuf, error_type, files, 0);

      /* Add $foo[size] */
      if (is_arr_upload) {
        snprintf(lbuf, llen, "%s[size][%s]",
                 abuf.c_str(), array_index.c_str());
      } else {
        snprintf(lbuf, llen, "%s[size]", param);
      }
      safe_php_register_variable(lbuf, file_size, files, 0);
      free(param);
    }
  }
fileupload_done:
  data = mbuff->post_data;
  size = mbuff->post_size;
  if (php_rfc1867_callback != nullptr) {
    multipart_event_end event_end;

    event_end.post_bytes_processed = mbuff->read_post_bytes;
    php_rfc1867_callback(&s_rfc1867_data->rfc1867ApcData,
                         MULTIPART_EVENT_END, &event_end, &event_extra_data);
  }
  if (lbuf) free(lbuf);
  s_rfc1867_data->rfc1867ProtectedVariables.clear();
  if (mbuff->boundary_next) free(mbuff->boundary_next);
  if (mbuff->boundary) free(mbuff->boundary);
  if (mbuff->buffer) free(mbuff->buffer);
  if (mbuff) free(mbuff);
}


static ssize_t parse8BIM(Image *ifile, Image *ofile)
{
  char
    brkused,
    quoted,
    *line,
    *token,
    *newstr,
    *name;

  int
    state,
    next;

  unsigned char
    dataset;

  unsigned int
    recnum;

  int
    inputlen = MagickPathExtent;

  MagickOffsetType
    savedpos,
    currentpos;

  ssize_t
    savedolen = 0L,
    outputlen = 0L;

  TokenInfo
    *token_info;

  dataset = 0;
  recnum = 0;
  line = (char *) AcquireQuantumMemory((size_t) inputlen,sizeof(*line));
  if (line == (char *) NULL)
    return(-1);
  newstr = name = token = (char *) NULL;
  savedpos = 0;
  token_info=AcquireTokenInfo();
  while (super_fgets(&line,&inputlen,ifile)!=NULL)
  {
    state=0;
    next=0;

    token=(char *) AcquireQuantumMemory((size_t) inputlen,sizeof(*token));
    if (token == (char *) NULL)
      break;
    newstr=(char *) AcquireQuantumMemory((size_t) inputlen,sizeof(*newstr));
    if (newstr == (char *) NULL)
      break;
    while (Tokenizer(token_info,0,token,(size_t) inputlen,line,"","=","\"",0,
           &brkused,&next,&quoted)==0)
    {
      if (state == 0)
        {
          int
            state,
            next;

          char
            brkused,
            quoted;

          state=0;
          next=0;
          while (Tokenizer(token_info,0,newstr,(size_t) inputlen,token,"","#",
            "", 0,&brkused,&next,&quoted)==0)
          {
            switch (state)
            {
              case 0:
                if (strcmp(newstr,"8BIM")==0)
                  dataset = 255;
                else
                  dataset = (unsigned char) StringToLong(newstr);
                break;
              case 1:
                recnum = (unsigned int) StringToUnsignedLong(newstr);
                break;
              case 2:
                name=(char *) AcquireQuantumMemory(strlen(newstr)+MagickPathExtent,
                  sizeof(*name));
                if (name)
                  (void) strcpy(name,newstr);
                break;
            }
            state++;
          }
        }
      else
        if (state == 1)
          {
            int
              next;

            ssize_t
              len;

            char
              brkused,
              quoted;

            next=0;
            len = (ssize_t) strlen(token);
            while (Tokenizer(token_info,0,newstr,(size_t) inputlen,token,"","&",
              "",0,&brkused,&next,&quoted)==0)
            {
              if (brkused && next > 0)
                {
                  char
                    *s = &token[next-1];

                  len -= (ssize_t) convertHTMLcodes(s,(int) strlen(s));
                }
            }

            if (dataset == 255)
              {
                unsigned char
                  nlen = 0;

                int
                  i;

                if (savedolen > 0)
                  {
                    MagickOffsetType
                      offset;

                    ssize_t diff = outputlen - savedolen;
                    currentpos = TellBlob(ofile);
                    if (currentpos < 0)
                      return(-1);
                    offset=SeekBlob(ofile,savedpos,SEEK_SET);
                    if (offset < 0)
                      return(-1);
                    (void) WriteBlobMSBLong(ofile,(unsigned int) diff);
                    offset=SeekBlob(ofile,currentpos,SEEK_SET);
                    if (offset < 0)
                      return(-1);
                    savedolen = 0L;
                  }
                if (outputlen & 1)
                  {
                    (void) WriteBlobByte(ofile,0x00);
                    outputlen++;
                  }
                (void) WriteBlobString(ofile,"8BIM");
                (void) WriteBlobMSBShort(ofile,(unsigned short) recnum);
                outputlen += 6;
                if (name)
                  nlen = (unsigned char) strlen(name);
                (void) WriteBlobByte(ofile,nlen);
                outputlen++;
                for (i=0; i<nlen; i++)
                  (void) WriteBlobByte(ofile,(unsigned char) name[i]);
                outputlen += nlen;
                if ((nlen & 0x01) == 0)
                  {
                    (void) WriteBlobByte(ofile,0x00);
                    outputlen++;
                  }
                if (recnum != IPTC_ID)
                  {
                    (void) WriteBlobMSBLong(ofile, (unsigned int) len);
                    outputlen += 4;

                    next=0;
                    outputlen += len;
                    while (len--)
                      (void) WriteBlobByte(ofile,(unsigned char) token[next++]);

                    if (outputlen & 1)
                      {
                        (void) WriteBlobByte(ofile,0x00);
                        outputlen++;
                      }
                  }
                else
                  {
                    /* patch in a fake length for now and fix it later */
                    savedpos = TellBlob(ofile);
                    if (savedpos < 0)
                      return(-1);
                    (void) WriteBlobMSBLong(ofile,0xFFFFFFFFU);
                    outputlen += 4;
                    savedolen = outputlen;
                  }
              }
            else
              {
                if (len <= 0x7FFF)
                  {
                    (void) WriteBlobByte(ofile,0x1c);
                    (void) WriteBlobByte(ofile,(unsigned char) dataset);
                    (void) WriteBlobByte(ofile,(unsigned char) (recnum & 0xff));
                    (void) WriteBlobMSBShort(ofile,(unsigned short) len);
                    outputlen += 5;
                    next=0;
                    outputlen += len;
                    while (len--)
                      (void) WriteBlobByte(ofile,(unsigned char) token[next++]);
                  }
              }
          }
      state++;
    }
    if (token != (char *) NULL)
      token=DestroyString(token);
    if (newstr != (char *) NULL)
      newstr=DestroyString(newstr);
    if (name != (char *) NULL)
      name=DestroyString(name);
  }
  token_info=DestroyTokenInfo(token_info);
  if (token != (char *) NULL)
    token=DestroyString(token);
  if (newstr != (char *) NULL)
    newstr=DestroyString(newstr);
  if (name != (char *) NULL)
    name=DestroyString(name);
  line=DestroyString(line);
  if (savedolen > 0)
    {
      MagickOffsetType
        offset;

      ssize_t diff = outputlen - savedolen;

      currentpos = TellBlob(ofile);
      if (currentpos < 0)
        return(-1);
      offset=SeekBlob(ofile,savedpos,SEEK_SET);
      if (offset < 0)
        return(-1);
      (void) WriteBlobMSBLong(ofile,(unsigned int) diff);
      offset=SeekBlob(ofile,currentpos,SEEK_SET);
      if (offset < 0)
        return(-1);
      savedolen = 0L;
    }
  return outputlen;
}

coders/meta.c in ImageMagick allows remote attackers to cause a denial of service (out-of-bounds read) via a crafted file.


static ssize_t parse8BIM(Image *ifile, Image *ofile)
{
  char
    brkused,
    quoted,
    *line,
    *token,
    *newstr,
    *name;

  int
    state,
    next;

  unsigned char
    dataset;

  unsigned int
    recnum;

  int
    inputlen = MagickPathExtent;

  MagickOffsetType
    savedpos,
    currentpos;

  ssize_t
    savedolen = 0L,
    outputlen = 0L;

  TokenInfo
    *token_info;

  dataset = 0;
  recnum = 0;
  line = (char *) AcquireQuantumMemory((size_t) inputlen,sizeof(*line));
  if (line == (char *) NULL)
    return(-1);
  newstr = name = token = (char *) NULL;
  savedpos = 0;
  token_info=AcquireTokenInfo();
  while (super_fgets(&line,&inputlen,ifile)!=NULL)
  {
    state=0;
    next=0;

    token=(char *) AcquireQuantumMemory((size_t) inputlen,sizeof(*token));
    if (token == (char *) NULL)
      break;
    newstr=(char *) AcquireQuantumMemory((size_t) inputlen,sizeof(*newstr));
    if (newstr == (char *) NULL)
      break;
    while (Tokenizer(token_info,0,token,(size_t) inputlen,line,"","=","\"",0,
           &brkused,&next,&quoted)==0)
    {
      if (state == 0)
        {
          int
            state,
            next;

          char
            brkused,
            quoted;

          state=0;
          next=0;
          while (Tokenizer(token_info,0,newstr,(size_t) inputlen,token,"","#",
            "", 0,&brkused,&next,&quoted)==0)
          {
            switch (state)
            {
              case 0:
                if (strcmp(newstr,"8BIM")==0)
                  dataset = 255;
                else
                  dataset = (unsigned char) StringToLong(newstr);
                break;
              case 1:
                recnum = (unsigned int) StringToUnsignedLong(newstr);
                break;
              case 2:
                name=(char *) AcquireQuantumMemory(strlen(newstr)+MagickPathExtent,
                  sizeof(*name));
                if (name)
                  (void) strcpy(name,newstr);
                break;
            }
            state++;
          }
        }
      else
        if (state == 1)
          {
            int
              next;

            ssize_t
              len;

            char
              brkused,
              quoted;

            next=0;
            len = (ssize_t) strlen(token);
            while (Tokenizer(token_info,0,newstr,(size_t) inputlen,token,"","&",
              "",0,&brkused,&next,&quoted)==0)
            {
              if (brkused && next > 0)
                {
                  char
                    *s = &token[next-1];

                  len -= (ssize_t) convertHTMLcodes(s,(int) strlen(s));
                }
            }

            if (dataset == 255)
              {
                unsigned char
                  nlen = 0;

                int
                  i;

                if (savedolen > 0)
                  {
                    MagickOffsetType
                      offset;

                    ssize_t diff = outputlen - savedolen;
                    currentpos = TellBlob(ofile);
                    if (currentpos < 0)
                      return(-1);
                    offset=SeekBlob(ofile,savedpos,SEEK_SET);
                    if (offset < 0)
                      return(-1);
                    (void) WriteBlobMSBLong(ofile,(unsigned int) diff);
                    offset=SeekBlob(ofile,currentpos,SEEK_SET);
                    if (offset < 0)
                      return(-1);
                    savedolen = 0L;
                  }
                if (outputlen & 1)
                  {
                    (void) WriteBlobByte(ofile,0x00);
                    outputlen++;
                  }
                (void) WriteBlobString(ofile,"8BIM");
                (void) WriteBlobMSBShort(ofile,(unsigned short) recnum);
                outputlen += 6;
                if (name)
                  nlen = (unsigned char) strlen(name);
                (void) WriteBlobByte(ofile,nlen);
                outputlen++;
                for (i=0; i<nlen; i++)
                  (void) WriteBlobByte(ofile,(unsigned char) name[i]);
                outputlen += nlen;
                if ((nlen & 0x01) == 0)
                  {
                    (void) WriteBlobByte(ofile,0x00);
                    outputlen++;
                  }
                if (recnum != IPTC_ID)
                  {
                    (void) WriteBlobMSBLong(ofile, (unsigned int) len);
                    outputlen += 4;

                    next=0;
                    outputlen += len;
                    while (len-- > 0)
                      (void) WriteBlobByte(ofile,(unsigned char) token[next++]);

                    if (outputlen & 1)
                      {
                        (void) WriteBlobByte(ofile,0x00);
                        outputlen++;
                      }
                  }
                else
                  {
                    /* patch in a fake length for now and fix it later */
                    savedpos = TellBlob(ofile);
                    if (savedpos < 0)
                      return(-1);
                    (void) WriteBlobMSBLong(ofile,0xFFFFFFFFU);
                    outputlen += 4;
                    savedolen = outputlen;
                  }
              }
            else
              {
                if (len <= 0x7FFF)
                  {
                    (void) WriteBlobByte(ofile,0x1c);
                    (void) WriteBlobByte(ofile,(unsigned char) dataset);
                    (void) WriteBlobByte(ofile,(unsigned char) (recnum & 0xff));
                    (void) WriteBlobMSBShort(ofile,(unsigned short) len);
                    outputlen += 5;
                    next=0;
                    outputlen += len;
                    while (len-- > 0)
                      (void) WriteBlobByte(ofile,(unsigned char) token[next++]);
                  }
              }
          }
      state++;
    }
    if (token != (char *) NULL)
      token=DestroyString(token);
    if (newstr != (char *) NULL)
      newstr=DestroyString(newstr);
    if (name != (char *) NULL)
      name=DestroyString(name);
  }
  token_info=DestroyTokenInfo(token_info);
  if (token != (char *) NULL)
    token=DestroyString(token);
  if (newstr != (char *) NULL)
    newstr=DestroyString(newstr);
  if (name != (char *) NULL)
    name=DestroyString(name);
  line=DestroyString(line);
  if (savedolen > 0)
    {
      MagickOffsetType
        offset;

      ssize_t diff = outputlen - savedolen;

      currentpos = TellBlob(ofile);
      if (currentpos < 0)
        return(-1);
      offset=SeekBlob(ofile,savedpos,SEEK_SET);
      if (offset < 0)
        return(-1);
      (void) WriteBlobMSBLong(ofile,(unsigned int) diff);
      offset=SeekBlob(ofile,currentpos,SEEK_SET);
      if (offset < 0)
        return(-1);
      savedolen = 0L;
    }
  return outputlen;
}


Prevent strrpos and strripos from visiting out-of-bounds memory.

Reviewed By: alexeyt

Differential Revision: D14669668

fbshipit-source-id: 16bb4674bccf83ffd737e60d1fd9fd4a0093f77c

int string_rfind(const char *input, int len, const char *s, int s_len,
                 int pos, bool case_sensitive) {
  assertx(input);
  assertx(s);
  if (!s_len || pos < -len || pos > len) {
    return -1;
  }
  void *ptr;
  if (case_sensitive) {
    if (pos >= 0) {
      ptr = bstrrstr(input + pos, len - pos, s, s_len);
    } else {
      ptr = bstrrstr(input, len + pos + s_len, s, s_len);
    }
  } else {
    if (pos >= 0) {
      ptr = bstrrcasestr(input + pos, len - pos, s, s_len);
    } else {
      ptr = bstrrcasestr(input, len + pos + s_len, s, s_len);
    }
  }
  if (ptr != nullptr) {
    return (int)((const char *)ptr - input);
  }
  return -1;
}

Insufficient boundary checks for the strrpos and strripos functions allow access to out-of-bounds memory. This affects all supported versions of HHVM (4.0.3, 3.30.4, and 3.27.7 and below).


int string_rfind(const char *input, int len, const char *s, int s_len,
                 int pos, bool case_sensitive) {
  assertx(input);
  assertx(s);
  if (!s_len || pos < -len || pos > len) {
    return -1;
  }
  void *ptr;
  if (case_sensitive) {
    if (pos >= 0) {
      ptr = bstrrstr(input + pos, len - pos, s, s_len);
    } else {
      ptr = bstrrstr(input, len + std::min(pos + s_len, 0), s, s_len);
    }
  } else {
    if (pos >= 0) {
      ptr = bstrrcasestr(input + pos, len - pos, s, s_len);
    } else {
      ptr = bstrrcasestr(input, len + std::min(pos + s_len, 0), s, s_len);
    }
  }
  if (ptr != nullptr) {
    return (int)((const char *)ptr - input);
  }
  return -1;
}


Fix default FastCGI interface

Reviewed By: alexeyt

Differential Revision: D15567393

fbshipit-source-id: ffab228f689d4ddca9ea4835c70aecf16da2aa9f

FastCGIServer::FastCGIServer(const std::string &address,
                             int port,
                             int workers,
                             bool useFileSocket)
  : Server(address, port),
    m_worker(&m_eventBaseManager),
    m_dispatcher(workers, workers,
                 RuntimeOption::ServerThreadDropCacheTimeoutSeconds,
                 RuntimeOption::ServerThreadDropStack,
                 this,
                 RuntimeOption::ServerThreadJobLIFOSwitchThreshold,
                 RuntimeOption::ServerThreadJobMaxQueuingMilliSeconds,
                 RequestPriority::k_numPriorities) {
  folly::SocketAddress sock_addr;
  if (useFileSocket) {
    sock_addr.setFromPath(address);
  } else if (address.empty()) {
    sock_addr.setFromLocalPort(port);
  } else {
    sock_addr.setFromHostPort(address, port);
  }
  m_socketConfig.bindAddress = sock_addr;
  m_socketConfig.acceptBacklog = RuntimeOption::ServerBacklog;
  std::chrono::seconds timeout;
  if (RuntimeOption::ConnectionTimeoutSeconds >= 0) {
    timeout = std::chrono::seconds(RuntimeOption::ConnectionTimeoutSeconds);
  } else {
    // default to 2 minutes
    timeout = std::chrono::seconds(120);
  }
  m_socketConfig.connectionIdleTimeout = timeout;
}

HHVM, when used with FastCGI, would bind by default to all available interfaces. This behavior could allow a malicious individual unintended direct access to the application, which could result in information disclosure. This issue affects versions 4.3.0, 4.4.0, 4.5.0, 4.6.0, 4.7.0, 4.8.0, versions 3.30.5 and below, and all versions in the 4.0, 4.1, and 4.2 series.


FastCGIServer::FastCGIServer(const std::string &address,
                             int port,
                             int workers,
                             bool useFileSocket)
  : Server(address, port),
    m_worker(&m_eventBaseManager),
    m_dispatcher(workers, workers,
                 RuntimeOption::ServerThreadDropCacheTimeoutSeconds,
                 RuntimeOption::ServerThreadDropStack,
                 this,
                 RuntimeOption::ServerThreadJobLIFOSwitchThreshold,
                 RuntimeOption::ServerThreadJobMaxQueuingMilliSeconds,
                 RequestPriority::k_numPriorities) {
  folly::SocketAddress sock_addr;
  if (useFileSocket) {
    sock_addr.setFromPath(address);
  } else if (address.empty()) {
    sock_addr.setFromHostPort("localhost", port);
    assert(sock_addr.isLoopbackAddress());
  } else {
    sock_addr.setFromHostPort(address, port);
  }
  m_socketConfig.bindAddress = sock_addr;
  m_socketConfig.acceptBacklog = RuntimeOption::ServerBacklog;
  std::chrono::seconds timeout;
  if (RuntimeOption::ConnectionTimeoutSeconds >= 0) {
    timeout = std::chrono::seconds(RuntimeOption::ConnectionTimeoutSeconds);
  } else {
    // default to 2 minutes
    timeout = std::chrono::seconds(120);
  }
  m_socketConfig.connectionIdleTimeout = timeout;
}


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

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 = varVariant.toString().data();
    if (varType.compare("boolean") == 0) {
      varValue = varVariant.toBoolean() ? "true" : "false";
    }
    m_packetString += getWddxEncoded(varType, varValue, varName, hasVarTag);
    return true;
  }

  return false;
}

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.


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;
}


Fix potential security leak in HashContext

Summary: CVE-2014-6229

This is not a NUL-terminated string, it's a fixed-size block of data.
The risks were key truncation (if there happens to be a NUL byte in the
key) or over-reading (which would be information leakage).

Reviewed By: @ptarjan

Differential Revision: D1533546

  explicit HashContext(const HashContext* ctx) {
    assert(ctx->ops);
    assert(ctx->ops->context_size >= 0);
    ops = ctx->ops;
    context = malloc(ops->context_size);
    ops->hash_copy(context, ctx->context);
    options = ctx->options;
    key = ctx->key ? strdup(ctx->key) : nullptr;
  }

The HashContext class in hphp/runtime/ext/ext_hash.cpp in Facebook HipHop Virtual Machine (HHVM) before 3.3.0 incorrectly expects that a certain key string uses '\0' for termination, which allows remote attackers to obtain sensitive information by leveraging read access beyond the end of the string, and makes it easier for remote attackers to defeat cryptographic protection mechanisms by leveraging truncation of a string containing an internal '\0' character.


  explicit HashContext(const HashContext* ctx) {
    assert(ctx->ops);
    assert(ctx->ops->context_size >= 0);
    ops = ctx->ops;
    context = malloc(ops->context_size);
    ops->hash_copy(context, ctx->context);
    options = ctx->options;
    if (ctx->key) {
      key = static_cast<char*>(malloc(ops->block_size));
      memcpy(key, ctx->key, ops->block_size);
    } else {
      key = nullptr;
    }
  }


Fix h2 codec state after bad priority header.

Summary:
It's possible for the http2 codec to enter an invalid state after processing a http2 header with invalid priorities.
CVE-2018-6346

Reviewed By: maxgeorg

Differential Revision: D13510025

fbshipit-source-id: 7c4e42daf1cd2b912454d13a66ab8488d1863263

folly::Optional<ErrorCode> HTTP2Codec::parseHeadersDecodeFrames(
    const folly::Optional<http2::PriorityUpdate>& priority,
    const folly::Optional<uint32_t>& promisedStream,
    const folly::Optional<ExAttributes>& exAttributes,
    std::unique_ptr<HTTPMessage>& msg) {
  // decompress headers
  Cursor headerCursor(curHeaderBlock_.front());
  bool isReq = false;
  if (promisedStream) {
    isReq = true;
  } else if (exAttributes) {
    isReq = isRequest(curHeader_.stream);
  } else {
    isReq = transportDirection_ == TransportDirection::DOWNSTREAM;
  }

  decodeInfo_.init(isReq, parsingDownstreamTrailers_);
  if (priority) {
    if (curHeader_.stream == priority->streamDependency) {
      streamError(folly::to<string>("Circular dependency for txn=",
                                    curHeader_.stream),
                  ErrorCode::PROTOCOL_ERROR,
                  curHeader_.type == http2::FrameType::HEADERS);
      return ErrorCode::NO_ERROR;
    }

    decodeInfo_.msg->setHTTP2Priority(
        std::make_tuple(priority->streamDependency,
                        priority->exclusive,
                        priority->weight));
  }
  headerCodec_.decodeStreaming(
      headerCursor, curHeaderBlock_.chainLength(), this);
  msg = std::move(decodeInfo_.msg);
  // Saving this in case we need to log it on error
  auto g = folly::makeGuard([this] { curHeaderBlock_.move(); });
  // Check decoding error
  if (decodeInfo_.decodeError != HPACK::DecodeError::NONE) {
    static const std::string decodeErrorMessage =
        "Failed decoding header block for stream=";
    // Avoid logging header blocks that have failed decoding due to being
    // excessively large.
    if (decodeInfo_.decodeError != HPACK::DecodeError::HEADERS_TOO_LARGE) {
      LOG(ERROR) << decodeErrorMessage << curHeader_.stream
                 << " header block=";
      VLOG(3) << IOBufPrinter::printHexFolly(curHeaderBlock_.front(), true);
    } else {
      LOG(ERROR) << decodeErrorMessage << curHeader_.stream;
    }

    if (msg) {
      // print the partial message
      msg->dumpMessage(3);
    }
    return ErrorCode::COMPRESSION_ERROR;
  }

  // Check parsing error
  if (decodeInfo_.parsingError != "") {
    LOG(ERROR) << "Failed parsing header list for stream=" << curHeader_.stream
               << ", error=" << decodeInfo_.parsingError << ", header block=";
    VLOG(3) << IOBufPrinter::printHexFolly(curHeaderBlock_.front(), true);
    HTTPException err(HTTPException::Direction::INGRESS,
                      folly::to<std::string>("HTTP2Codec stream error: ",
                                             "stream=",
                                             curHeader_.stream,
                                             " status=",
                                             400,
                                             " error: ",
                                             decodeInfo_.parsingError));
    err.setHttpStatusCode(400);
    callback_->onError(curHeader_.stream, err, true);
    return ErrorCode::NO_ERROR;
  }

  return folly::Optional<ErrorCode>();
}

A potential denial-of-service issue in the Proxygen handling of invalid HTTP2 priority settings (specifically a circular dependency). This affects Proxygen prior to v2018.12.31.00.


folly::Optional<ErrorCode> HTTP2Codec::parseHeadersDecodeFrames(
    const folly::Optional<http2::PriorityUpdate>& priority,
    const folly::Optional<uint32_t>& promisedStream,
    const folly::Optional<ExAttributes>& exAttributes,
    std::unique_ptr<HTTPMessage>& msg) {
  // decompress headers
  Cursor headerCursor(curHeaderBlock_.front());
  bool isReq = false;
  if (promisedStream) {
    isReq = true;
  } else if (exAttributes) {
    isReq = isRequest(curHeader_.stream);
  } else {
    isReq = transportDirection_ == TransportDirection::DOWNSTREAM;
  }

  // Validate circular dependencies.
  if (priority && (curHeader_.stream == priority->streamDependency)) {
    streamError(
        folly::to<string>("Circular dependency for txn=", curHeader_.stream),
        ErrorCode::PROTOCOL_ERROR,
        curHeader_.type == http2::FrameType::HEADERS);
    return ErrorCode::NO_ERROR;
  }

  decodeInfo_.init(isReq, parsingDownstreamTrailers_);
  if (priority) {
    decodeInfo_.msg->setHTTP2Priority(
        std::make_tuple(priority->streamDependency,
                        priority->exclusive,
                        priority->weight));
  }

  headerCodec_.decodeStreaming(
      headerCursor, curHeaderBlock_.chainLength(), this);
  msg = std::move(decodeInfo_.msg);
  // Saving this in case we need to log it on error
  auto g = folly::makeGuard([this] { curHeaderBlock_.move(); });
  // Check decoding error
  if (decodeInfo_.decodeError != HPACK::DecodeError::NONE) {
    static const std::string decodeErrorMessage =
        "Failed decoding header block for stream=";
    // Avoid logging header blocks that have failed decoding due to being
    // excessively large.
    if (decodeInfo_.decodeError != HPACK::DecodeError::HEADERS_TOO_LARGE) {
      LOG(ERROR) << decodeErrorMessage << curHeader_.stream
                 << " header block=";
      VLOG(3) << IOBufPrinter::printHexFolly(curHeaderBlock_.front(), true);
    } else {
      LOG(ERROR) << decodeErrorMessage << curHeader_.stream;
    }

    if (msg) {
      // print the partial message
      msg->dumpMessage(3);
    }
    return ErrorCode::COMPRESSION_ERROR;
  }

  // Check parsing error
  if (decodeInfo_.parsingError != "") {
    LOG(ERROR) << "Failed parsing header list for stream=" << curHeader_.stream
               << ", error=" << decodeInfo_.parsingError << ", header block=";
    VLOG(3) << IOBufPrinter::printHexFolly(curHeaderBlock_.front(), true);
    HTTPException err(HTTPException::Direction::INGRESS,
                      folly::to<std::string>("HTTP2Codec stream error: ",
                                             "stream=",
                                             curHeader_.stream,
                                             " status=",
                                             400,
                                             " error: ",
                                             decodeInfo_.parsingError));
    err.setHttpStatusCode(400);
    callback_->onError(curHeader_.stream, err, true);
    return ErrorCode::NO_ERROR;
  }

  return folly::Optional<ErrorCode>();
}


ext_mbstring: Fix invalid free() in php_mb_parse_encoding

Summary:
A chunk of memory allocated by 'req::calloc_noptrs' was being freed by 'free'. The former internally calls 'calloc' and returns a pointer at an index sizeof(MallocNode) inside the allocated buffer. This led to freeing invalid memory.

CVE-2019-11930

Reviewed By: jjergus

Differential Revision: D18179908

fbshipit-source-id: 0e3fe77628e0b9dee8361e712b8abac59ae5ed22

static bool php_mb_parse_encoding(const Variant& encoding,
                                  mbfl_encoding ***return_list,
                                  int *return_size, bool persistent) {
  bool ret;
  if (encoding.isArray()) {
    ret = php_mb_parse_encoding_array(encoding.toArray(),
                                      return_list, return_size,
                                      persistent ? 1 : 0);
  } else {
    String enc = encoding.toString();
    ret = php_mb_parse_encoding_list(enc.data(), enc.size(),
                                     return_list, return_size,
                                     persistent ? 1 : 0);
  }
  if (!ret) {
    if (return_list && *return_list) {
      free(*return_list);
      *return_list = nullptr;
    }
    return_size = 0;
  }
  return ret;
}

An invalid free in mb_detect_order can cause the application to crash or potentially result in remote code execution. This issue affects HHVM versions prior to 3.30.12, all versions between 4.0.0 and 4.8.5, all versions between 4.9.0 and 4.23.1, as well as 4.24.0, 4.25.0, 4.26.0, 4.27.0, 4.28.0, and 4.28.1.


static bool php_mb_parse_encoding(const Variant& encoding,
                                  mbfl_encoding ***return_list,
                                  int *return_size, bool persistent) {
  bool ret;
  if (encoding.isArray()) {
    ret = php_mb_parse_encoding_array(encoding.toArray(),
                                      return_list, return_size,
                                      persistent ? 1 : 0);
  } else {
    String enc = encoding.toString();
    ret = php_mb_parse_encoding_list(enc.data(), enc.size(),
                                     return_list, return_size,
                                     persistent ? 1 : 0);
  }
  if (!ret) {
    if (return_list && *return_list) {
      req::free(*return_list);
      *return_list = nullptr;
    }
    return_size = 0;
  }
  return ret;
}


Fix return value check for openssl API used during pubkey validation.

Found thanks to bug report by Michael Scheibel <m.Scheibel (at) tuvit (dot) de>
ok patrick@, markus@, tb@

ca_validate_pubkey(struct iked *env, struct iked_static_id *id,
    void *data, size_t len, struct iked_id *out)
{
	BIO		*rawcert = NULL;
	RSA		*peerrsa = NULL, *localrsa = NULL;
	EC_KEY		*peerec = NULL;
	EVP_PKEY	*peerkey = NULL, *localkey = NULL;
	int		 ret = -1;
	FILE		*fp = NULL;
	char		 idstr[IKED_ID_SIZE];
	char		 file[PATH_MAX];
	struct iked_id	 idp;

	switch (id->id_type) {
	case IKEV2_ID_IPV4:
	case IKEV2_ID_FQDN:
	case IKEV2_ID_UFQDN:
	case IKEV2_ID_IPV6:
		break;
	default:
		/* Some types like ASN1_DN will not be mapped to file names */
		log_debug("%s: unsupported public key type %s",
		    __func__, print_map(id->id_type, ikev2_id_map));
		return (-1);
	}

	bzero(&idp, sizeof(idp));
	if ((idp.id_buf = ibuf_new(id->id_data, id->id_length)) == NULL)
		goto done;

	idp.id_type = id->id_type;
	idp.id_offset = id->id_offset;
	if (ikev2_print_id(&idp, idstr, sizeof(idstr)) == -1)
		goto done;

	if (len == 0 && data) {
		/* Data is already an public key */
		peerkey = (EVP_PKEY *)data;
	}
	if (len > 0) {
		if ((rawcert = BIO_new_mem_buf(data, len)) == NULL)
			goto done;

		if ((peerkey = EVP_PKEY_new()) == NULL)
			goto sslerr;
		if ((peerrsa = d2i_RSAPublicKey_bio(rawcert, NULL))) {
			if (!EVP_PKEY_set1_RSA(peerkey, peerrsa)) {
				goto sslerr;
			}
		} else if (BIO_reset(rawcert) == 1 &&
		    (peerec = d2i_EC_PUBKEY_bio(rawcert, NULL))) {
			if (!EVP_PKEY_set1_EC_KEY(peerkey, peerec)) {
				goto sslerr;
			}
		} else {
			log_debug("%s: unknown key type received", __func__);
			goto sslerr;
		}
	}

	lc_idtype(idstr);
	if (strlcpy(file, IKED_PUBKEY_DIR, sizeof(file)) >= sizeof(file) ||
	    strlcat(file, idstr, sizeof(file)) >= sizeof(file)) {
		log_debug("%s: public key id too long %s", __func__, idstr);
		goto done;
	}

	if ((fp = fopen(file, "r")) == NULL) {
		/* Log to debug when called from ca_validate_cert */
		logit(len == 0 ? LOG_DEBUG : LOG_INFO,
		    "%s: could not open public key %s", __func__, file);
		goto done;
	}
	localkey = PEM_read_PUBKEY(fp, NULL, NULL, NULL);
	if (localkey == NULL) {
		/* reading PKCS #8 failed, try PEM RSA */
		rewind(fp);
		localrsa = PEM_read_RSAPublicKey(fp, NULL, NULL, NULL);
		fclose(fp);
		if (localrsa == NULL)
			goto sslerr;
		if ((localkey = EVP_PKEY_new()) == NULL)
			goto sslerr;
		if (!EVP_PKEY_set1_RSA(localkey, localrsa))
			goto sslerr;
	} else {
		fclose(fp);
	}
	if (localkey == NULL)
		goto sslerr;

	if (peerkey && !EVP_PKEY_cmp(peerkey, localkey)) {
		log_debug("%s: public key does not match %s", __func__, file);
		goto done;
	}

	log_debug("%s: valid public key in file %s", __func__, file);

	if (out && ca_pubkey_serialize(localkey, out))
		goto done;

	ret = 0;
 sslerr:
	if (ret != 0)
		ca_sslerror(__func__);
 done:
	ibuf_release(idp.id_buf);
	if (localkey != NULL)
		EVP_PKEY_free(localkey);
	if (peerrsa != NULL)
		RSA_free(peerrsa);
	if (peerec != NULL)
		EC_KEY_free(peerec);
	if (localrsa != NULL)
		RSA_free(localrsa);
	if (rawcert != NULL) {
		BIO_free(rawcert);
		if (peerkey != NULL)
			EVP_PKEY_free(peerkey);
	}

	return (ret);
}

iked in OpenIKED, as used in OpenBSD through 6.7, allows authentication bypass because ca.c has the wrong logic for checking whether a public key matches.


ca_validate_pubkey(struct iked *env, struct iked_static_id *id,
    void *data, size_t len, struct iked_id *out)
{
	BIO		*rawcert = NULL;
	RSA		*peerrsa = NULL, *localrsa = NULL;
	EC_KEY		*peerec = NULL;
	EVP_PKEY	*peerkey = NULL, *localkey = NULL;
	int		 ret = -1;
	FILE		*fp = NULL;
	char		 idstr[IKED_ID_SIZE];
	char		 file[PATH_MAX];
	struct iked_id	 idp;

	switch (id->id_type) {
	case IKEV2_ID_IPV4:
	case IKEV2_ID_FQDN:
	case IKEV2_ID_UFQDN:
	case IKEV2_ID_IPV6:
		break;
	default:
		/* Some types like ASN1_DN will not be mapped to file names */
		log_debug("%s: unsupported public key type %s",
		    __func__, print_map(id->id_type, ikev2_id_map));
		return (-1);
	}

	bzero(&idp, sizeof(idp));
	if ((idp.id_buf = ibuf_new(id->id_data, id->id_length)) == NULL)
		goto done;

	idp.id_type = id->id_type;
	idp.id_offset = id->id_offset;
	if (ikev2_print_id(&idp, idstr, sizeof(idstr)) == -1)
		goto done;

	if (len == 0 && data) {
		/* Data is already an public key */
		peerkey = (EVP_PKEY *)data;
	}
	if (len > 0) {
		if ((rawcert = BIO_new_mem_buf(data, len)) == NULL)
			goto done;

		if ((peerkey = EVP_PKEY_new()) == NULL)
			goto sslerr;
		if ((peerrsa = d2i_RSAPublicKey_bio(rawcert, NULL))) {
			if (!EVP_PKEY_set1_RSA(peerkey, peerrsa)) {
				goto sslerr;
			}
		} else if (BIO_reset(rawcert) == 1 &&
		    (peerec = d2i_EC_PUBKEY_bio(rawcert, NULL))) {
			if (!EVP_PKEY_set1_EC_KEY(peerkey, peerec)) {
				goto sslerr;
			}
		} else {
			log_debug("%s: unknown key type received", __func__);
			goto sslerr;
		}
	}

	lc_idtype(idstr);
	if (strlcpy(file, IKED_PUBKEY_DIR, sizeof(file)) >= sizeof(file) ||
	    strlcat(file, idstr, sizeof(file)) >= sizeof(file)) {
		log_debug("%s: public key id too long %s", __func__, idstr);
		goto done;
	}

	if ((fp = fopen(file, "r")) == NULL) {
		/* Log to debug when called from ca_validate_cert */
		logit(len == 0 ? LOG_DEBUG : LOG_INFO,
		    "%s: could not open public key %s", __func__, file);
		goto done;
	}
	localkey = PEM_read_PUBKEY(fp, NULL, NULL, NULL);
	if (localkey == NULL) {
		/* reading PKCS #8 failed, try PEM RSA */
		rewind(fp);
		localrsa = PEM_read_RSAPublicKey(fp, NULL, NULL, NULL);
		fclose(fp);
		if (localrsa == NULL)
			goto sslerr;
		if ((localkey = EVP_PKEY_new()) == NULL)
			goto sslerr;
		if (!EVP_PKEY_set1_RSA(localkey, localrsa))
			goto sslerr;
	} else {
		fclose(fp);
	}
	if (localkey == NULL)
		goto sslerr;

	if (peerkey && EVP_PKEY_cmp(peerkey, localkey) != 1) {
		log_debug("%s: public key does not match %s", __func__, file);
		goto done;
	}

	log_debug("%s: valid public key in file %s", __func__, file);

	if (out && ca_pubkey_serialize(localkey, out))
		goto done;

	ret = 0;
 sslerr:
	if (ret != 0)
		ca_sslerror(__func__);
 done:
	ibuf_release(idp.id_buf);
	if (localkey != NULL)
		EVP_PKEY_free(localkey);
	if (peerrsa != NULL)
		RSA_free(peerrsa);
	if (peerec != NULL)
		EC_KEY_free(peerec);
	if (localrsa != NULL)
		RSA_free(localrsa);
	if (rawcert != NULL) {
		BIO_free(rawcert);
		if (peerkey != NULL)
			EVP_PKEY_free(peerkey);
	}

	return (ret);
}


mm: make page ref count overflow check tighter and more explicit

We have a VM_BUG_ON() to check that the page reference count doesn't
underflow (or get close to overflow) by checking the sign of the count.

That's all fine, but we actually want to allow people to use a "get page
ref unless it's already very high" helper function, and we want that one
to use the sign of the page ref (without triggering this VM_BUG_ON).

Change the VM_BUG_ON to only check for small underflows (or _very_ close
to overflowing), and ignore overflows which have strayed into negative
territory.

Acked-by: Matthew Wilcox <willy@infradead.org>
Cc: Jann Horn <jannh@google.com>
Cc: stable@kernel.org
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>

static inline void get_page(struct page *page)
{
	page = compound_head(page);
	/*
	 * Getting a normal page or the head of a compound page
	 * requires to already have an elevated page->_refcount.
	 */
	VM_BUG_ON_PAGE(page_ref_count(page) <= 0, page);
	page_ref_inc(page);
}

The Linux kernel before 5.1-rc5 allows page->_refcount reference count overflow, with resultant use-after-free issues, if about 140 GiB of RAM exists. This is related to fs/fuse/dev.c, fs/pipe.c, fs/splice.c, include/linux/mm.h, include/linux/pipe_fs_i.h, kernel/trace/trace.c, mm/gup.c, and mm/hugetlb.c. It can occur with FUSE requests.


static inline void get_page(struct page *page)
{
	page = compound_head(page);
	/*
	 * Getting a normal page or the head of a compound page
	 * requires to already have an elevated page->_refcount.
	 */
	VM_BUG_ON_PAGE(page_ref_zero_or_close_to_overflow(page), page);
	page_ref_inc(page);
}


Don't open .pk3 files as OpenAL drivers.

qboolean S_AL_Init( soundInterface_t *si )
{
#ifdef USE_OPENAL
	const char* device = NULL;
	const char* inputdevice = NULL;
	int i;

	if( !si ) {
		return qfalse;
	}

	for (i = 0; i < MAX_RAW_STREAMS; i++) {
		streamSourceHandles[i] = -1;
		streamPlaying[i] = qfalse;
		streamSources[i] = 0;
		streamNumBuffers[i] = 0;
		streamBufIndex[i] = 0;
	}

	// New console variables
	s_alPrecache = Cvar_Get( "s_alPrecache", "1", CVAR_ARCHIVE );
	s_alGain = Cvar_Get( "s_alGain", "1.0", CVAR_ARCHIVE );
	s_alSources = Cvar_Get( "s_alSources", "96", CVAR_ARCHIVE );
	s_alDopplerFactor = Cvar_Get( "s_alDopplerFactor", "1.0", CVAR_ARCHIVE );
	s_alDopplerSpeed = Cvar_Get( "s_alDopplerSpeed", "9000", CVAR_ARCHIVE );
	s_alMinDistance = Cvar_Get( "s_alMinDistance", "120", CVAR_CHEAT );
	s_alMaxDistance = Cvar_Get("s_alMaxDistance", "1024", CVAR_CHEAT);
	s_alRolloff = Cvar_Get( "s_alRolloff", "2", CVAR_CHEAT);
	s_alGraceDistance = Cvar_Get("s_alGraceDistance", "512", CVAR_CHEAT);

	s_alDriver = Cvar_Get( "s_alDriver", ALDRIVER_DEFAULT, CVAR_ARCHIVE | CVAR_LATCH );

	s_alInputDevice = Cvar_Get( "s_alInputDevice", "", CVAR_ARCHIVE | CVAR_LATCH );
	s_alDevice = Cvar_Get("s_alDevice", "", CVAR_ARCHIVE | CVAR_LATCH);

	// Load QAL
	if( !QAL_Init( s_alDriver->string ) )
	{
		Com_Printf( "Failed to load library: \"%s\".\n", s_alDriver->string );
		if( !Q_stricmp( s_alDriver->string, ALDRIVER_DEFAULT ) || !QAL_Init( ALDRIVER_DEFAULT ) ) {
			return qfalse;
		}
	}

	device = s_alDevice->string;
	if(device && !*device)
		device = NULL;

	inputdevice = s_alInputDevice->string;
	if(inputdevice && !*inputdevice)
		inputdevice = NULL;

	// Device enumeration support
	enumeration_all_ext = qalcIsExtensionPresent(NULL, "ALC_ENUMERATE_ALL_EXT");
	enumeration_ext = qalcIsExtensionPresent(NULL, "ALC_ENUMERATION_EXT");

	if(enumeration_ext || enumeration_all_ext)
	{
		char devicenames[16384] = "";
		const char *devicelist;
#ifdef _WIN32
		const char *defaultdevice;
#endif
		int curlen;

		// get all available devices + the default device name.
		if(enumeration_all_ext)
		{
			devicelist = qalcGetString(NULL, ALC_ALL_DEVICES_SPECIFIER);
#ifdef _WIN32
			defaultdevice = qalcGetString(NULL, ALC_DEFAULT_ALL_DEVICES_SPECIFIER);
#endif
		}
		else
		{
			// We don't have ALC_ENUMERATE_ALL_EXT but normal enumeration.
			devicelist = qalcGetString(NULL, ALC_DEVICE_SPECIFIER);
#ifdef _WIN32
			defaultdevice = qalcGetString(NULL, ALC_DEFAULT_DEVICE_SPECIFIER);
#endif
			enumeration_ext = qtrue;
		}

#ifdef _WIN32
		// check whether the default device is generic hardware. If it is, change to
		// Generic Software as that one works more reliably with various sound systems.
		// If it's not, use OpenAL's default selection as we don't want to ignore
		// native hardware acceleration.
		if(!device && defaultdevice && !strcmp(defaultdevice, "Generic Hardware"))
			device = "Generic Software";
#endif

		// dump a list of available devices to a cvar for the user to see.

		if(devicelist)
		{
			while((curlen = strlen(devicelist)))
			{
				Q_strcat(devicenames, sizeof(devicenames), devicelist);
				Q_strcat(devicenames, sizeof(devicenames), "\n");

				devicelist += curlen + 1;
			}
		}

		s_alAvailableDevices = Cvar_Get("s_alAvailableDevices", devicenames, CVAR_ROM | CVAR_NORESTART);
	}

	alDevice = qalcOpenDevice(device);
	if( !alDevice && device )
	{
		Com_Printf( "Failed to open OpenAL device '%s', trying default.\n", device );
		alDevice = qalcOpenDevice(NULL);
	}

	if( !alDevice )
	{
		QAL_Shutdown( );
		Com_Printf( "Failed to open OpenAL device.\n" );
		return qfalse;
	}

	// Create OpenAL context
	alContext = qalcCreateContext( alDevice, NULL );
	if( !alContext )
	{
		QAL_Shutdown( );
		qalcCloseDevice( alDevice );
		Com_Printf( "Failed to create OpenAL context.\n" );
		return qfalse;
	}
	qalcMakeContextCurrent( alContext );

	// Initialize sources, buffers, music
	S_AL_BufferInit( );
	S_AL_SrcInit( );

	// Set up OpenAL parameters (doppler, etc)
	qalDistanceModel(AL_INVERSE_DISTANCE_CLAMPED);
	qalDopplerFactor( s_alDopplerFactor->value );
	qalSpeedOfSound( s_alDopplerSpeed->value );

#ifdef USE_VOIP
	// !!! FIXME: some of these alcCaptureOpenDevice() values should be cvars.
	// !!! FIXME: add support for capture device enumeration.
	// !!! FIXME: add some better error reporting.
	s_alCapture = Cvar_Get( "s_alCapture", "1", CVAR_ARCHIVE | CVAR_LATCH );
	if (!s_alCapture->integer)
	{
		Com_Printf("OpenAL capture support disabled by user ('+set s_alCapture 1' to enable)\n");
	}
#if USE_MUMBLE
	else if (cl_useMumble->integer)
	{
		Com_Printf("OpenAL capture support disabled for Mumble support\n");
	}
#endif
	else
	{
#ifdef __APPLE__
		// !!! FIXME: Apple has a 1.1-compliant OpenAL, which includes
		// !!! FIXME:  capture support, but they don't list it in the
		// !!! FIXME:  extension string. We need to check the version string,
		// !!! FIXME:  then the extension string, but that's too much trouble,
		// !!! FIXME:  so we'll just check the function pointer for now.
		if (qalcCaptureOpenDevice == NULL)
#else
		if (!qalcIsExtensionPresent(NULL, "ALC_EXT_capture"))
#endif
		{
			Com_Printf("No ALC_EXT_capture support, can't record audio.\n");
		}
		else
		{
			char inputdevicenames[16384] = "";
			const char *inputdevicelist;
			const char *defaultinputdevice;
			int curlen;

			capture_ext = qtrue;

			// get all available input devices + the default input device name.
			inputdevicelist = qalcGetString(NULL, ALC_CAPTURE_DEVICE_SPECIFIER);
			defaultinputdevice = qalcGetString(NULL, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER);

			// dump a list of available devices to a cvar for the user to see.
			if (inputdevicelist)
			{
				while((curlen = strlen(inputdevicelist)))
				{
					Q_strcat(inputdevicenames, sizeof(inputdevicenames), inputdevicelist);
					Q_strcat(inputdevicenames, sizeof(inputdevicenames), "\n");
					inputdevicelist += curlen + 1;
				}
			}

			s_alAvailableInputDevices = Cvar_Get("s_alAvailableInputDevices", inputdevicenames, CVAR_ROM | CVAR_NORESTART);

			Com_Printf("OpenAL default capture device is '%s'\n", defaultinputdevice ? defaultinputdevice : "none");
			alCaptureDevice = qalcCaptureOpenDevice(inputdevice, 48000, AL_FORMAT_MONO16, VOIP_MAX_PACKET_SAMPLES*4);
			if( !alCaptureDevice && inputdevice )
			{
				Com_Printf( "Failed to open OpenAL Input device '%s', trying default.\n", inputdevice );
				alCaptureDevice = qalcCaptureOpenDevice(NULL, 48000, AL_FORMAT_MONO16, VOIP_MAX_PACKET_SAMPLES*4);
			}
			Com_Printf( "OpenAL capture device %s.\n",
				    (alCaptureDevice == NULL) ? "failed to open" : "opened");
		}
	}
#endif

	si->Shutdown = S_AL_Shutdown;
	si->StartSound = S_AL_StartSound;
	si->StartLocalSound = S_AL_StartLocalSound;
	si->StartBackgroundTrack = S_AL_StartBackgroundTrack;
	si->StopBackgroundTrack = S_AL_StopBackgroundTrack;
	si->RawSamples = S_AL_RawSamples;
	si->StopAllSounds = S_AL_StopAllSounds;
	si->ClearLoopingSounds = S_AL_ClearLoopingSounds;
	si->AddLoopingSound = S_AL_AddLoopingSound;
	si->AddRealLoopingSound = S_AL_AddRealLoopingSound;
	si->StopLoopingSound = S_AL_StopLoopingSound;
	si->Respatialize = S_AL_Respatialize;
	si->UpdateEntityPosition = S_AL_UpdateEntityPosition;
	si->Update = S_AL_Update;
	si->DisableSounds = S_AL_DisableSounds;
	si->BeginRegistration = S_AL_BeginRegistration;
	si->RegisterSound = S_AL_RegisterSound;
	si->ClearSoundBuffer = S_AL_ClearSoundBuffer;
	si->SoundInfo = S_AL_SoundInfo;
	si->SoundList = S_AL_SoundList;

#ifdef USE_VOIP
	si->StartCapture = S_AL_StartCapture;
	si->AvailableCaptureSamples = S_AL_AvailableCaptureSamples;
	si->Capture = S_AL_Capture;
	si->StopCapture = S_AL_StopCapture;
	si->MasterGain = S_AL_MasterGain;
#endif

	return qtrue;
#else
	return qfalse;
#endif
}

In ioquake3 before 2017-03-14, the auto-downloading feature has insufficient content restrictions. This also affects Quake III Arena, OpenArena, OpenJK, iortcw, and other id Tech 3 (aka Quake 3 engine) forks. A malicious auto-downloaded file can trigger loading of crafted auto-downloaded files as native code DLLs. A malicious auto-downloaded file can contain configuration defaults that override the user's. Executable bytecode in a malicious auto-downloaded file can set configuration variables to values that will result in unwanted native code DLLs being loaded, resulting in sandbox escape.


qboolean S_AL_Init( soundInterface_t *si )
{
#ifdef USE_OPENAL
	const char* device = NULL;
	const char* inputdevice = NULL;
	int i;

	if( !si ) {
		return qfalse;
	}

	for (i = 0; i < MAX_RAW_STREAMS; i++) {
		streamSourceHandles[i] = -1;
		streamPlaying[i] = qfalse;
		streamSources[i] = 0;
		streamNumBuffers[i] = 0;
		streamBufIndex[i] = 0;
	}

	// New console variables
	s_alPrecache = Cvar_Get( "s_alPrecache", "1", CVAR_ARCHIVE );
	s_alGain = Cvar_Get( "s_alGain", "1.0", CVAR_ARCHIVE );
	s_alSources = Cvar_Get( "s_alSources", "96", CVAR_ARCHIVE );
	s_alDopplerFactor = Cvar_Get( "s_alDopplerFactor", "1.0", CVAR_ARCHIVE );
	s_alDopplerSpeed = Cvar_Get( "s_alDopplerSpeed", "9000", CVAR_ARCHIVE );
	s_alMinDistance = Cvar_Get( "s_alMinDistance", "120", CVAR_CHEAT );
	s_alMaxDistance = Cvar_Get("s_alMaxDistance", "1024", CVAR_CHEAT);
	s_alRolloff = Cvar_Get( "s_alRolloff", "2", CVAR_CHEAT);
	s_alGraceDistance = Cvar_Get("s_alGraceDistance", "512", CVAR_CHEAT);

	s_alDriver = Cvar_Get( "s_alDriver", ALDRIVER_DEFAULT, CVAR_ARCHIVE | CVAR_LATCH | CVAR_PROTECTED );

	s_alInputDevice = Cvar_Get( "s_alInputDevice", "", CVAR_ARCHIVE | CVAR_LATCH );
	s_alDevice = Cvar_Get("s_alDevice", "", CVAR_ARCHIVE | CVAR_LATCH);

	if ( COM_CompareExtension( s_alDriver->string, ".pk3" ) )
	{
		Com_Printf( "Rejecting DLL named \"%s\"", s_alDriver->string );
		return qfalse;
	}

	// Load QAL
	if( !QAL_Init( s_alDriver->string ) )
	{
		Com_Printf( "Failed to load library: \"%s\".\n", s_alDriver->string );
		if( !Q_stricmp( s_alDriver->string, ALDRIVER_DEFAULT ) || !QAL_Init( ALDRIVER_DEFAULT ) ) {
			return qfalse;
		}
	}

	device = s_alDevice->string;
	if(device && !*device)
		device = NULL;

	inputdevice = s_alInputDevice->string;
	if(inputdevice && !*inputdevice)
		inputdevice = NULL;

	// Device enumeration support
	enumeration_all_ext = qalcIsExtensionPresent(NULL, "ALC_ENUMERATE_ALL_EXT");
	enumeration_ext = qalcIsExtensionPresent(NULL, "ALC_ENUMERATION_EXT");

	if(enumeration_ext || enumeration_all_ext)
	{
		char devicenames[16384] = "";
		const char *devicelist;
#ifdef _WIN32
		const char *defaultdevice;
#endif
		int curlen;

		// get all available devices + the default device name.
		if(enumeration_all_ext)
		{
			devicelist = qalcGetString(NULL, ALC_ALL_DEVICES_SPECIFIER);
#ifdef _WIN32
			defaultdevice = qalcGetString(NULL, ALC_DEFAULT_ALL_DEVICES_SPECIFIER);
#endif
		}
		else
		{
			// We don't have ALC_ENUMERATE_ALL_EXT but normal enumeration.
			devicelist = qalcGetString(NULL, ALC_DEVICE_SPECIFIER);
#ifdef _WIN32
			defaultdevice = qalcGetString(NULL, ALC_DEFAULT_DEVICE_SPECIFIER);
#endif
			enumeration_ext = qtrue;
		}

#ifdef _WIN32
		// check whether the default device is generic hardware. If it is, change to
		// Generic Software as that one works more reliably with various sound systems.
		// If it's not, use OpenAL's default selection as we don't want to ignore
		// native hardware acceleration.
		if(!device && defaultdevice && !strcmp(defaultdevice, "Generic Hardware"))
			device = "Generic Software";
#endif

		// dump a list of available devices to a cvar for the user to see.

		if(devicelist)
		{
			while((curlen = strlen(devicelist)))
			{
				Q_strcat(devicenames, sizeof(devicenames), devicelist);
				Q_strcat(devicenames, sizeof(devicenames), "\n");

				devicelist += curlen + 1;
			}
		}

		s_alAvailableDevices = Cvar_Get("s_alAvailableDevices", devicenames, CVAR_ROM | CVAR_NORESTART);
	}

	alDevice = qalcOpenDevice(device);
	if( !alDevice && device )
	{
		Com_Printf( "Failed to open OpenAL device '%s', trying default.\n", device );
		alDevice = qalcOpenDevice(NULL);
	}

	if( !alDevice )
	{
		QAL_Shutdown( );
		Com_Printf( "Failed to open OpenAL device.\n" );
		return qfalse;
	}

	// Create OpenAL context
	alContext = qalcCreateContext( alDevice, NULL );
	if( !alContext )
	{
		QAL_Shutdown( );
		qalcCloseDevice( alDevice );
		Com_Printf( "Failed to create OpenAL context.\n" );
		return qfalse;
	}
	qalcMakeContextCurrent( alContext );

	// Initialize sources, buffers, music
	S_AL_BufferInit( );
	S_AL_SrcInit( );

	// Set up OpenAL parameters (doppler, etc)
	qalDistanceModel(AL_INVERSE_DISTANCE_CLAMPED);
	qalDopplerFactor( s_alDopplerFactor->value );
	qalSpeedOfSound( s_alDopplerSpeed->value );

#ifdef USE_VOIP
	// !!! FIXME: some of these alcCaptureOpenDevice() values should be cvars.
	// !!! FIXME: add support for capture device enumeration.
	// !!! FIXME: add some better error reporting.
	s_alCapture = Cvar_Get( "s_alCapture", "1", CVAR_ARCHIVE | CVAR_LATCH );
	if (!s_alCapture->integer)
	{
		Com_Printf("OpenAL capture support disabled by user ('+set s_alCapture 1' to enable)\n");
	}
#if USE_MUMBLE
	else if (cl_useMumble->integer)
	{
		Com_Printf("OpenAL capture support disabled for Mumble support\n");
	}
#endif
	else
	{
#ifdef __APPLE__
		// !!! FIXME: Apple has a 1.1-compliant OpenAL, which includes
		// !!! FIXME:  capture support, but they don't list it in the
		// !!! FIXME:  extension string. We need to check the version string,
		// !!! FIXME:  then the extension string, but that's too much trouble,
		// !!! FIXME:  so we'll just check the function pointer for now.
		if (qalcCaptureOpenDevice == NULL)
#else
		if (!qalcIsExtensionPresent(NULL, "ALC_EXT_capture"))
#endif
		{
			Com_Printf("No ALC_EXT_capture support, can't record audio.\n");
		}
		else
		{
			char inputdevicenames[16384] = "";
			const char *inputdevicelist;
			const char *defaultinputdevice;
			int curlen;

			capture_ext = qtrue;

			// get all available input devices + the default input device name.
			inputdevicelist = qalcGetString(NULL, ALC_CAPTURE_DEVICE_SPECIFIER);
			defaultinputdevice = qalcGetString(NULL, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER);

			// dump a list of available devices to a cvar for the user to see.
			if (inputdevicelist)
			{
				while((curlen = strlen(inputdevicelist)))
				{
					Q_strcat(inputdevicenames, sizeof(inputdevicenames), inputdevicelist);
					Q_strcat(inputdevicenames, sizeof(inputdevicenames), "\n");
					inputdevicelist += curlen + 1;
				}
			}

			s_alAvailableInputDevices = Cvar_Get("s_alAvailableInputDevices", inputdevicenames, CVAR_ROM | CVAR_NORESTART);

			Com_Printf("OpenAL default capture device is '%s'\n", defaultinputdevice ? defaultinputdevice : "none");
			alCaptureDevice = qalcCaptureOpenDevice(inputdevice, 48000, AL_FORMAT_MONO16, VOIP_MAX_PACKET_SAMPLES*4);
			if( !alCaptureDevice && inputdevice )
			{
				Com_Printf( "Failed to open OpenAL Input device '%s', trying default.\n", inputdevice );
				alCaptureDevice = qalcCaptureOpenDevice(NULL, 48000, AL_FORMAT_MONO16, VOIP_MAX_PACKET_SAMPLES*4);
			}
			Com_Printf( "OpenAL capture device %s.\n",
				    (alCaptureDevice == NULL) ? "failed to open" : "opened");
		}
	}
#endif

	si->Shutdown = S_AL_Shutdown;
	si->StartSound = S_AL_StartSound;
	si->StartLocalSound = S_AL_StartLocalSound;
	si->StartBackgroundTrack = S_AL_StartBackgroundTrack;
	si->StopBackgroundTrack = S_AL_StopBackgroundTrack;
	si->RawSamples = S_AL_RawSamples;
	si->StopAllSounds = S_AL_StopAllSounds;
	si->ClearLoopingSounds = S_AL_ClearLoopingSounds;
	si->AddLoopingSound = S_AL_AddLoopingSound;
	si->AddRealLoopingSound = S_AL_AddRealLoopingSound;
	si->StopLoopingSound = S_AL_StopLoopingSound;
	si->Respatialize = S_AL_Respatialize;
	si->UpdateEntityPosition = S_AL_UpdateEntityPosition;
	si->Update = S_AL_Update;
	si->DisableSounds = S_AL_DisableSounds;
	si->BeginRegistration = S_AL_BeginRegistration;
	si->RegisterSound = S_AL_RegisterSound;
	si->ClearSoundBuffer = S_AL_ClearSoundBuffer;
	si->SoundInfo = S_AL_SoundInfo;
	si->SoundList = S_AL_SoundList;

#ifdef USE_VOIP
	si->StartCapture = S_AL_StartCapture;
	si->AvailableCaptureSamples = S_AL_AvailableCaptureSamples;
	si->Capture = S_AL_Capture;
	si->StopCapture = S_AL_StopCapture;
	si->MasterGain = S_AL_MasterGain;
#endif

	return qtrue;
#else
	return qfalse;
#endif
}


AP4_AtomSampleTable::GetSample(AP4_Ordinal index,
                               AP4_Sample& sample)
{
    AP4_Result result;

    // check that we have an stsc atom
    if (!m_StscAtom) {
        return AP4_ERROR_INVALID_FORMAT;
    }

    // check that we have a chunk offset table
    if (m_StcoAtom == NULL && m_Co64Atom == NULL) {
        return AP4_ERROR_INVALID_FORMAT;
    }

    // MP4 uses 1-based indexes internally, so adjust by one
    index++;

    // find out in which chunk this sample is located
    AP4_Ordinal chunk, skip, desc;
    result = m_StscAtom->GetChunkForSample(index, chunk, skip, desc);
    if (AP4_FAILED(result)) return result;

    // check that the result is within bounds
    if (skip > index) return AP4_ERROR_INTERNAL;

    // get the atom offset for this chunk
    AP4_UI64 offset;
    if (m_StcoAtom) {
        AP4_UI32 offset_32;
        result = m_StcoAtom->GetChunkOffset(chunk, offset_32);
        offset = offset_32;
    } else {
        result = m_Co64Atom->GetChunkOffset(chunk, offset);
    }
    if (AP4_FAILED(result)) return result;

    // compute the additional offset inside the chunk
    for (unsigned int i = index-skip; i < index; i++) {
        AP4_Size size = 0;
        if (m_StszAtom) {
            result = m_StszAtom->GetSampleSize(i, size);
        } else if (m_Stz2Atom) {
            result = m_Stz2Atom->GetSampleSize(i, size);
        } else {
            result = AP4_ERROR_INVALID_FORMAT;
        }
        if (AP4_FAILED(result)) return result;
        offset += size;
    }

    // set the description index
    sample.SetDescriptionIndex(desc-1); // adjust for 0-based indexes

    // set the dts and cts
    AP4_UI32 cts_offset = 0;
    AP4_UI64 dts        = 0;
    AP4_UI32 duration   = 0;
    result = m_SttsAtom->GetDts(index, dts, &duration);
    if (AP4_FAILED(result)) return result;
    sample.SetDuration(duration);
    sample.SetDts(dts);
    if (m_CttsAtom == NULL) {
        sample.SetCts(dts);
    } else {
        result = m_CttsAtom->GetCtsOffset(index, cts_offset);
	    if (AP4_FAILED(result)) return result;
        sample.SetCtsDelta(cts_offset);
    }

    // set the size
    AP4_Size sample_size = 0;
    if (m_StszAtom) {
        result = m_StszAtom->GetSampleSize(index, sample_size);
    } else if (m_Stz2Atom) {
        result = m_Stz2Atom->GetSampleSize(index, sample_size);
    } else {
        result = AP4_ERROR_INVALID_FORMAT;
    }
    if (AP4_FAILED(result)) return result;
    sample.SetSize(sample_size);

    // set the sync flag
    if (m_StssAtom == NULL) {
        sample.SetSync(true);
    } else {
        sample.SetSync(m_StssAtom->IsSampleSync(index));
    }

    // set the offset
    sample.SetOffset(offset);

    // set the data stream
    sample.SetDataStream(m_SampleStream);

    return AP4_SUCCESS;
}

A NULL pointer dereference was discovered in AP4_AtomSampleTable::GetSample in Core/Ap4AtomSampleTable.cpp in Bento4 version 1.5.0-617. The vulnerability causes a segmentation fault and application crash, which leads to remote denial of service.


AP4_AtomSampleTable::GetSample(AP4_Ordinal index,
                               AP4_Sample& sample)
{
    AP4_Result result;

    // check that we have an stsc atom
    if (!m_StscAtom) {
        return AP4_ERROR_INVALID_FORMAT;
    }

    // check that we have a chunk offset table
    if (m_StcoAtom == NULL && m_Co64Atom == NULL) {
        return AP4_ERROR_INVALID_FORMAT;
    }

    // MP4 uses 1-based indexes internally, so adjust by one
    index++;

    // find out in which chunk this sample is located
    AP4_Ordinal chunk, skip, desc;
    result = m_StscAtom->GetChunkForSample(index, chunk, skip, desc);
    if (AP4_FAILED(result)) return result;

    // check that the result is within bounds
    if (skip > index) return AP4_ERROR_INTERNAL;

    // get the atom offset for this chunk
    AP4_UI64 offset;
    if (m_StcoAtom) {
        AP4_UI32 offset_32;
        result = m_StcoAtom->GetChunkOffset(chunk, offset_32);
        offset = offset_32;
    } else {
        result = m_Co64Atom->GetChunkOffset(chunk, offset);
    }
    if (AP4_FAILED(result)) return result;

    // compute the additional offset inside the chunk
    for (unsigned int i = index-skip; i < index; i++) {
        AP4_Size size = 0;
        if (m_StszAtom) {
            result = m_StszAtom->GetSampleSize(i, size);
        } else if (m_Stz2Atom) {
            result = m_Stz2Atom->GetSampleSize(i, size);
        } else {
            result = AP4_ERROR_INVALID_FORMAT;
        }
        if (AP4_FAILED(result)) return result;
        offset += size;
    }

    // set the description index
    sample.SetDescriptionIndex(desc-1); // adjust for 0-based indexes

    // set the dts and cts
    AP4_UI32 cts_offset = 0;
    AP4_UI64 dts        = 0;
    AP4_UI32 duration   = 0;
    if (m_SttsAtom) {
        result = m_SttsAtom->GetDts(index, dts, &duration);
        if (AP4_FAILED(result)) return result;
    }
    sample.SetDuration(duration);
    sample.SetDts(dts);
    if (m_CttsAtom == NULL) {
        sample.SetCts(dts);
    } else {
        result = m_CttsAtom->GetCtsOffset(index, cts_offset);
	    if (AP4_FAILED(result)) return result;
        sample.SetCtsDelta(cts_offset);
    }

    // set the size
    AP4_Size sample_size = 0;
    if (m_StszAtom) {
        result = m_StszAtom->GetSampleSize(index, sample_size);
    } else if (m_Stz2Atom) {
        result = m_Stz2Atom->GetSampleSize(index, sample_size);
    } else {
        result = AP4_ERROR_INVALID_FORMAT;
    }
    if (AP4_FAILED(result)) return result;
    sample.SetSize(sample_size);

    // set the sync flag
    if (m_StssAtom == NULL) {
        sample.SetSync(true);
    } else {
        sample.SetSync(m_StssAtom->IsSampleSync(index));
    }

    // set the offset
    sample.SetOffset(offset);

    // set the data stream
    sample.SetDataStream(m_SampleStream);

    return AP4_SUCCESS;
}


Merge some file writing extension checks from OpenJK.

Thanks Ensiform.
https://github.com/JACoders/OpenJK/commit/05928a57f9e4aae15a3bd0
https://github.com/JACoders/OpenJK/commit/ef124fd0fc48af164581176

void Con_Dump_f (void)
{
	int		l, x, i;
	short	*line;
	fileHandle_t	f;
	int		bufferlen;
	char	*buffer;
	char	filename[MAX_QPATH];

	if (Cmd_Argc() != 2)
	{
		Com_Printf ("usage: condump <filename>\n");
		return;
	}

	Q_strncpyz( filename, Cmd_Argv( 1 ), sizeof( filename ) );
	COM_DefaultExtension( filename, sizeof( filename ), ".txt" );

	f = FS_FOpenFileWrite( filename );
	if (!f)
	{
		Com_Printf ("ERROR: couldn't open %s.\n", filename);
		return;
	}

	Com_Printf ("Dumped console text to %s.\n", filename );

	// skip empty lines
	for (l = con.current - con.totallines + 1 ; l <= con.current ; l++)
	{
		line = con.text + (l%con.totallines)*con.linewidth;
		for (x=0 ; x<con.linewidth ; x++)
			if ((line[x] & 0xff) != ' ')
				break;
		if (x != con.linewidth)
			break;
	}

#ifdef _WIN32
	bufferlen = con.linewidth + 3 * sizeof ( char );
#else
	bufferlen = con.linewidth + 2 * sizeof ( char );
#endif

	buffer = Hunk_AllocateTempMemory( bufferlen );

	// write the remaining lines
	buffer[bufferlen-1] = 0;
	for ( ; l <= con.current ; l++)
	{
		line = con.text + (l%con.totallines)*con.linewidth;
		for(i=0; i<con.linewidth; i++)
			buffer[i] = line[i] & 0xff;
		for (x=con.linewidth-1 ; x>=0 ; x--)
		{
			if (buffer[x] == ' ')
				buffer[x] = 0;
			else
				break;
		}
#ifdef _WIN32
		Q_strcat(buffer, bufferlen, "\r\n");
#else
		Q_strcat(buffer, bufferlen, "\n");
#endif
		FS_Write(buffer, strlen(buffer), f);
	}

	Hunk_FreeTempMemory( buffer );
	FS_FCloseFile( f );
}