int vp8_decode_frame(VP8D_COMP *pbi) {
  vp8_reader *const bc = &pbi->mbc[8];
  VP8_COMMON *const pc = &pbi->common;
  MACROBLOCKD *const xd = &pbi->mb;
  const unsigned char *data = pbi->fragments.ptrs[0];
  const unsigned int data_sz = pbi->fragments.sizes[0];
  const unsigned char *data_end = data + data_sz;
  ptrdiff_t first_partition_length_in_bytes;

  int i, j, k, l;
  const int *const mb_feature_data_bits = vp8_mb_feature_data_bits;
  int corrupt_tokens = 0;
  int prev_independent_partitions = pbi->independent_partitions;

  YV12_BUFFER_CONFIG *yv12_fb_new = pbi->dec_fb_ref[INTRA_FRAME];

  /* start with no corruption of current frame */
  xd->corrupted = 0;
  yv12_fb_new->corrupted = 0;

  if (data_end - data < 3) {
    if (!pbi->ec_active) {
      vpx_internal_error(&pc->error, VPX_CODEC_CORRUPT_FRAME,
                         "Truncated packet");
    }

    /* Declare the missing frame as an inter frame since it will
       be handled as an inter frame when we have estimated its
       motion vectors. */
    pc->frame_type = INTER_FRAME;
    pc->version = 0;
    pc->show_frame = 1;
    first_partition_length_in_bytes = 0;
  } else {
    unsigned char clear_buffer[10];
    const unsigned char *clear = data;
    if (pbi->decrypt_cb) {
      int n = (int)VPXMIN(sizeof(clear_buffer), data_sz);
      pbi->decrypt_cb(pbi->decrypt_state, data, clear_buffer, n);
      clear = clear_buffer;
    }

    pc->frame_type = (FRAME_TYPE)(clear[0] & 1);
    pc->version = (clear[0] >> 1) & 7;
    pc->show_frame = (clear[0] >> 4) & 1;
    first_partition_length_in_bytes =
        (clear[0] | (clear[1] << 8) | (clear[2] << 16)) >> 5;

    if (!pbi->ec_active && (data + first_partition_length_in_bytes > data_end ||
                            data + first_partition_length_in_bytes < data)) {
      vpx_internal_error(&pc->error, VPX_CODEC_CORRUPT_FRAME,
                         "Truncated packet or corrupt partition 0 length");
    }

    data += 3;
    clear += 3;

    vp8_setup_version(pc);

    if (pc->frame_type == KEY_FRAME) {
      /* vet via sync code */
      /* When error concealment is enabled we should only check the sync
       * code if we have enough bits available
       */
      if (data + 3 < data_end) {
        if (clear[0] != 0x9d || clear[1] != 0x01 || clear[2] != 0x2a) {
          vpx_internal_error(&pc->error, VPX_CODEC_UNSUP_BITSTREAM,
                             "Invalid frame sync code");
        }
      }

      /* If error concealment is enabled we should only parse the new size
       * if we have enough data. Otherwise we will end up with the wrong
       * size.
       */
      if (data + 6 < data_end) {
        pc->Width = (clear[3] | (clear[4] << 8)) & 0x3fff;
        pc->horiz_scale = clear[4] >> 6;
        pc->Height = (clear[5] | (clear[6] << 8)) & 0x3fff;
        pc->vert_scale = clear[6] >> 6;
        data += 7;
      } else if (!pbi->ec_active) {
        vpx_internal_error(&pc->error, VPX_CODEC_CORRUPT_FRAME,
                           "Truncated key frame header");
      } else {
        /* Error concealment is active, clear the frame. */
        data = data_end;
      }
    } else {
      memcpy(&xd->pre, yv12_fb_new, sizeof(YV12_BUFFER_CONFIG));
      memcpy(&xd->dst, yv12_fb_new, sizeof(YV12_BUFFER_CONFIG));
    }
  }
  if ((!pbi->decoded_key_frame && pc->frame_type != KEY_FRAME)) {
    return -1;
  }

  init_frame(pbi);

  if (vp8dx_start_decode(bc, data, (unsigned int)(data_end - data),
                         pbi->decrypt_cb, pbi->decrypt_state)) {
    vpx_internal_error(&pc->error, VPX_CODEC_MEM_ERROR,
                       "Failed to allocate bool decoder 0");
  }
  if (pc->frame_type == KEY_FRAME) {
    (void)vp8_read_bit(bc);  // colorspace
    pc->clamp_type = (CLAMP_TYPE)vp8_read_bit(bc);
  }

  /* Is segmentation enabled */
  xd->segmentation_enabled = (unsigned char)vp8_read_bit(bc);

  if (xd->segmentation_enabled) {
    /* Signal whether or not the segmentation map is being explicitly updated
     * this frame. */
    xd->update_mb_segmentation_map = (unsigned char)vp8_read_bit(bc);
    xd->update_mb_segmentation_data = (unsigned char)vp8_read_bit(bc);

    if (xd->update_mb_segmentation_data) {
      xd->mb_segement_abs_delta = (unsigned char)vp8_read_bit(bc);

      memset(xd->segment_feature_data, 0, sizeof(xd->segment_feature_data));

      /* For each segmentation feature (Quant and loop filter level) */
      for (i = 0; i < MB_LVL_MAX; ++i) {
        for (j = 0; j < MAX_MB_SEGMENTS; ++j) {
          /* Frame level data */
          if (vp8_read_bit(bc)) {
            xd->segment_feature_data[i][j] =
                (signed char)vp8_read_literal(bc, mb_feature_data_bits[i]);

            if (vp8_read_bit(bc)) {
              xd->segment_feature_data[i][j] = -xd->segment_feature_data[i][j];
            }
          } else {
            xd->segment_feature_data[i][j] = 0;
          }
        }
      }
    }

    if (xd->update_mb_segmentation_map) {
      /* Which macro block level features are enabled */
      memset(xd->mb_segment_tree_probs, 255, sizeof(xd->mb_segment_tree_probs));

      /* Read the probs used to decode the segment id for each macro block. */
      for (i = 0; i < MB_FEATURE_TREE_PROBS; ++i) {
        /* If not explicitly set value is defaulted to 255 by memset above */
        if (vp8_read_bit(bc)) {
          xd->mb_segment_tree_probs[i] = (vp8_prob)vp8_read_literal(bc, 8);
        }
      }
    }
  } else {
    /* No segmentation updates on this frame */
    xd->update_mb_segmentation_map = 0;
    xd->update_mb_segmentation_data = 0;
  }

  /* Read the loop filter level and type */
  pc->filter_type = (LOOPFILTERTYPE)vp8_read_bit(bc);
  pc->filter_level = vp8_read_literal(bc, 6);
  pc->sharpness_level = vp8_read_literal(bc, 3);

  /* Read in loop filter deltas applied at the MB level based on mode or ref
   * frame. */
  xd->mode_ref_lf_delta_update = 0;
  xd->mode_ref_lf_delta_enabled = (unsigned char)vp8_read_bit(bc);

  if (xd->mode_ref_lf_delta_enabled) {
    /* Do the deltas need to be updated */
    xd->mode_ref_lf_delta_update = (unsigned char)vp8_read_bit(bc);

    if (xd->mode_ref_lf_delta_update) {
      /* Send update */
      for (i = 0; i < MAX_REF_LF_DELTAS; ++i) {
        if (vp8_read_bit(bc)) {
          /*sign = vp8_read_bit( bc );*/
          xd->ref_lf_deltas[i] = (signed char)vp8_read_literal(bc, 6);

          if (vp8_read_bit(bc)) { /* Apply sign */
            xd->ref_lf_deltas[i] = xd->ref_lf_deltas[i] * -1;
          }
        }
      }

      /* Send update */
      for (i = 0; i < MAX_MODE_LF_DELTAS; ++i) {
        if (vp8_read_bit(bc)) {
          /*sign = vp8_read_bit( bc );*/
          xd->mode_lf_deltas[i] = (signed char)vp8_read_literal(bc, 6);

          if (vp8_read_bit(bc)) { /* Apply sign */
            xd->mode_lf_deltas[i] = xd->mode_lf_deltas[i] * -1;
          }
        }
      }
    }
  }

  setup_token_decoder(pbi, data + first_partition_length_in_bytes);

  xd->current_bc = &pbi->mbc[0];

  /* Read the default quantizers. */
  {
    int Q, q_update;

    Q = vp8_read_literal(bc, 7); /* AC 1st order Q = default */
    pc->base_qindex = Q;
    q_update = 0;
    pc->y1dc_delta_q = get_delta_q(bc, pc->y1dc_delta_q, &q_update);
    pc->y2dc_delta_q = get_delta_q(bc, pc->y2dc_delta_q, &q_update);
    pc->y2ac_delta_q = get_delta_q(bc, pc->y2ac_delta_q, &q_update);
    pc->uvdc_delta_q = get_delta_q(bc, pc->uvdc_delta_q, &q_update);
    pc->uvac_delta_q = get_delta_q(bc, pc->uvac_delta_q, &q_update);

    if (q_update) vp8cx_init_de_quantizer(pbi);

    /* MB level dequantizer setup */
    vp8_mb_init_dequantizer(pbi, &pbi->mb);
  }

  /* Determine if the golden frame or ARF buffer should be updated and how.
   * For all non key frames the GF and ARF refresh flags and sign bias
   * flags must be set explicitly.
   */
  if (pc->frame_type != KEY_FRAME) {
    /* Should the GF or ARF be updated from the current frame */
    pc->refresh_golden_frame = vp8_read_bit(bc);
#if CONFIG_ERROR_CONCEALMENT
    /* Assume we shouldn't refresh golden if the bit is missing */
    xd->corrupted |= vp8dx_bool_error(bc);
    if (pbi->ec_active && xd->corrupted) pc->refresh_golden_frame = 0;
#endif

    pc->refresh_alt_ref_frame = vp8_read_bit(bc);
#if CONFIG_ERROR_CONCEALMENT
    /* Assume we shouldn't refresh altref if the bit is missing */
    xd->corrupted |= vp8dx_bool_error(bc);
    if (pbi->ec_active && xd->corrupted) pc->refresh_alt_ref_frame = 0;
#endif

    /* Buffer to buffer copy flags. */
    pc->copy_buffer_to_gf = 0;

    if (!pc->refresh_golden_frame) {
      pc->copy_buffer_to_gf = vp8_read_literal(bc, 2);
    }

#if CONFIG_ERROR_CONCEALMENT
    /* Assume we shouldn't copy to the golden if the bit is missing */
    xd->corrupted |= vp8dx_bool_error(bc);
    if (pbi->ec_active && xd->corrupted) pc->copy_buffer_to_gf = 0;
#endif

    pc->copy_buffer_to_arf = 0;

    if (!pc->refresh_alt_ref_frame) {
      pc->copy_buffer_to_arf = vp8_read_literal(bc, 2);
    }

#if CONFIG_ERROR_CONCEALMENT
    /* Assume we shouldn't copy to the alt-ref if the bit is missing */
    xd->corrupted |= vp8dx_bool_error(bc);
    if (pbi->ec_active && xd->corrupted) pc->copy_buffer_to_arf = 0;
#endif

    pc->ref_frame_sign_bias[GOLDEN_FRAME] = vp8_read_bit(bc);
    pc->ref_frame_sign_bias[ALTREF_FRAME] = vp8_read_bit(bc);
  }

  pc->refresh_entropy_probs = vp8_read_bit(bc);
#if CONFIG_ERROR_CONCEALMENT
  /* Assume we shouldn't refresh the probabilities if the bit is
   * missing */
  xd->corrupted |= vp8dx_bool_error(bc);
  if (pbi->ec_active && xd->corrupted) pc->refresh_entropy_probs = 0;
#endif
  if (pc->refresh_entropy_probs == 0) {
    memcpy(&pc->lfc, &pc->fc, sizeof(pc->fc));
  }

  pc->refresh_last_frame = pc->frame_type == KEY_FRAME || vp8_read_bit(bc);

#if CONFIG_ERROR_CONCEALMENT
  /* Assume we should refresh the last frame if the bit is missing */
  xd->corrupted |= vp8dx_bool_error(bc);
  if (pbi->ec_active && xd->corrupted) pc->refresh_last_frame = 1;
#endif

  if (0) {
    FILE *z = fopen("decodestats.stt", "a");
    fprintf(z, "%6d F:%d,G:%d,A:%d,L:%d,Q:%d\n", pc->current_video_frame,
            pc->frame_type, pc->refresh_golden_frame, pc->refresh_alt_ref_frame,
            pc->refresh_last_frame, pc->base_qindex);
    fclose(z);
  }

  {
    pbi->independent_partitions = 1;

    /* read coef probability tree */
    for (i = 0; i < BLOCK_TYPES; ++i) {
      for (j = 0; j < COEF_BANDS; ++j) {
        for (k = 0; k < PREV_COEF_CONTEXTS; ++k) {
          for (l = 0; l < ENTROPY_NODES; ++l) {
            vp8_prob *const p = pc->fc.coef_probs[i][j][k] + l;

            if (vp8_read(bc, vp8_coef_update_probs[i][j][k][l])) {
              *p = (vp8_prob)vp8_read_literal(bc, 8);
            }
            if (k > 0 && *p != pc->fc.coef_probs[i][j][k - 1][l]) {
              pbi->independent_partitions = 0;
            }
          }
        }
      }
    }
  }

  /* clear out the coeff buffer */
  memset(xd->qcoeff, 0, sizeof(xd->qcoeff));

  vp8_decode_mode_mvs(pbi);

#if CONFIG_ERROR_CONCEALMENT
  if (pbi->ec_active &&
      pbi->mvs_corrupt_from_mb < (unsigned int)pc->mb_cols * pc->mb_rows) {
    /* Motion vectors are missing in this frame. We will try to estimate
     * them and then continue decoding the frame as usual */
    vp8_estimate_missing_mvs(pbi);
  }
#endif

  memset(pc->above_context, 0, sizeof(ENTROPY_CONTEXT_PLANES) * pc->mb_cols);
  pbi->frame_corrupt_residual = 0;

#if CONFIG_MULTITHREAD
  if (pbi->b_multithreaded_rd && pc->multi_token_partition != ONE_PARTITION) {
    unsigned int thread;
    vp8mt_decode_mb_rows(pbi, xd);
    vp8_yv12_extend_frame_borders(yv12_fb_new);
    for (thread = 0; thread < pbi->decoding_thread_count; ++thread) {
      corrupt_tokens |= pbi->mb_row_di[thread].mbd.corrupted;
    }
  } else
#endif
  {
    decode_mb_rows(pbi);
    corrupt_tokens |= xd->corrupted;
  }

  /* Collect information about decoder corruption. */
  /* 1. Check first boolean decoder for errors. */
  yv12_fb_new->corrupted = vp8dx_bool_error(bc);
  /* 2. Check the macroblock information */
  yv12_fb_new->corrupted |= corrupt_tokens;

  if (!pbi->decoded_key_frame) {
    if (pc->frame_type == KEY_FRAME && !yv12_fb_new->corrupted) {
      pbi->decoded_key_frame = 1;
    } else {
      vpx_internal_error(&pbi->common.error, VPX_CODEC_CORRUPT_FRAME,
                         "A stream must start with a complete key frame");
    }
  }

  /* vpx_log("Decoder: Frame Decoded, Size Roughly:%d bytes
   * \n",bc->pos+pbi->bc2.pos); */

  if (pc->refresh_entropy_probs == 0) {
    memcpy(&pc->fc, &pc->lfc, sizeof(pc->fc));
    pbi->independent_partitions = prev_independent_partitions;
  }

#ifdef PACKET_TESTING
  {
    FILE *f = fopen("decompressor.VP8", "ab");
    unsigned int size = pbi->bc2.pos + pbi->bc.pos + 8;
    fwrite((void *)&size, 4, 1, f);
    fwrite((void *)pbi->Source, size, 1, f);
    fclose(f);
  }
#endif

  return 0;
}


hs-v2: Only log once the connection warning to v2

Closes #40474

Signed-off-by: David Goulet <dgoulet@torproject.org>

connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
                                           origin_circuit_t *circ,
                                           crypt_path_t *cpath)
{
  socks_request_t *socks = conn->socks_request;
  const or_options_t *options = get_options();
  connection_t *base_conn = ENTRY_TO_CONN(conn);
  time_t now = time(NULL);
  rewrite_result_t rr;

  /* First we'll do the rewrite part.  Let's see if we get a reasonable
   * answer.
   */
  memset(&rr, 0, sizeof(rr));
  connection_ap_handshake_rewrite(conn,&rr);

  if (rr.should_close) {
    /* connection_ap_handshake_rewrite told us to close the connection:
     * either because it sent back an answer, or because it sent back an
     * error */
    connection_mark_unattached_ap(conn, rr.end_reason);
    if (END_STREAM_REASON_DONE == (rr.end_reason & END_STREAM_REASON_MASK))
      return 0;
    else
      return -1;
  }

  const time_t map_expires = rr.map_expires;
  const int automap = rr.automap;
  const addressmap_entry_source_t exit_source = rr.exit_source;

  /* Now see whether the hostname is bogus.  This could happen because of an
   * onion hostname whose format we don't recognize. */
  hostname_type_t addresstype;
  if (!parse_extended_hostname(socks->address, &addresstype)) {
    control_event_client_status(LOG_WARN, "SOCKS_BAD_HOSTNAME HOSTNAME=%s",
                                escaped(socks->address));
    if (addresstype == BAD_HOSTNAME) {
      conn->socks_request->socks_extended_error_code = SOCKS5_HS_BAD_ADDRESS;
    }
    connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
    return -1;
  }

  /* If this is a .exit hostname, strip off the .name.exit part, and
   * see whether we're willing to connect there, and otherwise handle the
   * .exit address.
   *
   * We'll set chosen_exit_name and/or close the connection as appropriate.
   */
  if (addresstype == EXIT_HOSTNAME) {
    /* If StrictNodes is not set, then .exit overrides ExcludeNodes but
     * not ExcludeExitNodes. */
    routerset_t *excludeset = options->StrictNodes ?
      options->ExcludeExitNodesUnion_ : options->ExcludeExitNodes;
    const node_t *node = NULL;

    /* If this .exit was added by an AUTOMAP, then it came straight from
     * a user.  That's not safe. */
    if (exit_source == ADDRMAPSRC_AUTOMAP) {
      /* Whoops; this one is stale.  It must have gotten added earlier?
       * (Probably this is not possible, since AllowDotExit no longer
       * exists.) */
      log_warn(LD_APP,"Stale automapped address for '%s.exit'. Refusing.",
               safe_str_client(socks->address));
      control_event_client_status(LOG_WARN, "SOCKS_BAD_HOSTNAME HOSTNAME=%s",
                                  escaped(socks->address));
      connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
      tor_assert_nonfatal_unreached();
      return -1;
    }

    /* Double-check to make sure there are no .exits coming from
     * impossible/weird sources. */
    if (exit_source == ADDRMAPSRC_DNS || exit_source == ADDRMAPSRC_NONE) {
      /* It shouldn't be possible to get a .exit address from any of these
       * sources. */
      log_warn(LD_BUG,"Address '%s.exit', with impossible source for the "
               ".exit part. Refusing.",
               safe_str_client(socks->address));
      control_event_client_status(LOG_WARN, "SOCKS_BAD_HOSTNAME HOSTNAME=%s",
                                  escaped(socks->address));
      connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
      return -1;
    }

    tor_assert(!automap);

    /* Now, find the character before the .(name) part.
     * (The ".exit" part got stripped off by "parse_extended_hostname").
     *
     * We're going to put the exit name into conn->chosen_exit_name, and
     * look up a node correspondingly. */
    char *s = strrchr(socks->address,'.');
    if (s) {
      /* The address was of the form "(stuff).(name).exit */
      if (s[1] != '\0') {
        /* Looks like a real .exit one. */
        conn->chosen_exit_name = tor_strdup(s+1);
        node = node_get_by_nickname(conn->chosen_exit_name, 0);

        if (exit_source == ADDRMAPSRC_TRACKEXIT) {
          /* We 5 tries before it expires the addressmap */
          conn->chosen_exit_retries = TRACKHOSTEXITS_RETRIES;
        }
        *s = 0;
      } else {
        /* Oops, the address was (stuff)..exit.  That's not okay. */
        log_warn(LD_APP,"Malformed exit address '%s.exit'. Refusing.",
                 safe_str_client(socks->address));
        control_event_client_status(LOG_WARN, "SOCKS_BAD_HOSTNAME HOSTNAME=%s",
                                    escaped(socks->address));
        connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
        return -1;
      }
    } else {
      /* It looks like they just asked for "foo.exit".  That's a special
       * form that means (foo's address).foo.exit. */

      conn->chosen_exit_name = tor_strdup(socks->address);
      node = node_get_by_nickname(conn->chosen_exit_name, 0);
      if (node) {
        *socks->address = 0;
        node_get_address_string(node, socks->address, sizeof(socks->address));
      }
    }

    /* Now make sure that the chosen exit exists... */
    if (!node) {
      log_warn(LD_APP,
               "Unrecognized relay in exit address '%s.exit'. Refusing.",
               safe_str_client(socks->address));
      connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
      return -1;
    }
    /* ...and make sure that it isn't excluded. */
    if (routerset_contains_node(excludeset, node)) {
      log_warn(LD_APP,
               "Excluded relay in exit address '%s.exit'. Refusing.",
               safe_str_client(socks->address));
      connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
      return -1;
    }
    /* XXXX-1090 Should we also allow foo.bar.exit if ExitNodes is set and
       Bar is not listed in it?  I say yes, but our revised manpage branch
       implies no. */
  }

  /* Now, we handle everything that isn't a .onion address. */
  if (addresstype != ONION_V3_HOSTNAME && addresstype != ONION_V2_HOSTNAME) {
    /* Not a hidden-service request.  It's either a hostname or an IP,
     * possibly with a .exit that we stripped off.  We're going to check
     * if we're allowed to connect/resolve there, and then launch the
     * appropriate request. */

    /* Check for funny characters in the address. */
    if (address_is_invalid_destination(socks->address, 1)) {
      control_event_client_status(LOG_WARN, "SOCKS_BAD_HOSTNAME HOSTNAME=%s",
                                  escaped(socks->address));
      log_warn(LD_APP,
               "Destination '%s' seems to be an invalid hostname. Failing.",
               safe_str_client(socks->address));
      connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
      return -1;
    }

    /* socks->address is a non-onion hostname or IP address.
     * If we can't do any non-onion requests, refuse the connection.
     * If we have a hostname but can't do DNS, refuse the connection.
     * If we have an IP address, but we can't use that address family,
     * refuse the connection.
     *
     * If we can do DNS requests, and we can use at least one address family,
     * then we have to resolve the address first. Then we'll know if it
     * resolves to a usable address family. */

    /* First, check if all non-onion traffic is disabled */
    if (!conn->entry_cfg.dns_request && !conn->entry_cfg.ipv4_traffic
        && !conn->entry_cfg.ipv6_traffic) {
        log_warn(LD_APP, "Refusing to connect to non-hidden-service hostname "
                 "or IP address %s because Port has OnionTrafficOnly set (or "
                 "NoDNSRequest, NoIPv4Traffic, and NoIPv6Traffic).",
                 safe_str_client(socks->address));
        connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY);
        return -1;
    }

    /* Then check if we have a hostname or IP address, and whether DNS or
     * the IP address family are permitted.  Reject if not. */
    tor_addr_t dummy_addr;
    int socks_family = tor_addr_parse(&dummy_addr, socks->address);
    /* family will be -1 for a non-onion hostname that's not an IP */
    if (socks_family == -1) {
      if (!conn->entry_cfg.dns_request) {
        log_warn(LD_APP, "Refusing to connect to hostname %s "
                 "because Port has NoDNSRequest set.",
                 safe_str_client(socks->address));
        connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY);
        return -1;
      }
    } else if (socks_family == AF_INET) {
      if (!conn->entry_cfg.ipv4_traffic) {
        log_warn(LD_APP, "Refusing to connect to IPv4 address %s because "
                 "Port has NoIPv4Traffic set.",
                 safe_str_client(socks->address));
        connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY);
        return -1;
      }
    } else if (socks_family == AF_INET6) {
      if (!conn->entry_cfg.ipv6_traffic) {
        log_warn(LD_APP, "Refusing to connect to IPv6 address %s because "
                 "Port has NoIPv6Traffic set.",
                 safe_str_client(socks->address));
        connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY);
        return -1;
      }
    } else {
      tor_assert_nonfatal_unreached_once();
    }

    /* See if this is a hostname lookup that we can answer immediately.
     * (For example, an attempt to look up the IP address for an IP address.)
     */
    if (socks->command == SOCKS_COMMAND_RESOLVE) {
      tor_addr_t answer;
      /* Reply to resolves immediately if we can. */
      if (tor_addr_parse(&answer, socks->address) >= 0) {/* is it an IP? */
        /* remember _what_ is supposed to have been resolved. */
        strlcpy(socks->address, rr.orig_address, sizeof(socks->address));
        connection_ap_handshake_socks_resolved_addr(conn, &answer, -1,
                                                    map_expires);
        connection_mark_unattached_ap(conn,
                                END_STREAM_REASON_DONE |
                                END_STREAM_REASON_FLAG_ALREADY_SOCKS_REPLIED);
        return 0;
      }
      tor_assert(!automap);
      rep_hist_note_used_resolve(now); /* help predict this next time */
    } else if (socks->command == SOCKS_COMMAND_CONNECT) {
      /* Now see if this is a connect request that we can reject immediately */

      tor_assert(!automap);
      /* Don't allow connections to port 0. */
      if (socks->port == 0) {
        log_notice(LD_APP,"Application asked to connect to port 0. Refusing.");
        connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
        return -1;
      }
      /* You can't make connections to internal addresses, by default.
       * Exceptions are begindir requests (where the address is meaningless),
       * or cases where you've hand-configured a particular exit, thereby
       * making the local address meaningful. */
      if (options->ClientRejectInternalAddresses &&
          !conn->use_begindir && !conn->chosen_exit_name && !circ) {
        /* If we reach this point then we don't want to allow internal
         * addresses.  Check if we got one. */
        tor_addr_t addr;
        if (tor_addr_hostname_is_local(socks->address) ||
            (tor_addr_parse(&addr, socks->address) >= 0 &&
             tor_addr_is_internal(&addr, 0))) {
          /* If this is an explicit private address with no chosen exit node,
           * then we really don't want to try to connect to it.  That's
           * probably an error. */
          if (conn->is_transparent_ap) {
#define WARN_INTRVL_LOOP 300
            static ratelim_t loop_warn_limit = RATELIM_INIT(WARN_INTRVL_LOOP);
            char *m;
            if ((m = rate_limit_log(&loop_warn_limit, approx_time()))) {
              log_warn(LD_NET,
                       "Rejecting request for anonymous connection to private "
                       "address %s on a TransPort or NATDPort.  Possible loop "
                       "in your NAT rules?%s", safe_str_client(socks->address),
                       m);
              tor_free(m);
            }
          } else {
#define WARN_INTRVL_PRIV 300
            static ratelim_t priv_warn_limit = RATELIM_INIT(WARN_INTRVL_PRIV);
            char *m;
            if ((m = rate_limit_log(&priv_warn_limit, approx_time()))) {
              log_warn(LD_NET,
                       "Rejecting SOCKS request for anonymous connection to "
                       "private address %s.%s",
                       safe_str_client(socks->address),m);
              tor_free(m);
            }
          }
          connection_mark_unattached_ap(conn, END_STREAM_REASON_PRIVATE_ADDR);
          return -1;
        }
      } /* end "if we should check for internal addresses" */

      /* Okay.  We're still doing a CONNECT, and it wasn't a private
       * address.  Here we do special handling for literal IP addresses,
       * to see if we should reject this preemptively, and to set up
       * fields in conn->entry_cfg to tell the exit what AF we want. */
      {
        tor_addr_t addr;
        /* XXX Duplicate call to tor_addr_parse. */
        if (tor_addr_parse(&addr, socks->address) >= 0) {
          /* If we reach this point, it's an IPv4 or an IPv6 address. */
          sa_family_t family = tor_addr_family(&addr);

          if ((family == AF_INET && ! conn->entry_cfg.ipv4_traffic) ||
              (family == AF_INET6 && ! conn->entry_cfg.ipv6_traffic)) {
            /* You can't do an IPv4 address on a v6-only socks listener,
             * or vice versa. */
            log_warn(LD_NET, "Rejecting SOCKS request for an IP address "
                     "family that this listener does not support.");
            connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY);
            return -1;
          } else if (family == AF_INET6 && socks->socks_version == 4) {
            /* You can't make a socks4 request to an IPv6 address. Socks4
             * doesn't support that. */
            log_warn(LD_NET, "Rejecting SOCKS4 request for an IPv6 address.");
            connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY);
            return -1;
          } else if (socks->socks_version == 4 &&
                     !conn->entry_cfg.ipv4_traffic) {
            /* You can't do any kind of Socks4 request when IPv4 is forbidden.
             *
             * XXX raise this check outside the enclosing block? */
            log_warn(LD_NET, "Rejecting SOCKS4 request on a listener with "
                     "no IPv4 traffic supported.");
            connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY);
            return -1;
          } else if (family == AF_INET6) {
            /* Tell the exit: we won't accept any ipv4 connection to an IPv6
             * address. */
            conn->entry_cfg.ipv4_traffic = 0;
          } else if (family == AF_INET) {
            /* Tell the exit: we won't accept any ipv6 connection to an IPv4
             * address. */
            conn->entry_cfg.ipv6_traffic = 0;
          }

          /* Next, yet another check: we know it's a direct IP address. Is it
           * the IP address of a known relay and its ORPort, or of a directory
           * authority and its OR or Dir Port? If so, and if a consensus param
           * says to, then exit relays will refuse this request (see ticket
           * 2667 for details). Let's just refuse it locally right now, to
           * save time and network load but also to give the user a more
           * useful log message. */
          if (!network_reentry_is_allowed() &&
              nodelist_reentry_contains(&addr, socks->port)) {
            log_warn(LD_APP, "Not attempting connection to %s:%d because "
                     "the network would reject it. Are you trying to send "
                     "Tor traffic over Tor? This traffic can be harmful to "
                     "the Tor network. If you really need it, try using "
                     "a bridge as a workaround.",
                     safe_str_client(socks->address), socks->port);
            connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
            return -1;
          }
        }
      }

      /* we never allow IPv6 answers on socks4. (TODO: Is this smart?) */
      if (socks->socks_version == 4)
        conn->entry_cfg.ipv6_traffic = 0;

      /* Still handling CONNECT. Now, check for exit enclaves.  (Which we
       * don't do on BEGIN_DIR, or when there is a chosen exit.)
       *
       * TODO: Should we remove this?  Exit enclaves are nutty and don't
       * work very well
       */
      if (!conn->use_begindir && !conn->chosen_exit_name && !circ) {
        /* see if we can find a suitable enclave exit */
        const node_t *r =
          router_find_exact_exit_enclave(socks->address, socks->port);
        if (r) {
          log_info(LD_APP,
                   "Redirecting address %s to exit at enclave router %s",
                   safe_str_client(socks->address), node_describe(r));
          /* use the hex digest, not nickname, in case there are two
             routers with this nickname */
          conn->chosen_exit_name =
            tor_strdup(hex_str(r->identity, DIGEST_LEN));
          conn->chosen_exit_optional = 1;
        }
      }

      /* Still handling CONNECT: warn or reject if it's using a dangerous
       * port. */
      if (!conn->use_begindir && !conn->chosen_exit_name && !circ)
        if (consider_plaintext_ports(conn, socks->port) < 0)
          return -1;

      /* Remember the port so that we will predict that more requests
         there will happen in the future. */
      if (!conn->use_begindir) {
        /* help predict this next time */
        rep_hist_note_used_port(now, socks->port);
      }
    } else if (socks->command == SOCKS_COMMAND_RESOLVE_PTR) {
      rep_hist_note_used_resolve(now); /* help predict this next time */
      /* no extra processing needed */
    } else {
      /* We should only be doing CONNECT, RESOLVE, or RESOLVE_PTR! */
      tor_fragile_assert();
    }

    /* Okay. At this point we've set chosen_exit_name if needed, rewritten the
     * address, and decided not to reject it for any number of reasons. Now
     * mark the connection as waiting for a circuit, and try to attach it!
     */
    base_conn->state = AP_CONN_STATE_CIRCUIT_WAIT;

    /* If we were given a circuit to attach to, try to attach. Otherwise,
     * try to find a good one and attach to that. */
    int rv;
    if (circ) {
      rv = connection_ap_handshake_attach_chosen_circuit(conn, circ, cpath);
    } else {
      /* We'll try to attach it at the next event loop, or whenever
       * we call connection_ap_attach_pending() */
      connection_ap_mark_as_pending_circuit(conn);
      rv = 0;
    }

    /* If the above function returned 0 then we're waiting for a circuit.
     * if it returned 1, we're attached.  Both are okay.  But if it returned
     * -1, there was an error, so make sure the connection is marked, and
     * return -1. */
    if (rv < 0) {
      if (!base_conn->marked_for_close)
        connection_mark_unattached_ap(conn, END_STREAM_REASON_CANT_ATTACH);
      return -1;
    }

    return 0;
  } else {
    /* If we get here, it's a request for a .onion address! */

    /* We don't support v2 onions anymore. Log a warning and bail. */
    if (addresstype == ONION_V2_HOSTNAME) {
      log_warn(LD_PROTOCOL, "Tried to connect to a v2 onion address, but this "
               "version of Tor no longer supports them. Please encourage the "
               "site operator to upgrade. For more information see "
               "https://blog.torproject.org/v2-deprecation-timeline.");
      control_event_client_status(LOG_WARN, "SOCKS_BAD_HOSTNAME HOSTNAME=%s",
                                  escaped(socks->address));
      /* Send back the 0xF6 extended code indicating a bad hostname. This is
       * mostly so Tor Browser can make a proper UX with regards to v2
       * addresses. */
      conn->socks_request->socks_extended_error_code = SOCKS5_HS_BAD_ADDRESS;
      connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
      return -1;
    }

    tor_assert(addresstype == ONION_V3_HOSTNAME);
    tor_assert(!automap);
    return connection_ap_handle_onion(conn, socks, circ);
  }

  return 0; /* unreached but keeps the compiler happy */
}

An information disclosure exists in Brave Browser Desktop prior to version 1.28.62, where logged warning messages that included timestamps of connections to V2 onion domains in tor.log.


connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
                                           origin_circuit_t *circ,
                                           crypt_path_t *cpath)
{
  socks_request_t *socks = conn->socks_request;
  const or_options_t *options = get_options();
  connection_t *base_conn = ENTRY_TO_CONN(conn);
  time_t now = time(NULL);
  rewrite_result_t rr;

  /* First we'll do the rewrite part.  Let's see if we get a reasonable
   * answer.
   */
  memset(&rr, 0, sizeof(rr));
  connection_ap_handshake_rewrite(conn,&rr);

  if (rr.should_close) {
    /* connection_ap_handshake_rewrite told us to close the connection:
     * either because it sent back an answer, or because it sent back an
     * error */
    connection_mark_unattached_ap(conn, rr.end_reason);
    if (END_STREAM_REASON_DONE == (rr.end_reason & END_STREAM_REASON_MASK))
      return 0;
    else
      return -1;
  }

  const time_t map_expires = rr.map_expires;
  const int automap = rr.automap;
  const addressmap_entry_source_t exit_source = rr.exit_source;

  /* Now see whether the hostname is bogus.  This could happen because of an
   * onion hostname whose format we don't recognize. */
  hostname_type_t addresstype;
  if (!parse_extended_hostname(socks->address, &addresstype)) {
    control_event_client_status(LOG_WARN, "SOCKS_BAD_HOSTNAME HOSTNAME=%s",
                                escaped(socks->address));
    if (addresstype == BAD_HOSTNAME) {
      conn->socks_request->socks_extended_error_code = SOCKS5_HS_BAD_ADDRESS;
    }
    connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
    return -1;
  }

  /* If this is a .exit hostname, strip off the .name.exit part, and
   * see whether we're willing to connect there, and otherwise handle the
   * .exit address.
   *
   * We'll set chosen_exit_name and/or close the connection as appropriate.
   */
  if (addresstype == EXIT_HOSTNAME) {
    /* If StrictNodes is not set, then .exit overrides ExcludeNodes but
     * not ExcludeExitNodes. */
    routerset_t *excludeset = options->StrictNodes ?
      options->ExcludeExitNodesUnion_ : options->ExcludeExitNodes;
    const node_t *node = NULL;

    /* If this .exit was added by an AUTOMAP, then it came straight from
     * a user.  That's not safe. */
    if (exit_source == ADDRMAPSRC_AUTOMAP) {
      /* Whoops; this one is stale.  It must have gotten added earlier?
       * (Probably this is not possible, since AllowDotExit no longer
       * exists.) */
      log_warn(LD_APP,"Stale automapped address for '%s.exit'. Refusing.",
               safe_str_client(socks->address));
      control_event_client_status(LOG_WARN, "SOCKS_BAD_HOSTNAME HOSTNAME=%s",
                                  escaped(socks->address));
      connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
      tor_assert_nonfatal_unreached();
      return -1;
    }

    /* Double-check to make sure there are no .exits coming from
     * impossible/weird sources. */
    if (exit_source == ADDRMAPSRC_DNS || exit_source == ADDRMAPSRC_NONE) {
      /* It shouldn't be possible to get a .exit address from any of these
       * sources. */
      log_warn(LD_BUG,"Address '%s.exit', with impossible source for the "
               ".exit part. Refusing.",
               safe_str_client(socks->address));
      control_event_client_status(LOG_WARN, "SOCKS_BAD_HOSTNAME HOSTNAME=%s",
                                  escaped(socks->address));
      connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
      return -1;
    }

    tor_assert(!automap);

    /* Now, find the character before the .(name) part.
     * (The ".exit" part got stripped off by "parse_extended_hostname").
     *
     * We're going to put the exit name into conn->chosen_exit_name, and
     * look up a node correspondingly. */
    char *s = strrchr(socks->address,'.');
    if (s) {
      /* The address was of the form "(stuff).(name).exit */
      if (s[1] != '\0') {
        /* Looks like a real .exit one. */
        conn->chosen_exit_name = tor_strdup(s+1);
        node = node_get_by_nickname(conn->chosen_exit_name, 0);

        if (exit_source == ADDRMAPSRC_TRACKEXIT) {
          /* We 5 tries before it expires the addressmap */
          conn->chosen_exit_retries = TRACKHOSTEXITS_RETRIES;
        }
        *s = 0;
      } else {
        /* Oops, the address was (stuff)..exit.  That's not okay. */
        log_warn(LD_APP,"Malformed exit address '%s.exit'. Refusing.",
                 safe_str_client(socks->address));
        control_event_client_status(LOG_WARN, "SOCKS_BAD_HOSTNAME HOSTNAME=%s",
                                    escaped(socks->address));
        connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
        return -1;
      }
    } else {
      /* It looks like they just asked for "foo.exit".  That's a special
       * form that means (foo's address).foo.exit. */

      conn->chosen_exit_name = tor_strdup(socks->address);
      node = node_get_by_nickname(conn->chosen_exit_name, 0);
      if (node) {
        *socks->address = 0;
        node_get_address_string(node, socks->address, sizeof(socks->address));
      }
    }

    /* Now make sure that the chosen exit exists... */
    if (!node) {
      log_warn(LD_APP,
               "Unrecognized relay in exit address '%s.exit'. Refusing.",
               safe_str_client(socks->address));
      connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
      return -1;
    }
    /* ...and make sure that it isn't excluded. */
    if (routerset_contains_node(excludeset, node)) {
      log_warn(LD_APP,
               "Excluded relay in exit address '%s.exit'. Refusing.",
               safe_str_client(socks->address));
      connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
      return -1;
    }
    /* XXXX-1090 Should we also allow foo.bar.exit if ExitNodes is set and
       Bar is not listed in it?  I say yes, but our revised manpage branch
       implies no. */
  }

  /* Now, we handle everything that isn't a .onion address. */
  if (addresstype != ONION_V3_HOSTNAME && addresstype != ONION_V2_HOSTNAME) {
    /* Not a hidden-service request.  It's either a hostname or an IP,
     * possibly with a .exit that we stripped off.  We're going to check
     * if we're allowed to connect/resolve there, and then launch the
     * appropriate request. */

    /* Check for funny characters in the address. */
    if (address_is_invalid_destination(socks->address, 1)) {
      control_event_client_status(LOG_WARN, "SOCKS_BAD_HOSTNAME HOSTNAME=%s",
                                  escaped(socks->address));
      log_warn(LD_APP,
               "Destination '%s' seems to be an invalid hostname. Failing.",
               safe_str_client(socks->address));
      connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
      return -1;
    }

    /* socks->address is a non-onion hostname or IP address.
     * If we can't do any non-onion requests, refuse the connection.
     * If we have a hostname but can't do DNS, refuse the connection.
     * If we have an IP address, but we can't use that address family,
     * refuse the connection.
     *
     * If we can do DNS requests, and we can use at least one address family,
     * then we have to resolve the address first. Then we'll know if it
     * resolves to a usable address family. */

    /* First, check if all non-onion traffic is disabled */
    if (!conn->entry_cfg.dns_request && !conn->entry_cfg.ipv4_traffic
        && !conn->entry_cfg.ipv6_traffic) {
        log_warn(LD_APP, "Refusing to connect to non-hidden-service hostname "
                 "or IP address %s because Port has OnionTrafficOnly set (or "
                 "NoDNSRequest, NoIPv4Traffic, and NoIPv6Traffic).",
                 safe_str_client(socks->address));
        connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY);
        return -1;
    }

    /* Then check if we have a hostname or IP address, and whether DNS or
     * the IP address family are permitted.  Reject if not. */
    tor_addr_t dummy_addr;
    int socks_family = tor_addr_parse(&dummy_addr, socks->address);
    /* family will be -1 for a non-onion hostname that's not an IP */
    if (socks_family == -1) {
      if (!conn->entry_cfg.dns_request) {
        log_warn(LD_APP, "Refusing to connect to hostname %s "
                 "because Port has NoDNSRequest set.",
                 safe_str_client(socks->address));
        connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY);
        return -1;
      }
    } else if (socks_family == AF_INET) {
      if (!conn->entry_cfg.ipv4_traffic) {
        log_warn(LD_APP, "Refusing to connect to IPv4 address %s because "
                 "Port has NoIPv4Traffic set.",
                 safe_str_client(socks->address));
        connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY);
        return -1;
      }
    } else if (socks_family == AF_INET6) {
      if (!conn->entry_cfg.ipv6_traffic) {
        log_warn(LD_APP, "Refusing to connect to IPv6 address %s because "
                 "Port has NoIPv6Traffic set.",
                 safe_str_client(socks->address));
        connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY);
        return -1;
      }
    } else {
      tor_assert_nonfatal_unreached_once();
    }

    /* See if this is a hostname lookup that we can answer immediately.
     * (For example, an attempt to look up the IP address for an IP address.)
     */
    if (socks->command == SOCKS_COMMAND_RESOLVE) {
      tor_addr_t answer;
      /* Reply to resolves immediately if we can. */
      if (tor_addr_parse(&answer, socks->address) >= 0) {/* is it an IP? */
        /* remember _what_ is supposed to have been resolved. */
        strlcpy(socks->address, rr.orig_address, sizeof(socks->address));
        connection_ap_handshake_socks_resolved_addr(conn, &answer, -1,
                                                    map_expires);
        connection_mark_unattached_ap(conn,
                                END_STREAM_REASON_DONE |
                                END_STREAM_REASON_FLAG_ALREADY_SOCKS_REPLIED);
        return 0;
      }
      tor_assert(!automap);
      rep_hist_note_used_resolve(now); /* help predict this next time */
    } else if (socks->command == SOCKS_COMMAND_CONNECT) {
      /* Now see if this is a connect request that we can reject immediately */

      tor_assert(!automap);
      /* Don't allow connections to port 0. */
      if (socks->port == 0) {
        log_notice(LD_APP,"Application asked to connect to port 0. Refusing.");
        connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
        return -1;
      }
      /* You can't make connections to internal addresses, by default.
       * Exceptions are begindir requests (where the address is meaningless),
       * or cases where you've hand-configured a particular exit, thereby
       * making the local address meaningful. */
      if (options->ClientRejectInternalAddresses &&
          !conn->use_begindir && !conn->chosen_exit_name && !circ) {
        /* If we reach this point then we don't want to allow internal
         * addresses.  Check if we got one. */
        tor_addr_t addr;
        if (tor_addr_hostname_is_local(socks->address) ||
            (tor_addr_parse(&addr, socks->address) >= 0 &&
             tor_addr_is_internal(&addr, 0))) {
          /* If this is an explicit private address with no chosen exit node,
           * then we really don't want to try to connect to it.  That's
           * probably an error. */
          if (conn->is_transparent_ap) {
#define WARN_INTRVL_LOOP 300
            static ratelim_t loop_warn_limit = RATELIM_INIT(WARN_INTRVL_LOOP);
            char *m;
            if ((m = rate_limit_log(&loop_warn_limit, approx_time()))) {
              log_warn(LD_NET,
                       "Rejecting request for anonymous connection to private "
                       "address %s on a TransPort or NATDPort.  Possible loop "
                       "in your NAT rules?%s", safe_str_client(socks->address),
                       m);
              tor_free(m);
            }
          } else {
#define WARN_INTRVL_PRIV 300
            static ratelim_t priv_warn_limit = RATELIM_INIT(WARN_INTRVL_PRIV);
            char *m;
            if ((m = rate_limit_log(&priv_warn_limit, approx_time()))) {
              log_warn(LD_NET,
                       "Rejecting SOCKS request for anonymous connection to "
                       "private address %s.%s",
                       safe_str_client(socks->address),m);
              tor_free(m);
            }
          }
          connection_mark_unattached_ap(conn, END_STREAM_REASON_PRIVATE_ADDR);
          return -1;
        }
      } /* end "if we should check for internal addresses" */

      /* Okay.  We're still doing a CONNECT, and it wasn't a private
       * address.  Here we do special handling for literal IP addresses,
       * to see if we should reject this preemptively, and to set up
       * fields in conn->entry_cfg to tell the exit what AF we want. */
      {
        tor_addr_t addr;
        /* XXX Duplicate call to tor_addr_parse. */
        if (tor_addr_parse(&addr, socks->address) >= 0) {
          /* If we reach this point, it's an IPv4 or an IPv6 address. */
          sa_family_t family = tor_addr_family(&addr);

          if ((family == AF_INET && ! conn->entry_cfg.ipv4_traffic) ||
              (family == AF_INET6 && ! conn->entry_cfg.ipv6_traffic)) {
            /* You can't do an IPv4 address on a v6-only socks listener,
             * or vice versa. */
            log_warn(LD_NET, "Rejecting SOCKS request for an IP address "
                     "family that this listener does not support.");
            connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY);
            return -1;
          } else if (family == AF_INET6 && socks->socks_version == 4) {
            /* You can't make a socks4 request to an IPv6 address. Socks4
             * doesn't support that. */
            log_warn(LD_NET, "Rejecting SOCKS4 request for an IPv6 address.");
            connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY);
            return -1;
          } else if (socks->socks_version == 4 &&
                     !conn->entry_cfg.ipv4_traffic) {
            /* You can't do any kind of Socks4 request when IPv4 is forbidden.
             *
             * XXX raise this check outside the enclosing block? */
            log_warn(LD_NET, "Rejecting SOCKS4 request on a listener with "
                     "no IPv4 traffic supported.");
            connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY);
            return -1;
          } else if (family == AF_INET6) {
            /* Tell the exit: we won't accept any ipv4 connection to an IPv6
             * address. */
            conn->entry_cfg.ipv4_traffic = 0;
          } else if (family == AF_INET) {
            /* Tell the exit: we won't accept any ipv6 connection to an IPv4
             * address. */
            conn->entry_cfg.ipv6_traffic = 0;
          }

          /* Next, yet another check: we know it's a direct IP address. Is it
           * the IP address of a known relay and its ORPort, or of a directory
           * authority and its OR or Dir Port? If so, and if a consensus param
           * says to, then exit relays will refuse this request (see ticket
           * 2667 for details). Let's just refuse it locally right now, to
           * save time and network load but also to give the user a more
           * useful log message. */
          if (!network_reentry_is_allowed() &&
              nodelist_reentry_contains(&addr, socks->port)) {
            log_warn(LD_APP, "Not attempting connection to %s:%d because "
                     "the network would reject it. Are you trying to send "
                     "Tor traffic over Tor? This traffic can be harmful to "
                     "the Tor network. If you really need it, try using "
                     "a bridge as a workaround.",
                     safe_str_client(socks->address), socks->port);
            connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
            return -1;
          }
        }
      }

      /* we never allow IPv6 answers on socks4. (TODO: Is this smart?) */
      if (socks->socks_version == 4)
        conn->entry_cfg.ipv6_traffic = 0;

      /* Still handling CONNECT. Now, check for exit enclaves.  (Which we
       * don't do on BEGIN_DIR, or when there is a chosen exit.)
       *
       * TODO: Should we remove this?  Exit enclaves are nutty and don't
       * work very well
       */
      if (!conn->use_begindir && !conn->chosen_exit_name && !circ) {
        /* see if we can find a suitable enclave exit */
        const node_t *r =
          router_find_exact_exit_enclave(socks->address, socks->port);
        if (r) {
          log_info(LD_APP,
                   "Redirecting address %s to exit at enclave router %s",
                   safe_str_client(socks->address), node_describe(r));
          /* use the hex digest, not nickname, in case there are two
             routers with this nickname */
          conn->chosen_exit_name =
            tor_strdup(hex_str(r->identity, DIGEST_LEN));
          conn->chosen_exit_optional = 1;
        }
      }

      /* Still handling CONNECT: warn or reject if it's using a dangerous
       * port. */
      if (!conn->use_begindir && !conn->chosen_exit_name && !circ)
        if (consider_plaintext_ports(conn, socks->port) < 0)
          return -1;

      /* Remember the port so that we will predict that more requests
         there will happen in the future. */
      if (!conn->use_begindir) {
        /* help predict this next time */
        rep_hist_note_used_port(now, socks->port);
      }
    } else if (socks->command == SOCKS_COMMAND_RESOLVE_PTR) {
      rep_hist_note_used_resolve(now); /* help predict this next time */
      /* no extra processing needed */
    } else {
      /* We should only be doing CONNECT, RESOLVE, or RESOLVE_PTR! */
      tor_fragile_assert();
    }

    /* Okay. At this point we've set chosen_exit_name if needed, rewritten the
     * address, and decided not to reject it for any number of reasons. Now
     * mark the connection as waiting for a circuit, and try to attach it!
     */
    base_conn->state = AP_CONN_STATE_CIRCUIT_WAIT;

    /* If we were given a circuit to attach to, try to attach. Otherwise,
     * try to find a good one and attach to that. */
    int rv;
    if (circ) {
      rv = connection_ap_handshake_attach_chosen_circuit(conn, circ, cpath);
    } else {
      /* We'll try to attach it at the next event loop, or whenever
       * we call connection_ap_attach_pending() */
      connection_ap_mark_as_pending_circuit(conn);
      rv = 0;
    }

    /* If the above function returned 0 then we're waiting for a circuit.
     * if it returned 1, we're attached.  Both are okay.  But if it returned
     * -1, there was an error, so make sure the connection is marked, and
     * return -1. */
    if (rv < 0) {
      if (!base_conn->marked_for_close)
        connection_mark_unattached_ap(conn, END_STREAM_REASON_CANT_ATTACH);
      return -1;
    }

    return 0;
  } else {
    /* If we get here, it's a request for a .onion address! */

    /* We don't support v2 onions anymore. Log a warning and bail. */
    if (addresstype == ONION_V2_HOSTNAME) {
      static bool log_once = false;
      if (!log_once) {
        log_warn(LD_PROTOCOL, "Tried to connect to a v2 onion address, but "
                 "this version of Tor no longer supports them. Please "
                 "encourage the site operator to upgrade. For more "
                 "information see "
                 "https://blog.torproject.org/v2-deprecation-timeline.");
        log_once = true;
      }
      control_event_client_status(LOG_WARN, "SOCKS_BAD_HOSTNAME HOSTNAME=%s",
                                  escaped(socks->address));
      /* Send back the 0xF6 extended code indicating a bad hostname. This is
       * mostly so Tor Browser can make a proper UX with regards to v2
       * addresses. */
      conn->socks_request->socks_extended_error_code = SOCKS5_HS_BAD_ADDRESS;
      connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
      return -1;
    }

    tor_assert(addresstype == ONION_V3_HOSTNAME);
    tor_assert(!automap);
    return connection_ap_handle_onion(conn, socks, circ);
  }

  return 0; /* unreached but keeps the compiler happy */
}


static UINT serial_process_irp_write(SERIAL_DEVICE* serial, IRP* irp)
{
	UINT32 Length;
	UINT64 Offset;
	DWORD nbWritten = 0;

	if (Stream_GetRemainingLength(irp->input) < 32)
		return ERROR_INVALID_DATA;

	Stream_Read_UINT32(irp->input, Length); /* Length (4 bytes) */
	Stream_Read_UINT64(irp->input, Offset); /* Offset (8 bytes) */
	Stream_Seek(irp->input, 20);            /* Padding (20 bytes) */
	/* MS-RDPESP 3.2.5.1.5: The Offset field is ignored
	 * assert(Offset == 0);
	 *
	 * Using a serial printer, noticed though this field could be
	 * set.
	 */
	WLog_Print(serial->log, WLOG_DEBUG, "writing %" PRIu32 " bytes to %s", Length,
	           serial->device.name);

	/* FIXME: CommWriteFile to be replaced by WriteFile */
	if (CommWriteFile(serial->hComm, Stream_Pointer(irp->input), Length, &nbWritten, NULL))
	{
		irp->IoStatus = STATUS_SUCCESS;
	}
	else
	{
		WLog_Print(serial->log, WLOG_DEBUG,
		           "write failure to %s, nbWritten=%" PRIu32 ", last-error: 0x%08" PRIX32 "",
		           serial->device.name, nbWritten, GetLastError());
		irp->IoStatus = _GetLastErrorToIoStatus(serial);
	}

	WLog_Print(serial->log, WLOG_DEBUG, "%" PRIu32 " bytes written to %s", nbWritten,
	           serial->device.name);
	Stream_Write_UINT32(irp->output, nbWritten); /* Length (4 bytes) */
	Stream_Write_UINT8(irp->output, 0);          /* Padding (1 byte) */
	return CHANNEL_RC_OK;
}

In FreeRDP before 2.1.0, there is an out-of-bound read in irp functions (parallel_process_irp_create, serial_process_irp_create, drive_process_irp_write, printer_process_irp_write, rdpei_recv_pdu, serial_process_irp_write). This has been fixed in 2.1.0.


static UINT serial_process_irp_write(SERIAL_DEVICE* serial, IRP* irp)
{
	UINT32 Length;
	UINT64 Offset;
	void* ptr;
	DWORD nbWritten = 0;

	if (Stream_GetRemainingLength(irp->input) < 32)
		return ERROR_INVALID_DATA;

	Stream_Read_UINT32(irp->input, Length); /* Length (4 bytes) */
	Stream_Read_UINT64(irp->input, Offset); /* Offset (8 bytes) */
	if (!Stream_SafeSeek(irp->input, 20))   /* Padding (20 bytes) */
		return ERROR_INVALID_DATA;

	/* MS-RDPESP 3.2.5.1.5: The Offset field is ignored
	 * assert(Offset == 0);
	 *
	 * Using a serial printer, noticed though this field could be
	 * set.
	 */
	WLog_Print(serial->log, WLOG_DEBUG, "writing %" PRIu32 " bytes to %s", Length,
	           serial->device.name);

	ptr = Stream_Pointer(irp->input);
	if (!Stream_SafeSeek(irp->input, Length))
		return ERROR_INVALID_DATA;
	/* FIXME: CommWriteFile to be replaced by WriteFile */
	if (CommWriteFile(serial->hComm, ptr, Length, &nbWritten, NULL))
	{
		irp->IoStatus = STATUS_SUCCESS;
	}
	else
	{
		WLog_Print(serial->log, WLOG_DEBUG,
		           "write failure to %s, nbWritten=%" PRIu32 ", last-error: 0x%08" PRIX32 "",
		           serial->device.name, nbWritten, GetLastError());
		irp->IoStatus = _GetLastErrorToIoStatus(serial);
	}

	WLog_Print(serial->log, WLOG_DEBUG, "%" PRIu32 " bytes written to %s", nbWritten,
	           serial->device.name);
	Stream_Write_UINT32(irp->output, nbWritten); /* Length (4 bytes) */
	Stream_Write_UINT8(irp->output, 0);          /* Padding (1 byte) */
	return CHANNEL_RC_OK;
}


static UINT rdpei_recv_pdu(RDPEI_CHANNEL_CALLBACK* callback, wStream* s)
{
	UINT16 eventId;
	UINT32 pduLength;
	UINT error;
	Stream_Read_UINT16(s, eventId);   /* eventId (2 bytes) */
	Stream_Read_UINT32(s, pduLength); /* pduLength (4 bytes) */
#ifdef WITH_DEBUG_RDPEI
	WLog_DBG(TAG, "rdpei_recv_pdu: eventId: %" PRIu16 " (%s) length: %" PRIu32 "", eventId,
	         rdpei_eventid_string(eventId), pduLength);
#endif

	switch (eventId)
	{
		case EVENTID_SC_READY:
			if ((error = rdpei_recv_sc_ready_pdu(callback, s)))
			{
				WLog_ERR(TAG, "rdpei_recv_sc_ready_pdu failed with error %" PRIu32 "!", error);
				return error;
			}

			if ((error = rdpei_send_cs_ready_pdu(callback)))
			{
				WLog_ERR(TAG, "rdpei_send_cs_ready_pdu failed with error %" PRIu32 "!", error);
				return error;
			}

			break;

		case EVENTID_SUSPEND_TOUCH:
			if ((error = rdpei_recv_suspend_touch_pdu(callback, s)))
			{
				WLog_ERR(TAG, "rdpei_recv_suspend_touch_pdu failed with error %" PRIu32 "!", error);
				return error;
			}

			break;

		case EVENTID_RESUME_TOUCH:
			if ((error = rdpei_recv_resume_touch_pdu(callback, s)))
			{
				WLog_ERR(TAG, "rdpei_recv_resume_touch_pdu failed with error %" PRIu32 "!", error);
				return error;
			}

			break;

		default:
			break;
	}

	return CHANNEL_RC_OK;
}


static UINT rdpei_recv_pdu(RDPEI_CHANNEL_CALLBACK* callback, wStream* s)
{
	UINT16 eventId;
	UINT32 pduLength;
	UINT error;
	if (Stream_GetRemainingLength(s) < 6)
		return ERROR_INVALID_DATA;

	Stream_Read_UINT16(s, eventId);   /* eventId (2 bytes) */
	Stream_Read_UINT32(s, pduLength); /* pduLength (4 bytes) */
#ifdef WITH_DEBUG_RDPEI
	WLog_DBG(TAG, "rdpei_recv_pdu: eventId: %" PRIu16 " (%s) length: %" PRIu32 "", eventId,
	         rdpei_eventid_string(eventId), pduLength);
#endif

	switch (eventId)
	{
		case EVENTID_SC_READY:
			if ((error = rdpei_recv_sc_ready_pdu(callback, s)))
			{
				WLog_ERR(TAG, "rdpei_recv_sc_ready_pdu failed with error %" PRIu32 "!", error);
				return error;
			}

			if ((error = rdpei_send_cs_ready_pdu(callback)))
			{
				WLog_ERR(TAG, "rdpei_send_cs_ready_pdu failed with error %" PRIu32 "!", error);
				return error;
			}

			break;

		case EVENTID_SUSPEND_TOUCH:
			if ((error = rdpei_recv_suspend_touch_pdu(callback, s)))
			{
				WLog_ERR(TAG, "rdpei_recv_suspend_touch_pdu failed with error %" PRIu32 "!", error);
				return error;
			}

			break;

		case EVENTID_RESUME_TOUCH:
			if ((error = rdpei_recv_resume_touch_pdu(callback, s)))
			{
				WLog_ERR(TAG, "rdpei_recv_resume_touch_pdu failed with error %" PRIu32 "!", error);
				return error;
			}

			break;

		default:
			break;
	}

	return CHANNEL_RC_OK;
}


static UINT printer_process_irp_write(PRINTER_DEVICE* printer_dev, IRP* irp)
{
	UINT32 Length;
	UINT64 Offset;
	rdpPrintJob* printjob = NULL;
	UINT error = CHANNEL_RC_OK;
	Stream_Read_UINT32(irp->input, Length);
	Stream_Read_UINT64(irp->input, Offset);
	Stream_Seek(irp->input, 20); /* Padding */

	if (printer_dev->printer)
		printjob = printer_dev->printer->FindPrintJob(printer_dev->printer, irp->FileId);

	if (!printjob)
	{
		irp->IoStatus = STATUS_UNSUCCESSFUL;
		Length = 0;
	}
	else
	{
		error = printjob->Write(printjob, Stream_Pointer(irp->input), Length);
	}

	if (error)
	{
		WLog_ERR(TAG, "printjob->Write failed with error %" PRIu32 "!", error);
		return error;
	}

	Stream_Write_UINT32(irp->output, Length);
	Stream_Write_UINT8(irp->output, 0); /* Padding */
	return irp->Complete(irp);
}


static UINT printer_process_irp_write(PRINTER_DEVICE* printer_dev, IRP* irp)
{
	UINT32 Length;
	UINT64 Offset;
	rdpPrintJob* printjob = NULL;
	UINT error = CHANNEL_RC_OK;
	void* ptr;

	if (Stream_GetRemainingLength(irp->input) < 32)
		return ERROR_INVALID_DATA;
	Stream_Read_UINT32(irp->input, Length);
	Stream_Read_UINT64(irp->input, Offset);
	Stream_Seek(irp->input, 20); /* Padding */
	ptr = Stream_Pointer(irp->input);
	if (!Stream_SafeSeek(irp->input, Length))
		return ERROR_INVALID_DATA;
	if (printer_dev->printer)
		printjob = printer_dev->printer->FindPrintJob(printer_dev->printer, irp->FileId);

	if (!printjob)
	{
		irp->IoStatus = STATUS_UNSUCCESSFUL;
		Length = 0;
	}
	else
	{
		error = printjob->Write(printjob, ptr, Length);
	}

	if (error)
	{
		WLog_ERR(TAG, "printjob->Write failed with error %" PRIu32 "!", error);
		return error;
	}

	Stream_Write_UINT32(irp->output, Length);
	Stream_Write_UINT8(irp->output, 0); /* Padding */
	return irp->Complete(irp);
}


static UINT serial_process_irp_create(SERIAL_DEVICE* serial, IRP* irp)
{
	DWORD DesiredAccess;
	DWORD SharedAccess;
	DWORD CreateDisposition;
	UINT32 PathLength;

	if (Stream_GetRemainingLength(irp->input) < 32)
		return ERROR_INVALID_DATA;

	Stream_Read_UINT32(irp->input, DesiredAccess);     /* DesiredAccess (4 bytes) */
	Stream_Seek_UINT64(irp->input);                    /* AllocationSize (8 bytes) */
	Stream_Seek_UINT32(irp->input);                    /* FileAttributes (4 bytes) */
	Stream_Read_UINT32(irp->input, SharedAccess);      /* SharedAccess (4 bytes) */
	Stream_Read_UINT32(irp->input, CreateDisposition); /* CreateDisposition (4 bytes) */
	Stream_Seek_UINT32(irp->input);                    /* CreateOptions (4 bytes) */
	Stream_Read_UINT32(irp->input, PathLength);        /* PathLength (4 bytes) */

	if (Stream_GetRemainingLength(irp->input) < PathLength)
		return ERROR_INVALID_DATA;

	Stream_Seek(irp->input, PathLength); /* Path (variable) */
	assert(PathLength == 0);             /* MS-RDPESP 2.2.2.2 */
#ifndef _WIN32
	/* Windows 2012 server sends on a first call :
	 *     DesiredAccess     = 0x00100080: SYNCHRONIZE | FILE_READ_ATTRIBUTES
	 *     SharedAccess      = 0x00000007: FILE_SHARE_DELETE | FILE_SHARE_WRITE | FILE_SHARE_READ
	 *     CreateDisposition = 0x00000001: CREATE_NEW
	 *
	 * then Windows 2012 sends :
	 *     DesiredAccess     = 0x00120089: SYNCHRONIZE | READ_CONTROL | FILE_READ_ATTRIBUTES |
	 * FILE_READ_EA | FILE_READ_DATA SharedAccess      = 0x00000007: FILE_SHARE_DELETE |
	 * FILE_SHARE_WRITE | FILE_SHARE_READ CreateDisposition = 0x00000001: CREATE_NEW
	 *
	 * assert(DesiredAccess == (GENERIC_READ | GENERIC_WRITE));
	 * assert(SharedAccess == 0);
	 * assert(CreateDisposition == OPEN_EXISTING);
	 *
	 */
	WLog_Print(serial->log, WLOG_DEBUG,
	           "DesiredAccess: 0x%" PRIX32 ", SharedAccess: 0x%" PRIX32
	           ", CreateDisposition: 0x%" PRIX32 "",
	           DesiredAccess, SharedAccess, CreateDisposition);
	/* FIXME: As of today only the flags below are supported by CommCreateFileA: */
	DesiredAccess = GENERIC_READ | GENERIC_WRITE;
	SharedAccess = 0;
	CreateDisposition = OPEN_EXISTING;
#endif
	serial->hComm =
	    CreateFile(serial->device.name, DesiredAccess, SharedAccess, NULL, /* SecurityAttributes */
	               CreateDisposition, 0,                                   /* FlagsAndAttributes */
	               NULL);                                                  /* TemplateFile */

	if (!serial->hComm || (serial->hComm == INVALID_HANDLE_VALUE))
	{
		WLog_Print(serial->log, WLOG_WARN, "CreateFile failure: %s last-error: 0x%08" PRIX32 "",
		           serial->device.name, GetLastError());
		irp->IoStatus = STATUS_UNSUCCESSFUL;
		goto error_handle;
	}

	_comm_setServerSerialDriver(serial->hComm, serial->ServerSerialDriverId);
	_comm_set_permissive(serial->hComm, serial->permissive);
	/* NOTE: binary mode/raw mode required for the redirection. On
	 * Linux, CommCreateFileA forces this setting.
	 */
	/* ZeroMemory(&dcb, sizeof(DCB)); */
	/* dcb.DCBlength = sizeof(DCB); */
	/* GetCommState(serial->hComm, &dcb); */
	/* dcb.fBinary = TRUE; */
	/* SetCommState(serial->hComm, &dcb); */
	assert(irp->FileId == 0);
	irp->FileId = irp->devman->id_sequence++; /* FIXME: why not ((WINPR_COMM*)hComm)->fd? */
	irp->IoStatus = STATUS_SUCCESS;
	WLog_Print(serial->log, WLOG_DEBUG, "%s (DeviceId: %" PRIu32 ", FileId: %" PRIu32 ") created.",
	           serial->device.name, irp->device->id, irp->FileId);
error_handle:
	Stream_Write_UINT32(irp->output, irp->FileId); /* FileId (4 bytes) */
	Stream_Write_UINT8(irp->output, 0);            /* Information (1 byte) */
	return CHANNEL_RC_OK;
}


static UINT serial_process_irp_create(SERIAL_DEVICE* serial, IRP* irp)
{
	DWORD DesiredAccess;
	DWORD SharedAccess;
	DWORD CreateDisposition;
	UINT32 PathLength;

	if (Stream_GetRemainingLength(irp->input) < 32)
		return ERROR_INVALID_DATA;

	Stream_Read_UINT32(irp->input, DesiredAccess);     /* DesiredAccess (4 bytes) */
	Stream_Seek_UINT64(irp->input);                    /* AllocationSize (8 bytes) */
	Stream_Seek_UINT32(irp->input);                    /* FileAttributes (4 bytes) */
	Stream_Read_UINT32(irp->input, SharedAccess);      /* SharedAccess (4 bytes) */
	Stream_Read_UINT32(irp->input, CreateDisposition); /* CreateDisposition (4 bytes) */
	Stream_Seek_UINT32(irp->input);                    /* CreateOptions (4 bytes) */
	Stream_Read_UINT32(irp->input, PathLength);        /* PathLength (4 bytes) */

	if (!Stream_SafeSeek(irp->input, PathLength)) /* Path (variable) */
		return ERROR_INVALID_DATA;

	assert(PathLength == 0);             /* MS-RDPESP 2.2.2.2 */
#ifndef _WIN32
	/* Windows 2012 server sends on a first call :
	 *     DesiredAccess     = 0x00100080: SYNCHRONIZE | FILE_READ_ATTRIBUTES
	 *     SharedAccess      = 0x00000007: FILE_SHARE_DELETE | FILE_SHARE_WRITE | FILE_SHARE_READ
	 *     CreateDisposition = 0x00000001: CREATE_NEW
	 *
	 * then Windows 2012 sends :
	 *     DesiredAccess     = 0x00120089: SYNCHRONIZE | READ_CONTROL | FILE_READ_ATTRIBUTES |
	 * FILE_READ_EA | FILE_READ_DATA SharedAccess      = 0x00000007: FILE_SHARE_DELETE |
	 * FILE_SHARE_WRITE | FILE_SHARE_READ CreateDisposition = 0x00000001: CREATE_NEW
	 *
	 * assert(DesiredAccess == (GENERIC_READ | GENERIC_WRITE));
	 * assert(SharedAccess == 0);
	 * assert(CreateDisposition == OPEN_EXISTING);
	 *
	 */
	WLog_Print(serial->log, WLOG_DEBUG,
	           "DesiredAccess: 0x%" PRIX32 ", SharedAccess: 0x%" PRIX32
	           ", CreateDisposition: 0x%" PRIX32 "",
	           DesiredAccess, SharedAccess, CreateDisposition);
	/* FIXME: As of today only the flags below are supported by CommCreateFileA: */
	DesiredAccess = GENERIC_READ | GENERIC_WRITE;
	SharedAccess = 0;
	CreateDisposition = OPEN_EXISTING;
#endif
	serial->hComm =
	    CreateFile(serial->device.name, DesiredAccess, SharedAccess, NULL, /* SecurityAttributes */
	               CreateDisposition, 0,                                   /* FlagsAndAttributes */
	               NULL);                                                  /* TemplateFile */

	if (!serial->hComm || (serial->hComm == INVALID_HANDLE_VALUE))
	{
		WLog_Print(serial->log, WLOG_WARN, "CreateFile failure: %s last-error: 0x%08" PRIX32 "",
		           serial->device.name, GetLastError());
		irp->IoStatus = STATUS_UNSUCCESSFUL;
		goto error_handle;
	}

	_comm_setServerSerialDriver(serial->hComm, serial->ServerSerialDriverId);
	_comm_set_permissive(serial->hComm, serial->permissive);
	/* NOTE: binary mode/raw mode required for the redirection. On
	 * Linux, CommCreateFileA forces this setting.
	 */
	/* ZeroMemory(&dcb, sizeof(DCB)); */
	/* dcb.DCBlength = sizeof(DCB); */
	/* GetCommState(serial->hComm, &dcb); */
	/* dcb.fBinary = TRUE; */
	/* SetCommState(serial->hComm, &dcb); */
	assert(irp->FileId == 0);
	irp->FileId = irp->devman->id_sequence++; /* FIXME: why not ((WINPR_COMM*)hComm)->fd? */
	irp->IoStatus = STATUS_SUCCESS;
	WLog_Print(serial->log, WLOG_DEBUG, "%s (DeviceId: %" PRIu32 ", FileId: %" PRIu32 ") created.",
	           serial->device.name, irp->device->id, irp->FileId);
error_handle:
	Stream_Write_UINT32(irp->output, irp->FileId); /* FileId (4 bytes) */
	Stream_Write_UINT8(irp->output, 0);            /* Information (1 byte) */
	return CHANNEL_RC_OK;
}


static UINT drive_process_irp_write(DRIVE_DEVICE* drive, IRP* irp)
{
	DRIVE_FILE* file;
	UINT32 Length;
	UINT64 Offset;

	if (!drive || !irp || !irp->input || !irp->output || !irp->Complete)
		return ERROR_INVALID_PARAMETER;

	if (Stream_GetRemainingLength(irp->input) < 32)
		return ERROR_INVALID_DATA;

	Stream_Read_UINT32(irp->input, Length);
	Stream_Read_UINT64(irp->input, Offset);
	Stream_Seek(irp->input, 20); /* Padding */
	file = drive_get_file_by_id(drive, irp->FileId);

	if (!file)
	{
		irp->IoStatus = STATUS_UNSUCCESSFUL;
		Length = 0;
	}
	else if (!drive_file_seek(file, Offset))
	{
		irp->IoStatus = drive_map_windows_err(GetLastError());
		Length = 0;
	}
	else if (!drive_file_write(file, Stream_Pointer(irp->input), Length))
	{
		irp->IoStatus = drive_map_windows_err(GetLastError());
		Length = 0;
	}

	Stream_Write_UINT32(irp->output, Length);
	Stream_Write_UINT8(irp->output, 0); /* Padding */
	return irp->Complete(irp);
}


static UINT drive_process_irp_write(DRIVE_DEVICE* drive, IRP* irp)
{
	DRIVE_FILE* file;
	UINT32 Length;
	UINT64 Offset;
	void* ptr;

	if (!drive || !irp || !irp->input || !irp->output || !irp->Complete)
		return ERROR_INVALID_PARAMETER;

	if (Stream_GetRemainingLength(irp->input) < 32)
		return ERROR_INVALID_DATA;

	Stream_Read_UINT32(irp->input, Length);
	Stream_Read_UINT64(irp->input, Offset);
	Stream_Seek(irp->input, 20); /* Padding */
	ptr = Stream_Pointer(irp->input);
	if (!Stream_SafeSeek(irp->input, Length))
		return ERROR_INVALID_DATA;
	file = drive_get_file_by_id(drive, irp->FileId);

	if (!file)
	{
		irp->IoStatus = STATUS_UNSUCCESSFUL;
		Length = 0;
	}
	else if (!drive_file_seek(file, Offset))
	{
		irp->IoStatus = drive_map_windows_err(GetLastError());
		Length = 0;
	}
	else if (!drive_file_write(file, ptr, Length))
	{
		irp->IoStatus = drive_map_windows_err(GetLastError());
		Length = 0;
	}

	Stream_Write_UINT32(irp->output, Length);
	Stream_Write_UINT8(irp->output, 0); /* Padding */
	return irp->Complete(irp);
}


Work around shell code in podcast names (#598)

std::string queueloader::get_filename(const std::string& str) {
	std::string fn = ctrl->get_dlpath();

	if (fn[fn.length()-1] != NEWSBEUTER_PATH_SEP[0])
		fn.append(NEWSBEUTER_PATH_SEP);
	char buf[1024];
	snprintf(buf, sizeof(buf), "%s", str.c_str());
	char * base = basename(buf);
	if (!base || strlen(base) == 0) {
		char lbuf[128];
		time_t t = time(NULL);
		strftime(lbuf, sizeof(lbuf), "%Y-%b-%d-%H%M%S.unknown", localtime(&t));
		fn.append(lbuf);
	} else {
		fn.append(base);
	}
	return fn;
}

Improper Neutralization of Special Elements used in an OS Command in the podcast playback function of Podbeuter in Newsbeuter 0.3 through 2.9 allows remote attackers to perform user-assisted code execution by crafting an RSS item with a media enclosure (i.e., a podcast file) that includes shell metacharacters in its filename, related to pb_controller.cpp and queueloader.cpp, a different vulnerability than CVE-2017-12904.


std::string queueloader::get_filename(const std::string& str) {
	std::string fn = ctrl->get_dlpath();

	if (fn[fn.length()-1] != NEWSBEUTER_PATH_SEP[0])
		fn.append(NEWSBEUTER_PATH_SEP);
	char buf[1024];
	snprintf(buf, sizeof(buf), "%s", str.c_str());
	char * base = basename(buf);
	if (!base || strlen(base) == 0) {
		char lbuf[128];
		time_t t = time(NULL);
		strftime(lbuf, sizeof(lbuf), "%Y-%b-%d-%H%M%S.unknown", localtime(&t));
		fn.append(lbuf);
	} else {
		fn.append(utils::replace_all(base, "'", "%27"));
	}
	return fn;
}


void pb_controller::play_file(const std::string& file) {
	std::string cmdline;
	std::string player = cfg->get_configvalue("player");
	if (player == "")
		return;
	cmdline.append(player);
	cmdline.append(" \"");
	cmdline.append(utils::replace_all(file,"\"", "\\\""));
	cmdline.append("\"");
	stfl::reset();
	LOG(LOG_DEBUG, "pb_controller::play_file: running `%s'", cmdline.c_str());
	::system(cmdline.c_str());
}


void pb_controller::play_file(const std::string& file) {
	std::string cmdline;
	std::string player = cfg->get_configvalue("player");
	if (player == "")
		return;
	cmdline.append(player);
	cmdline.append(" \'");
	cmdline.append(utils::replace_all(file,"'", "%27"));
	cmdline.append("\'");
	stfl::reset();
	LOG(LOG_DEBUG, "pb_controller::play_file: running `%s'", cmdline.c_str());
	::system(cmdline.c_str());
}


auth: Reject DBUS_COOKIE_SHA1 for users other than the server owner

The DBUS_COOKIE_SHA1 authentication mechanism aims to prove ownership
of a shared home directory by having the server write a secret "cookie"
into a .dbus-keyrings subdirectory of the desired identity's home
directory with 0700 permissions, and having the client prove that it can
read the cookie. This never actually worked for non-malicious clients in
the case where server uid != client uid (unless the server and client
both have privileges, such as Linux CAP_DAC_OVERRIDE or traditional
Unix uid 0) because an unprivileged server would fail to write out the
cookie, and an unprivileged client would be unable to read the resulting
file owned by the server.

Additionally, since dbus 1.7.10 we have checked that ~/.dbus-keyrings
is owned by the uid of the server (a side-effect of a check added to
harden our use of XDG_RUNTIME_DIR), further ruling out successful use
by a non-malicious client with a uid differing from the server's.

Joe Vennix of Apple Information Security discovered that the
implementation of DBUS_COOKIE_SHA1 was susceptible to a symbolic link
attack: a malicious client with write access to its own home directory
could manipulate a ~/.dbus-keyrings symlink to cause the DBusServer to
read and write in unintended locations. In the worst case this could
result in the DBusServer reusing a cookie that is known to the
malicious client, and treating that cookie as evidence that a subsequent
client connection came from an attacker-chosen uid, allowing
authentication bypass.

This is mitigated by the fact that by default, the well-known system
dbus-daemon (since 2003) and the well-known session dbus-daemon (in
stable releases since dbus 1.10.0 in 2015) only accept the EXTERNAL
authentication mechanism, and as a result will reject DBUS_COOKIE_SHA1
at an early stage, before manipulating cookies. As a result, this
vulnerability only applies to:

* system or session dbus-daemons with non-standard configuration
* third-party dbus-daemon invocations such as at-spi2-core (although
  in practice at-spi2-core also only accepts EXTERNAL by default)
* third-party uses of DBusServer such as the one in Upstart

Avoiding symlink attacks in a portable way is difficult, because APIs
like openat() and Linux /proc/self/fd are not universally available.
However, because DBUS_COOKIE_SHA1 already doesn't work in practice for
a non-matching uid, we can solve this vulnerability in an easier way
without regressions, by rejecting it early (before looking at
~/.dbus-keyrings) whenever the requested identity doesn't match the
identity of the process hosting the DBusServer.

Signed-off-by: Simon McVittie <smcv@collabora.com>
Closes: https://gitlab.freedesktop.org/dbus/dbus/issues/269
Closes: CVE-2019-12749

sha1_handle_first_client_response (DBusAuth         *auth,
                                   const DBusString *data)
{
  /* We haven't sent a challenge yet, we're expecting a desired
   * username from the client.
   */
  DBusString tmp;
  DBusString tmp2;
  dbus_bool_t retval = FALSE;
  DBusError error = DBUS_ERROR_INIT;

  _dbus_string_set_length (&auth->challenge, 0);

  if (_dbus_string_get_length (data) > 0)
    {
      if (_dbus_string_get_length (&auth->identity) > 0)
        {
          /* Tried to send two auth identities, wtf */
          _dbus_verbose ("%s: client tried to send auth identity, but we already have one\n",
                         DBUS_AUTH_NAME (auth));
          return send_rejected (auth);
        }
      else
        {
          /* this is our auth identity */
          if (!_dbus_string_copy (data, 0, &auth->identity, 0))
            return FALSE;
        }
    }

  if (!_dbus_credentials_add_from_user (auth->desired_identity, data))
    {
      _dbus_verbose ("%s: Did not get a valid username from client\n",
                     DBUS_AUTH_NAME (auth));
      return send_rejected (auth);
    }

  if (!_dbus_string_init (&tmp))
    return FALSE;

  if (!_dbus_string_init (&tmp2))
    {
      _dbus_string_free (&tmp);
      return FALSE;
    }

  /* we cache the keyring for speed, so here we drop it if it's the
   * wrong one. FIXME caching the keyring here is useless since we use
   * a different DBusAuth for every connection.
   */
  if (auth->keyring &&
      !_dbus_keyring_is_for_credentials (auth->keyring,
                                         auth->desired_identity))
    {
      _dbus_keyring_unref (auth->keyring);
      auth->keyring = NULL;
    }

  if (auth->keyring == NULL)
    {
      auth->keyring = _dbus_keyring_new_for_credentials (auth->desired_identity,
                                                         &auth->context,
                                                         &error);

      if (auth->keyring == NULL)
        {
          if (dbus_error_has_name (&error,
                                   DBUS_ERROR_NO_MEMORY))
            {
              dbus_error_free (&error);
              goto out;
            }
          else
            {
              _DBUS_ASSERT_ERROR_IS_SET (&error);
              _dbus_verbose ("%s: Error loading keyring: %s\n",
                             DBUS_AUTH_NAME (auth), error.message);
              if (send_rejected (auth))
                retval = TRUE; /* retval is only about mem */
              dbus_error_free (&error);
              goto out;
            }
        }
      else
        {
          _dbus_assert (!dbus_error_is_set (&error));
        }
    }

  _dbus_assert (auth->keyring != NULL);

  auth->cookie_id = _dbus_keyring_get_best_key (auth->keyring, &error);
  if (auth->cookie_id < 0)
    {
      _DBUS_ASSERT_ERROR_IS_SET (&error);
      _dbus_verbose ("%s: Could not get a cookie ID to send to client: %s\n",
                     DBUS_AUTH_NAME (auth), error.message);
      if (send_rejected (auth))
        retval = TRUE;
      dbus_error_free (&error);
      goto out;
    }
  else
    {
      _dbus_assert (!dbus_error_is_set (&error));
    }

  if (!_dbus_string_copy (&auth->context, 0,
                          &tmp2, _dbus_string_get_length (&tmp2)))
    goto out;

  if (!_dbus_string_append (&tmp2, " "))
    goto out;

  if (!_dbus_string_append_int (&tmp2, auth->cookie_id))
    goto out;

  if (!_dbus_string_append (&tmp2, " "))
    goto out;

  if (!_dbus_generate_random_bytes (&tmp, N_CHALLENGE_BYTES, &error))
    {
      if (dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY))
        {
          dbus_error_free (&error);
          goto out;
        }
      else
        {
          _DBUS_ASSERT_ERROR_IS_SET (&error);
          _dbus_verbose ("%s: Error generating challenge: %s\n",
                         DBUS_AUTH_NAME (auth), error.message);
          if (send_rejected (auth))
            retval = TRUE; /* retval is only about mem */

          dbus_error_free (&error);
          goto out;
        }
    }

  _dbus_string_set_length (&auth->challenge, 0);
  if (!_dbus_string_hex_encode (&tmp, 0, &auth->challenge, 0))
    goto out;

  if (!_dbus_string_hex_encode (&tmp, 0, &tmp2,
                                _dbus_string_get_length (&tmp2)))
    goto out;

  if (!send_data (auth, &tmp2))
    goto out;

  goto_state (auth, &server_state_waiting_for_data);
  retval = TRUE;

 out:
  _dbus_string_zero (&tmp);
  _dbus_string_free (&tmp);
  _dbus_string_zero (&tmp2);
  _dbus_string_free (&tmp2);

  return retval;
}

dbus before 1.10.28, 1.12.x before 1.12.16, and 1.13.x before 1.13.12, as used in DBusServer in Canonical Upstart in Ubuntu 14.04 (and in some, less common, uses of dbus-daemon), allows cookie spoofing because of symlink mishandling in the reference implementation of DBUS_COOKIE_SHA1 in the libdbus library. (This only affects the DBUS_COOKIE_SHA1 authentication mechanism.) A malicious client with write access to its own home directory could manipulate a ~/.dbus-keyrings symlink to cause a DBusServer with a different uid to read and write in unintended locations. In the worst case, this could result in the DBusServer reusing a cookie that is known to the malicious client, and treating that cookie as evidence that a subsequent client connection came from an attacker-chosen uid, allowing authentication bypass.


sha1_handle_first_client_response (DBusAuth         *auth,
                                   const DBusString *data)
{
  /* We haven't sent a challenge yet, we're expecting a desired
   * username from the client.
   */
  DBusString tmp;
  DBusString tmp2;
  dbus_bool_t retval = FALSE;
  DBusError error = DBUS_ERROR_INIT;
  DBusCredentials *myself = NULL;

  _dbus_string_set_length (&auth->challenge, 0);

  if (_dbus_string_get_length (data) > 0)
    {
      if (_dbus_string_get_length (&auth->identity) > 0)
        {
          /* Tried to send two auth identities, wtf */
          _dbus_verbose ("%s: client tried to send auth identity, but we already have one\n",
                         DBUS_AUTH_NAME (auth));
          return send_rejected (auth);
        }
      else
        {
          /* this is our auth identity */
          if (!_dbus_string_copy (data, 0, &auth->identity, 0))
            return FALSE;
        }
    }

  if (!_dbus_credentials_add_from_user (auth->desired_identity, data))
    {
      _dbus_verbose ("%s: Did not get a valid username from client\n",
                     DBUS_AUTH_NAME (auth));
      return send_rejected (auth);
    }

  if (!_dbus_string_init (&tmp))
    return FALSE;

  if (!_dbus_string_init (&tmp2))
    {
      _dbus_string_free (&tmp);
      return FALSE;
    }

  myself = _dbus_credentials_new_from_current_process ();

  if (myself == NULL)
    goto out;

  if (!_dbus_credentials_same_user (myself, auth->desired_identity))
    {
      /*
       * DBUS_COOKIE_SHA1 is not suitable for authenticating that the
       * client is anyone other than the user owning the process
       * containing the DBusServer: we probably aren't allowed to write
       * to other users' home directories. Even if we can (for example
       * uid 0 on traditional Unix or CAP_DAC_OVERRIDE on Linux), we
       * must not, because the other user controls their home directory,
       * and could carry out symlink attacks to make us read from or
       * write to unintended locations. It's difficult to avoid symlink
       * attacks in a portable way, so we just don't try. This isn't a
       * regression, because DBUS_COOKIE_SHA1 never worked for other
       * users anyway.
       */
      _dbus_verbose ("%s: client tried to authenticate as \"%s\", "
                     "but that doesn't match this process",
                     DBUS_AUTH_NAME (auth),
                     _dbus_string_get_const_data (data));
      retval = send_rejected (auth);
      goto out;
    }

  /* we cache the keyring for speed, so here we drop it if it's the
   * wrong one. FIXME caching the keyring here is useless since we use
   * a different DBusAuth for every connection.
   */
  if (auth->keyring &&
      !_dbus_keyring_is_for_credentials (auth->keyring,
                                         auth->desired_identity))
    {
      _dbus_keyring_unref (auth->keyring);
      auth->keyring = NULL;
    }

  if (auth->keyring == NULL)
    {
      auth->keyring = _dbus_keyring_new_for_credentials (auth->desired_identity,
                                                         &auth->context,
                                                         &error);

      if (auth->keyring == NULL)
        {
          if (dbus_error_has_name (&error,
                                   DBUS_ERROR_NO_MEMORY))
            {
              dbus_error_free (&error);
              goto out;
            }
          else
            {
              _DBUS_ASSERT_ERROR_IS_SET (&error);
              _dbus_verbose ("%s: Error loading keyring: %s\n",
                             DBUS_AUTH_NAME (auth), error.message);
              if (send_rejected (auth))
                retval = TRUE; /* retval is only about mem */
              dbus_error_free (&error);
              goto out;
            }
        }
      else
        {
          _dbus_assert (!dbus_error_is_set (&error));
        }
    }

  _dbus_assert (auth->keyring != NULL);

  auth->cookie_id = _dbus_keyring_get_best_key (auth->keyring, &error);
  if (auth->cookie_id < 0)
    {
      _DBUS_ASSERT_ERROR_IS_SET (&error);
      _dbus_verbose ("%s: Could not get a cookie ID to send to client: %s\n",
                     DBUS_AUTH_NAME (auth), error.message);
      if (send_rejected (auth))
        retval = TRUE;
      dbus_error_free (&error);
      goto out;
    }
  else
    {
      _dbus_assert (!dbus_error_is_set (&error));
    }

  if (!_dbus_string_copy (&auth->context, 0,
                          &tmp2, _dbus_string_get_length (&tmp2)))
    goto out;

  if (!_dbus_string_append (&tmp2, " "))
    goto out;

  if (!_dbus_string_append_int (&tmp2, auth->cookie_id))
    goto out;

  if (!_dbus_string_append (&tmp2, " "))
    goto out;

  if (!_dbus_generate_random_bytes (&tmp, N_CHALLENGE_BYTES, &error))
    {
      if (dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY))
        {
          dbus_error_free (&error);
          goto out;
        }
      else
        {
          _DBUS_ASSERT_ERROR_IS_SET (&error);
          _dbus_verbose ("%s: Error generating challenge: %s\n",
                         DBUS_AUTH_NAME (auth), error.message);
          if (send_rejected (auth))
            retval = TRUE; /* retval is only about mem */

          dbus_error_free (&error);
          goto out;
        }
    }

  _dbus_string_set_length (&auth->challenge, 0);
  if (!_dbus_string_hex_encode (&tmp, 0, &auth->challenge, 0))
    goto out;

  if (!_dbus_string_hex_encode (&tmp, 0, &tmp2,
                                _dbus_string_get_length (&tmp2)))
    goto out;

  if (!send_data (auth, &tmp2))
    goto out;

  goto_state (auth, &server_state_waiting_for_data);
  retval = TRUE;

 out:
  _dbus_string_zero (&tmp);
  _dbus_string_free (&tmp);
  _dbus_string_zero (&tmp2);
  _dbus_string_free (&tmp2);

  if (myself != NULL)
    _dbus_credentials_unref (myself);

  return retval;
}


Fix #561. Use proper counter for the idx variable

    ExifData::const_iterator isoSpeed(const ExifData& ed)
    {
        static const char* keys[] = {
            "Exif.Photo.ISOSpeedRatings",
            "Exif.Image.ISOSpeedRatings",
            "Exif.CanonSi.ISOSpeed",
            "Exif.CanonCs.ISOSpeed",
            "Exif.Nikon1.ISOSpeed",
            "Exif.Nikon2.ISOSpeed",
            "Exif.Nikon3.ISOSpeed",
            "Exif.NikonIi.ISO",
            "Exif.NikonIi.ISO2",
            "Exif.MinoltaCsNew.ISOSetting",
            "Exif.MinoltaCsOld.ISOSetting",
            "Exif.MinoltaCs5D.ISOSpeed",
            "Exif.MinoltaCs7D.ISOSpeed",
            "Exif.Sony1Cs.ISOSetting",
            "Exif.Sony2Cs.ISOSetting",
            "Exif.Sony1Cs2.ISOSetting",
            "Exif.Sony2Cs2.ISOSetting",
            "Exif.Sony1MltCsA100.ISOSetting",
            "Exif.Pentax.ISO",
            "Exif.PentaxDng.ISO",
            "Exif.Olympus.ISOSpeed",
            "Exif.Samsung2.ISO",
            "Exif.Casio.ISO",
            "Exif.Casio2.ISO",
            "Exif.Casio2.ISOSpeed"
        };

        struct SensKeyNameList {
            int count;
            const char* keys[3];
        };

        // covers Exif.Phot.SensitivityType values 1-7. Note that SOS, REI and
        // ISO do differ in their meaning. Values coming first in a list (and
        // existing as a tag) are picked up first and used as the "ISO" value.
        static const SensKeyNameList sensitivityKey[] = {
            { 1, { "Exif.Photo.StandardOutputSensitivity" }},
            { 1, { "Exif.Photo.RecommendedExposureIndex" }},
            { 1, { "Exif.Photo.ISOSpeed" }},
            { 2, { "Exif.Photo.RecommendedExposureIndex", "Exif.Photo.StandardOutputSensitivity" }},
            { 2, { "Exif.Photo.ISOSpeed", "Exif.Photo.StandardOutputSensitivity" }},
            { 2, { "Exif.Photo.ISOSpeed", "Exif.Photo.RecommendedExposureIndex" }},
            { 3, { "Exif.Photo.ISOSpeed", "Exif.Photo.RecommendedExposureIndex", "Exif.Photo.StandardOutputSensitivity" }}
        };

        static const char* sensitivityType[] = {
            "Exif.Photo.SensitivityType"
        };

        // Find the first ISO value which is not "0"
        const int cnt = EXV_COUNTOF(keys);
        ExifData::const_iterator md = ed.end();
        long iso_val = -1;
        for (int idx = 0; idx < cnt; ) {
            md = findMetadatum(ed, keys + idx, cnt - idx);
            if (md == ed.end()) break;
            std::ostringstream os;
            md->write(os, &ed);
            bool ok = false;
            iso_val = parseLong(os.str(), ok);
            if (ok && iso_val > 0) break;
            while (strcmp(keys[idx++], md->key().c_str()) != 0 && idx < cnt) {}
            md = ed.end();
        }

        // there is either a possible ISO "overflow" or no legacy
        // ISO tag at all. Check for SensitivityType tag and the referenced
        // ISO value (see EXIF 2.3 Annex G)
        long iso_tmp_val = -1;
        while (iso_tmp_val == -1 && (iso_val == 65535 || md == ed.end())) {
            ExifData::const_iterator md_st = findMetadatum(ed, sensitivityType, 1);
            // no SensitivityType? exit with existing data
            if (md_st == ed.end())
                break;
            // otherwise pick up actual value and grab value accordingly
            std::ostringstream os;
            md_st->write(os, &ed);
            bool ok = false;
            long st_val = parseLong(os.str(), ok);
            // SensivityType out of range or cannot be parsed properly
            if (!ok || st_val < 1 || st_val > 7)
                break;
            // pick up list of ISO tags, and check for at least one of
            // them available.
            const SensKeyNameList *sensKeys = &sensitivityKey[st_val - 1];
            md_st = ed.end();
            for (int idx = 0; idx < sensKeys->count; md_st = ed.end()) {
                md_st = findMetadatum(ed, const_cast<const char**>(sensKeys->keys), sensKeys->count);
                if (md_st == ed.end())
                    break;
                std::ostringstream os_iso;
                md_st->write(os_iso, &ed);
                ok = false;
                iso_tmp_val = parseLong(os_iso.str(), ok);
                // something wrong with the value
                if (ok || iso_tmp_val > 0) {
                    md = md_st;
                    break;
                }
                while (strcmp(sensKeys->keys[idx++], md_st->key().c_str()) != 0 && idx < cnt) {}
            }
            break;
        }

        return md;
    }

Exiv2::isoSpeed in easyaccess.cpp in Exiv2 v0.27-RC2 allows remote attackers to cause a denial of service (NULL pointer dereference and application crash) via a crafted file.


    ExifData::const_iterator isoSpeed(const ExifData& ed)
    {
        static const char* keys[] = {
            "Exif.Photo.ISOSpeedRatings",
            "Exif.Image.ISOSpeedRatings",
            "Exif.CanonSi.ISOSpeed",
            "Exif.CanonCs.ISOSpeed",
            "Exif.Nikon1.ISOSpeed",
            "Exif.Nikon2.ISOSpeed",
            "Exif.Nikon3.ISOSpeed",
            "Exif.NikonIi.ISO",
            "Exif.NikonIi.ISO2",
            "Exif.MinoltaCsNew.ISOSetting",
            "Exif.MinoltaCsOld.ISOSetting",
            "Exif.MinoltaCs5D.ISOSpeed",
            "Exif.MinoltaCs7D.ISOSpeed",
            "Exif.Sony1Cs.ISOSetting",
            "Exif.Sony2Cs.ISOSetting",
            "Exif.Sony1Cs2.ISOSetting",
            "Exif.Sony2Cs2.ISOSetting",
            "Exif.Sony1MltCsA100.ISOSetting",
            "Exif.Pentax.ISO",
            "Exif.PentaxDng.ISO",
            "Exif.Olympus.ISOSpeed",
            "Exif.Samsung2.ISO",
            "Exif.Casio.ISO",
            "Exif.Casio2.ISO",
            "Exif.Casio2.ISOSpeed"
        };

        struct SensKeyNameList {
            int count;
            const char* keys[3];
        };

        // covers Exif.Phot.SensitivityType values 1-7. Note that SOS, REI and
        // ISO do differ in their meaning. Values coming first in a list (and
        // existing as a tag) are picked up first and used as the "ISO" value.
        static const SensKeyNameList sensitivityKey[] = {
            { 1, { "Exif.Photo.StandardOutputSensitivity" }},
            { 1, { "Exif.Photo.RecommendedExposureIndex" }},
            { 1, { "Exif.Photo.ISOSpeed" }},
            { 2, { "Exif.Photo.RecommendedExposureIndex", "Exif.Photo.StandardOutputSensitivity" }},
            { 2, { "Exif.Photo.ISOSpeed", "Exif.Photo.StandardOutputSensitivity" }},
            { 2, { "Exif.Photo.ISOSpeed", "Exif.Photo.RecommendedExposureIndex" }},
            { 3, { "Exif.Photo.ISOSpeed", "Exif.Photo.RecommendedExposureIndex", "Exif.Photo.StandardOutputSensitivity" }}
        };

        static const char* sensitivityType[] = {
            "Exif.Photo.SensitivityType"
        };

        // Find the first ISO value which is not "0"
        const int cnt = EXV_COUNTOF(keys);
        ExifData::const_iterator md = ed.end();
        long iso_val = -1;
        for (int idx = 0; idx < cnt; ) {
            md = findMetadatum(ed, keys + idx, cnt - idx);
            if (md == ed.end()) break;
            std::ostringstream os;
            md->write(os, &ed);
            bool ok = false;
            iso_val = parseLong(os.str(), ok);
            if (ok && iso_val > 0) break;
            while (strcmp(keys[idx++], md->key().c_str()) != 0 && idx < cnt) {}
            md = ed.end();
        }

        // there is either a possible ISO "overflow" or no legacy
        // ISO tag at all. Check for SensitivityType tag and the referenced
        // ISO value (see EXIF 2.3 Annex G)
        long iso_tmp_val = -1;
        while (iso_tmp_val == -1 && (iso_val == 65535 || md == ed.end())) {
            ExifData::const_iterator md_st = findMetadatum(ed, sensitivityType, 1);
            // no SensitivityType? exit with existing data
            if (md_st == ed.end())
                break;
            // otherwise pick up actual value and grab value accordingly
            std::ostringstream os;
            md_st->write(os, &ed);
            bool ok = false;
            const long st_val = parseLong(os.str(), ok);
            // SensivityType out of range or cannot be parsed properly
            if (!ok || st_val < 1 || st_val > 7)
                break;
            // pick up list of ISO tags, and check for at least one of
            // them available.
            const SensKeyNameList *sensKeys = &sensitivityKey[st_val - 1];
            md_st = ed.end();
            for (int idx = 0; idx < sensKeys->count; md_st = ed.end()) {
                md_st = findMetadatum(ed, const_cast<const char**>(sensKeys->keys), sensKeys->count);
                if (md_st == ed.end())
                    break;
                std::ostringstream os_iso;
                md_st->write(os_iso, &ed);
                ok = false;
                iso_tmp_val = parseLong(os_iso.str(), ok);
                // something wrong with the value
                if (ok || iso_tmp_val > 0) {
                    md = md_st;
                    break;
                }
                while (strcmp(sensKeys->keys[idx++], md_st->key().c_str()) != 0 && idx < sensKeys->count) {}
            }
            break;
        }

        return md;
    }