Avoid possible segfault when handling networkstatus vote with bad flavor

Fix for 6530; fix on 0.2.2.6-alpha.

networkstatus_parse_vote_from_string(const char *s, const char **eos_out,
                                     networkstatus_type_t ns_type)
{
  smartlist_t *tokens = smartlist_create();
  smartlist_t *rs_tokens = NULL, *footer_tokens = NULL;
  networkstatus_voter_info_t *voter = NULL;
  networkstatus_t *ns = NULL;
  digests_t ns_digests;
  const char *cert, *end_of_header, *end_of_footer, *s_dup = s;
  directory_token_t *tok;
  int ok;
  struct in_addr in;
  int i, inorder, n_signatures = 0;
  memarea_t *area = NULL, *rs_area = NULL;
  consensus_flavor_t flav = FLAV_NS;

  tor_assert(s);

  if (eos_out)
    *eos_out = NULL;

  if (router_get_networkstatus_v3_hashes(s, &ns_digests)) {
    log_warn(LD_DIR, "Unable to compute digest of network-status");
    goto err;
  }

  area = memarea_new();
  end_of_header = find_start_of_next_routerstatus(s);
  if (tokenize_string(area, s, end_of_header, tokens,
                      (ns_type == NS_TYPE_CONSENSUS) ?
                      networkstatus_consensus_token_table :
                      networkstatus_token_table, 0)) {
    log_warn(LD_DIR, "Error tokenizing network-status vote header");
    goto err;
  }

  ns = tor_malloc_zero(sizeof(networkstatus_t));
  memcpy(&ns->digests, &ns_digests, sizeof(ns_digests));

  tok = find_by_keyword(tokens, K_NETWORK_STATUS_VERSION);
  tor_assert(tok);
  if (tok->n_args > 1) {
    int flavor = networkstatus_parse_flavor_name(tok->args[1]);
    if (flavor < 0) {
      log_warn(LD_DIR, "Can't parse document with unknown flavor %s",
               escaped(tok->args[2]));
      goto err;
    }
    ns->flavor = flav = flavor;
  }
  if (flav != FLAV_NS && ns_type != NS_TYPE_CONSENSUS) {
    log_warn(LD_DIR, "Flavor found on non-consensus networkstatus.");
    goto err;
  }

  if (ns_type != NS_TYPE_CONSENSUS) {
    const char *end_of_cert = NULL;
    if (!(cert = strstr(s, "\ndir-key-certificate-version")))
      goto err;
    ++cert;
    ns->cert = authority_cert_parse_from_string(cert, &end_of_cert);
    if (!ns->cert || !end_of_cert || end_of_cert > end_of_header)
      goto err;
  }

  tok = find_by_keyword(tokens, K_VOTE_STATUS);
  tor_assert(tok->n_args);
  if (!strcmp(tok->args[0], "vote")) {
    ns->type = NS_TYPE_VOTE;
  } else if (!strcmp(tok->args[0], "consensus")) {
    ns->type = NS_TYPE_CONSENSUS;
  } else if (!strcmp(tok->args[0], "opinion")) {
    ns->type = NS_TYPE_OPINION;
  } else {
    log_warn(LD_DIR, "Unrecognized vote status %s in network-status",
             escaped(tok->args[0]));
    goto err;
  }
  if (ns_type != ns->type) {
    log_warn(LD_DIR, "Got the wrong kind of v3 networkstatus.");
    goto err;
  }

  if (ns->type == NS_TYPE_VOTE || ns->type == NS_TYPE_OPINION) {
    tok = find_by_keyword(tokens, K_PUBLISHED);
    if (parse_iso_time(tok->args[0], &ns->published))
      goto err;

    ns->supported_methods = smartlist_create();
    tok = find_opt_by_keyword(tokens, K_CONSENSUS_METHODS);
    if (tok) {
      for (i=0; i < tok->n_args; ++i)
        smartlist_add(ns->supported_methods, tor_strdup(tok->args[i]));
    } else {
      smartlist_add(ns->supported_methods, tor_strdup("1"));
    }
  } else {
    tok = find_opt_by_keyword(tokens, K_CONSENSUS_METHOD);
    if (tok) {
      ns->consensus_method = (int)tor_parse_long(tok->args[0], 10, 1, INT_MAX,
                                                 &ok, NULL);
      if (!ok)
        goto err;
    } else {
      ns->consensus_method = 1;
    }
  }

  tok = find_by_keyword(tokens, K_VALID_AFTER);
  if (parse_iso_time(tok->args[0], &ns->valid_after))
    goto err;

  tok = find_by_keyword(tokens, K_FRESH_UNTIL);
  if (parse_iso_time(tok->args[0], &ns->fresh_until))
    goto err;

  tok = find_by_keyword(tokens, K_VALID_UNTIL);
  if (parse_iso_time(tok->args[0], &ns->valid_until))
    goto err;

  tok = find_by_keyword(tokens, K_VOTING_DELAY);
  tor_assert(tok->n_args >= 2);
  ns->vote_seconds =
    (int) tor_parse_long(tok->args[0], 10, 0, INT_MAX, &ok, NULL);
  if (!ok)
    goto err;
  ns->dist_seconds =
    (int) tor_parse_long(tok->args[1], 10, 0, INT_MAX, &ok, NULL);
  if (!ok)
    goto err;
  if (ns->valid_after + MIN_VOTE_INTERVAL > ns->fresh_until) {
    log_warn(LD_DIR, "Vote/consensus freshness interval is too short");
    goto err;
  }
  if (ns->valid_after + MIN_VOTE_INTERVAL*2 > ns->valid_until) {
    log_warn(LD_DIR, "Vote/consensus liveness interval is too short");
    goto err;
  }
  if (ns->vote_seconds < MIN_VOTE_SECONDS) {
    log_warn(LD_DIR, "Vote seconds is too short");
    goto err;
  }
  if (ns->dist_seconds < MIN_DIST_SECONDS) {
    log_warn(LD_DIR, "Dist seconds is too short");
    goto err;
  }

  if ((tok = find_opt_by_keyword(tokens, K_CLIENT_VERSIONS))) {
    ns->client_versions = tor_strdup(tok->args[0]);
  }
  if ((tok = find_opt_by_keyword(tokens, K_SERVER_VERSIONS))) {
    ns->server_versions = tor_strdup(tok->args[0]);
  }

  tok = find_by_keyword(tokens, K_KNOWN_FLAGS);
  ns->known_flags = smartlist_create();
  inorder = 1;
  for (i = 0; i < tok->n_args; ++i) {
    smartlist_add(ns->known_flags, tor_strdup(tok->args[i]));
    if (i>0 && strcmp(tok->args[i-1], tok->args[i])>= 0) {
      log_warn(LD_DIR, "%s >= %s", tok->args[i-1], tok->args[i]);
      inorder = 0;
    }
  }
  if (!inorder) {
    log_warn(LD_DIR, "known-flags not in order");
    goto err;
  }

  tok = find_opt_by_keyword(tokens, K_PARAMS);
  if (tok) {
    inorder = 1;
    ns->net_params = smartlist_create();
    for (i = 0; i < tok->n_args; ++i) {
      int ok=0;
      char *eq = strchr(tok->args[i], '=');
      if (!eq) {
        log_warn(LD_DIR, "Bad element '%s' in params", escaped(tok->args[i]));
        goto err;
      }
      tor_parse_long(eq+1, 10, INT32_MIN, INT32_MAX, &ok, NULL);
      if (!ok) {
        log_warn(LD_DIR, "Bad element '%s' in params", escaped(tok->args[i]));
        goto err;
      }
      if (i > 0 && strcmp(tok->args[i-1], tok->args[i]) >= 0) {
        log_warn(LD_DIR, "%s >= %s", tok->args[i-1], tok->args[i]);
        inorder = 0;
      }
      smartlist_add(ns->net_params, tor_strdup(tok->args[i]));
    }
    if (!inorder) {
      log_warn(LD_DIR, "params not in order");
      goto err;
    }
  }

  ns->voters = smartlist_create();

  SMARTLIST_FOREACH_BEGIN(tokens, directory_token_t *, _tok) {
    tok = _tok;
    if (tok->tp == K_DIR_SOURCE) {
      tor_assert(tok->n_args >= 6);

      if (voter)
        smartlist_add(ns->voters, voter);
      voter = tor_malloc_zero(sizeof(networkstatus_voter_info_t));
      voter->sigs = smartlist_create();
      if (ns->type != NS_TYPE_CONSENSUS)
        memcpy(voter->vote_digest, ns_digests.d[DIGEST_SHA1], DIGEST_LEN);

      voter->nickname = tor_strdup(tok->args[0]);
      if (strlen(tok->args[1]) != HEX_DIGEST_LEN ||
          base16_decode(voter->identity_digest, sizeof(voter->identity_digest),
                        tok->args[1], HEX_DIGEST_LEN) < 0) {
        log_warn(LD_DIR, "Error decoding identity digest %s in "
                 "network-status vote.", escaped(tok->args[1]));
        goto err;
      }
      if (ns->type != NS_TYPE_CONSENSUS &&
          tor_memneq(ns->cert->cache_info.identity_digest,
                 voter->identity_digest, DIGEST_LEN)) {
        log_warn(LD_DIR,"Mismatch between identities in certificate and vote");
        goto err;
      }
      voter->address = tor_strdup(tok->args[2]);
      if (!tor_inet_aton(tok->args[3], &in)) {
        log_warn(LD_DIR, "Error decoding IP address %s in network-status.",
                 escaped(tok->args[3]));
        goto err;
      }
      voter->addr = ntohl(in.s_addr);
      voter->dir_port = (uint16_t)
        tor_parse_long(tok->args[4], 10, 0, 65535, &ok, NULL);
      if (!ok)
        goto err;
      voter->or_port = (uint16_t)
        tor_parse_long(tok->args[5], 10, 0, 65535, &ok, NULL);
      if (!ok)
        goto err;
    } else if (tok->tp == K_CONTACT) {
      if (!voter || voter->contact) {
        log_warn(LD_DIR, "contact element is out of place.");
        goto err;
      }
      voter->contact = tor_strdup(tok->args[0]);
    } else if (tok->tp == K_VOTE_DIGEST) {
      tor_assert(ns->type == NS_TYPE_CONSENSUS);
      tor_assert(tok->n_args >= 1);
      if (!voter || ! tor_digest_is_zero(voter->vote_digest)) {
        log_warn(LD_DIR, "vote-digest element is out of place.");
        goto err;
      }
      if (strlen(tok->args[0]) != HEX_DIGEST_LEN ||
        base16_decode(voter->vote_digest, sizeof(voter->vote_digest),
                      tok->args[0], HEX_DIGEST_LEN) < 0) {
        log_warn(LD_DIR, "Error decoding vote digest %s in "
                 "network-status consensus.", escaped(tok->args[0]));
        goto err;
      }
    }
  } SMARTLIST_FOREACH_END(_tok);
  if (voter) {
    smartlist_add(ns->voters, voter);
    voter = NULL;
  }
  if (smartlist_len(ns->voters) == 0) {
    log_warn(LD_DIR, "Missing dir-source elements in a vote networkstatus.");
    goto err;
  } else if (ns->type != NS_TYPE_CONSENSUS && smartlist_len(ns->voters) != 1) {
    log_warn(LD_DIR, "Too many dir-source elements in a vote networkstatus.");
    goto err;
  }

  if (ns->type != NS_TYPE_CONSENSUS &&
      (tok = find_opt_by_keyword(tokens, K_LEGACY_DIR_KEY))) {
    int bad = 1;
    if (strlen(tok->args[0]) == HEX_DIGEST_LEN) {
      networkstatus_voter_info_t *voter = smartlist_get(ns->voters, 0);
      if (base16_decode(voter->legacy_id_digest, DIGEST_LEN,
                        tok->args[0], HEX_DIGEST_LEN)<0)
        bad = 1;
      else
        bad = 0;
    }
    if (bad) {
      log_warn(LD_DIR, "Invalid legacy key digest %s on vote.",
               escaped(tok->args[0]));
    }
  }

  /* Parse routerstatus lines. */
  rs_tokens = smartlist_create();
  rs_area = memarea_new();
  s = end_of_header;
  ns->routerstatus_list = smartlist_create();

  while (!strcmpstart(s, "r ")) {
    if (ns->type != NS_TYPE_CONSENSUS) {
      vote_routerstatus_t *rs = tor_malloc_zero(sizeof(vote_routerstatus_t));
      if (routerstatus_parse_entry_from_string(rs_area, &s, rs_tokens, ns,
                                               rs, 0, 0))
        smartlist_add(ns->routerstatus_list, rs);
      else {
        tor_free(rs->version);
        tor_free(rs);
      }
    } else {
      routerstatus_t *rs;
      if ((rs = routerstatus_parse_entry_from_string(rs_area, &s, rs_tokens,
                                                     NULL, NULL,
                                                     ns->consensus_method,
                                                     flav)))
        smartlist_add(ns->routerstatus_list, rs);
    }
  }
  for (i = 1; i < smartlist_len(ns->routerstatus_list); ++i) {
    routerstatus_t *rs1, *rs2;
    if (ns->type != NS_TYPE_CONSENSUS) {
      vote_routerstatus_t *a = smartlist_get(ns->routerstatus_list, i-1);
      vote_routerstatus_t *b = smartlist_get(ns->routerstatus_list, i);
      rs1 = &a->status; rs2 = &b->status;
    } else {
      rs1 = smartlist_get(ns->routerstatus_list, i-1);
      rs2 = smartlist_get(ns->routerstatus_list, i);
    }
    if (fast_memcmp(rs1->identity_digest, rs2->identity_digest, DIGEST_LEN)
        >= 0) {
      log_warn(LD_DIR, "Vote networkstatus entries not sorted by identity "
               "digest");
      goto err;
    }
  }

  /* Parse footer; check signature. */
  footer_tokens = smartlist_create();
  if ((end_of_footer = strstr(s, "\nnetwork-status-version ")))
    ++end_of_footer;
  else
    end_of_footer = s + strlen(s);
  if (tokenize_string(area,s, end_of_footer, footer_tokens,
                      networkstatus_vote_footer_token_table, 0)) {
    log_warn(LD_DIR, "Error tokenizing network-status vote footer.");
    goto err;
  }

  {
    int found_sig = 0;
    SMARTLIST_FOREACH_BEGIN(footer_tokens, directory_token_t *, _tok) {
      tok = _tok;
      if (tok->tp == K_DIRECTORY_SIGNATURE)
        found_sig = 1;
      else if (found_sig) {
        log_warn(LD_DIR, "Extraneous token after first directory-signature");
        goto err;
      }
    } SMARTLIST_FOREACH_END(_tok);
  }

  if ((tok = find_opt_by_keyword(footer_tokens, K_DIRECTORY_FOOTER))) {
    if (tok != smartlist_get(footer_tokens, 0)) {
      log_warn(LD_DIR, "Misplaced directory-footer token");
      goto err;
    }
  }

  tok = find_opt_by_keyword(footer_tokens, K_BW_WEIGHTS);
  if (tok) {
    ns->weight_params = smartlist_create();
    for (i = 0; i < tok->n_args; ++i) {
      int ok=0;
      char *eq = strchr(tok->args[i], '=');
      if (!eq) {
        log_warn(LD_DIR, "Bad element '%s' in weight params",
                 escaped(tok->args[i]));
        goto err;
      }
      tor_parse_long(eq+1, 10, INT32_MIN, INT32_MAX, &ok, NULL);
      if (!ok) {
        log_warn(LD_DIR, "Bad element '%s' in params", escaped(tok->args[i]));
        goto err;
      }
      smartlist_add(ns->weight_params, tor_strdup(tok->args[i]));
    }
  }

  SMARTLIST_FOREACH_BEGIN(footer_tokens, directory_token_t *, _tok) {
    char declared_identity[DIGEST_LEN];
    networkstatus_voter_info_t *v;
    document_signature_t *sig;
    const char *id_hexdigest = NULL;
    const char *sk_hexdigest = NULL;
    digest_algorithm_t alg = DIGEST_SHA1;
    tok = _tok;
    if (tok->tp != K_DIRECTORY_SIGNATURE)
      continue;
    tor_assert(tok->n_args >= 2);
    if (tok->n_args == 2) {
      id_hexdigest = tok->args[0];
      sk_hexdigest = tok->args[1];
    } else {
      const char *algname = tok->args[0];
      int a;
      id_hexdigest = tok->args[1];
      sk_hexdigest = tok->args[2];
      a = crypto_digest_algorithm_parse_name(algname);
      if (a<0) {
        log_warn(LD_DIR, "Unknown digest algorithm %s; skipping",
                 escaped(algname));
        continue;
      }
      alg = a;
    }

    if (!tok->object_type ||
        strcmp(tok->object_type, "SIGNATURE") ||
        tok->object_size < 128 || tok->object_size > 512) {
      log_warn(LD_DIR, "Bad object type or length on directory-signature");
      goto err;
    }

    if (strlen(id_hexdigest) != HEX_DIGEST_LEN ||
        base16_decode(declared_identity, sizeof(declared_identity),
                      id_hexdigest, HEX_DIGEST_LEN) < 0) {
      log_warn(LD_DIR, "Error decoding declared identity %s in "
               "network-status vote.", escaped(id_hexdigest));
      goto err;
    }
    if (!(v = networkstatus_get_voter_by_id(ns, declared_identity))) {
      log_warn(LD_DIR, "ID on signature on network-status vote does not match "
               "any declared directory source.");
      goto err;
    }
    sig = tor_malloc_zero(sizeof(document_signature_t));
    memcpy(sig->identity_digest, v->identity_digest, DIGEST_LEN);
    sig->alg = alg;
    if (strlen(sk_hexdigest) != HEX_DIGEST_LEN ||
        base16_decode(sig->signing_key_digest, sizeof(sig->signing_key_digest),
                      sk_hexdigest, HEX_DIGEST_LEN) < 0) {
      log_warn(LD_DIR, "Error decoding declared signing key digest %s in "
               "network-status vote.", escaped(sk_hexdigest));
      tor_free(sig);
      goto err;
    }

    if (ns->type != NS_TYPE_CONSENSUS) {
      if (tor_memneq(declared_identity, ns->cert->cache_info.identity_digest,
                 DIGEST_LEN)) {
        log_warn(LD_DIR, "Digest mismatch between declared and actual on "
                 "network-status vote.");
        tor_free(sig);
        goto err;
      }
    }

    if (voter_get_sig_by_algorithm(v, sig->alg)) {
      /* We already parsed a vote with this algorithm from this voter. Use the
         first one. */
      log_fn(LOG_PROTOCOL_WARN, LD_DIR, "We received a networkstatus "
             "that contains two votes from the same voter with the same "
             "algorithm. Ignoring the second vote.");
      tor_free(sig);
      continue;
    }

    if (ns->type != NS_TYPE_CONSENSUS) {
      if (check_signature_token(ns_digests.d[DIGEST_SHA1], DIGEST_LEN,
                                tok, ns->cert->signing_key, 0,
                                "network-status vote")) {
        tor_free(sig);
        goto err;
      }
      sig->good_signature = 1;
    } else {
      if (tok->object_size >= INT_MAX || tok->object_size >= SIZE_T_CEILING) {
        tor_free(sig);
        goto err;
      }
      sig->signature = tor_memdup(tok->object_body, tok->object_size);
      sig->signature_len = (int) tok->object_size;
    }
    smartlist_add(v->sigs, sig);

    ++n_signatures;
  } SMARTLIST_FOREACH_END(_tok);

  if (! n_signatures) {
    log_warn(LD_DIR, "No signatures on networkstatus vote.");
    goto err;
  } else if (ns->type == NS_TYPE_VOTE && n_signatures != 1) {
    log_warn(LD_DIR, "Received more than one signature on a "
             "network-status vote.");
    goto err;
  }

  if (eos_out)
    *eos_out = end_of_footer;

  goto done;
 err:
  dump_desc(s_dup, "v3 networkstatus");
  networkstatus_vote_free(ns);
  ns = NULL;
 done:
  if (tokens) {
    SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
    smartlist_free(tokens);
  }
  if (voter) {
    if (voter->sigs) {
      SMARTLIST_FOREACH(voter->sigs, document_signature_t *, sig,
                        document_signature_free(sig));
      smartlist_free(voter->sigs);
    }
    tor_free(voter->nickname);
    tor_free(voter->address);
    tor_free(voter->contact);
    tor_free(voter);
  }
  if (rs_tokens) {
    SMARTLIST_FOREACH(rs_tokens, directory_token_t *, t, token_clear(t));
    smartlist_free(rs_tokens);
  }
  if (footer_tokens) {
    SMARTLIST_FOREACH(footer_tokens, directory_token_t *, t, token_clear(t));
    smartlist_free(footer_tokens);
  }
  if (area) {
    DUMP_AREA(area, "v3 networkstatus");
    memarea_drop_all(area);
  }
  if (rs_area)
    memarea_drop_all(rs_area);

  return ns;
}

Use-after-free vulnerability in dns.c in Tor before 0.2.2.38 might allow remote attackers to cause a denial of service (daemon crash) via vectors related to failed DNS requests.


kdc: validate sname in TGS-REQ

In tgs_build_reply(), validate the server name in the TGS-REQ is present before
dereferencing.

tgs_build_reply(astgs_request_t priv,
		hdb_entry_ex *krbtgt,
		krb5_enctype krbtgt_etype,
		const krb5_keyblock *replykey,
		int rk_is_subkey,
		krb5_ticket *ticket,
		const char **e_text,
		AuthorizationData **auth_data,
		const struct sockaddr *from_addr)
{
    krb5_context context = priv->context;
    krb5_kdc_configuration *config = priv->config;
    KDC_REQ *req = &priv->req;
    KDC_REQ_BODY *b = &priv->req.req_body;
    const char *from = priv->from;
    krb5_error_code ret, ret2;
    krb5_principal cp = NULL, sp = NULL, rsp = NULL, tp = NULL, dp = NULL;
    krb5_principal krbtgt_out_principal = NULL;
    char *spn = NULL, *cpn = NULL, *tpn = NULL, *dpn = NULL, *krbtgt_out_n = NULL;
    hdb_entry_ex *server = NULL, *client = NULL, *s4u2self_impersonated_client = NULL;
    HDB *clientdb, *s4u2self_impersonated_clientdb;
    krb5_realm ref_realm = NULL;
    EncTicketPart *tgt = &ticket->ticket;
    krb5_principals spp = NULL;
    const EncryptionKey *ekey;
    krb5_keyblock sessionkey;
    krb5_kvno kvno;
    krb5_data rspac;
    const char *tgt_realm = /* Realm of TGT issuer */
        krb5_principal_get_realm(context, krbtgt->entry.principal);
    const char *our_realm = /* Realm of this KDC */
        krb5_principal_get_comp_string(context, krbtgt->entry.principal, 1);
    char **capath = NULL;
    size_t num_capath = 0;

    hdb_entry_ex *krbtgt_out = NULL;

    METHOD_DATA enc_pa_data;

    PrincipalName *s;
    Realm r;
    EncTicketPart adtkt;
    char opt_str[128];
    int signedpath = 0;

    Key *tkey_check;
    Key *tkey_sign;
    int flags = HDB_F_FOR_TGS_REQ;

    memset(&sessionkey, 0, sizeof(sessionkey));
    memset(&adtkt, 0, sizeof(adtkt));
    krb5_data_zero(&rspac);
    memset(&enc_pa_data, 0, sizeof(enc_pa_data));

    s = b->sname;
    r = b->realm;

    /*
     * The canonicalize KDC option is passed as a hint to the backend, but
     * can typically be ignored. Per RFC 6806, names are not canonicalized
     * in response to a TGS request (although we make an exception, see
     * force-canonicalize below).
     */
    if (b->kdc_options.canonicalize)
	flags |= HDB_F_CANON;

    if(b->kdc_options.enc_tkt_in_skey){
	Ticket *t;
	hdb_entry_ex *uu;
	krb5_principal p;
	Key *uukey;
	krb5uint32 second_kvno = 0;
	krb5uint32 *kvno_ptr = NULL;

	if(b->additional_tickets == NULL ||
	   b->additional_tickets->len == 0){
	    ret = KRB5KDC_ERR_BADOPTION; /* ? */
	    kdc_log(context, config, 4,
		    "No second ticket present in user-to-user request");
            _kdc_audit_addreason((kdc_request_t)priv,
                                 "No second ticket present in user-to-user request");
	    goto out;
	}
	t = &b->additional_tickets->val[0];
	if(!get_krbtgt_realm(&t->sname)){
	    kdc_log(context, config, 4,
		    "Additional ticket is not a ticket-granting ticket");
            _kdc_audit_addreason((kdc_request_t)priv,
                                 "Additional ticket is not a ticket-granting ticket");
	    ret = KRB5KDC_ERR_POLICY;
	    goto out;
	}
	_krb5_principalname2krb5_principal(context, &p, t->sname, t->realm);
	ret = krb5_unparse_name(context, p, &tpn);
	if (ret)
		goto out;
	if(t->enc_part.kvno){
	    second_kvno = *t->enc_part.kvno;
	    kvno_ptr = &second_kvno;
	}
	ret = _kdc_db_fetch(context, config, p,
			    HDB_F_GET_KRBTGT, kvno_ptr,
			    NULL, &uu);
	krb5_free_principal(context, p);
	if(ret){
	    if (ret == HDB_ERR_NOENTRY)
		ret = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN;
            _kdc_audit_addreason((kdc_request_t)priv,
                                 "User-to-user service principal (TGS) unknown");
	    goto out;
	}
	ret = hdb_enctype2key(context, &uu->entry, NULL,
			      t->enc_part.etype, &uukey);
	if(ret){
	    _kdc_free_ent(context, uu);
	    ret = KRB5KDC_ERR_ETYPE_NOSUPP; /* XXX */
            _kdc_audit_addreason((kdc_request_t)priv,
                                 "User-to-user enctype not supported");
	    goto out;
	}
	ret = krb5_decrypt_ticket(context, t, &uukey->key, &adtkt, 0);
	_kdc_free_ent(context, uu);
	if(ret) {
            _kdc_audit_addreason((kdc_request_t)priv,
                                 "User-to-user TGT decrypt failure");
	    goto out;
        }

	ret = verify_flags(context, config, &adtkt, tpn);
	if (ret) {
            _kdc_audit_addreason((kdc_request_t)priv,
                                 "User-to-user TGT expired or invalid");
	    goto out;
        }

	s = &adtkt.cname;
	r = adtkt.crealm;
    }

    _krb5_principalname2krb5_principal(context, &sp, *s, r);
    ret = krb5_unparse_name(context, sp, &priv->sname);
    if (ret)
	goto out;
    spn = priv->sname;
    _krb5_principalname2krb5_principal(context, &cp, tgt->cname, tgt->crealm);
    ret = krb5_unparse_name(context, cp, &priv->cname);
    if (ret)
	goto out;
    cpn = priv->cname;
    unparse_flags (KDCOptions2int(b->kdc_options),
		   asn1_KDCOptions_units(),
		   opt_str, sizeof(opt_str));
    if(*opt_str)
	kdc_log(context, config, 4,
		"TGS-REQ %s from %s for %s [%s]",
		cpn, from, spn, opt_str);
    else
	kdc_log(context, config, 4,
		"TGS-REQ %s from %s for %s", cpn, from, spn);

    /*
     * Fetch server
     */

server_lookup:
    ret = _kdc_db_fetch(context, config, sp,
                        HDB_F_GET_SERVER | HDB_F_DELAY_NEW_KEYS | flags,
			NULL, NULL, &server);
    priv->server = server;
    if (ret == HDB_ERR_NOT_FOUND_HERE) {
	kdc_log(context, config, 5, "target %s does not have secrets at this KDC, need to proxy", spn);
        _kdc_audit_addreason((kdc_request_t)priv, "Target not found here");
	goto out;
    } else if (ret == HDB_ERR_WRONG_REALM) {
        free(ref_realm);
	ref_realm = strdup(server->entry.principal->realm);
	if (ref_realm == NULL) {
            ret = krb5_enomem(context);
	    goto out;
	}

	kdc_log(context, config, 4,
		"Returning a referral to realm %s for "
		"server %s.",
		ref_realm, spn);
	krb5_free_principal(context, sp);
	sp = NULL;
	ret = krb5_make_principal(context, &sp, r, KRB5_TGS_NAME,
				  ref_realm, NULL);
	if (ret)
	    goto out;
	free(priv->sname);
        priv->sname = NULL;
	ret = krb5_unparse_name(context, sp, &priv->sname);
	if (ret)
	    goto out;
	spn = priv->sname;

	goto server_lookup;
    } else if (ret) {
	const char *new_rlm, *msg;
	Realm req_rlm;
	krb5_realm *realms;

	if ((req_rlm = get_krbtgt_realm(&sp->name)) != NULL) {
            if (capath == NULL) {
                /* With referalls, hierarchical capaths are always enabled */
                ret2 = _krb5_find_capath(context, tgt->crealm, our_realm,
                                         req_rlm, TRUE, &capath, &num_capath);
                if (ret2) {
                    ret = ret2;
                    _kdc_audit_addreason((kdc_request_t)priv,
                                         "No trusted path from client realm to ours");
                    goto out;
                }
            }
            new_rlm = num_capath > 0 ? capath[--num_capath] : NULL;
            if (new_rlm) {
                kdc_log(context, config, 5, "krbtgt from %s via %s for "
                        "realm %s not found, trying %s", tgt->crealm,
                        our_realm, req_rlm, new_rlm);

                free(ref_realm);
                ref_realm = strdup(new_rlm);
                if (ref_realm == NULL) {
                    ret = krb5_enomem(context);
                    goto out;
                }

                krb5_free_principal(context, sp);
                sp = NULL;
                krb5_make_principal(context, &sp, r,
                                    KRB5_TGS_NAME, ref_realm, NULL);
                free(priv->sname);
                priv->sname = NULL;
                ret = krb5_unparse_name(context, sp, &priv->sname);
                if (ret)
                    goto out;
                spn = priv->sname;
                goto server_lookup;
            }
	} else if (need_referral(context, config, &b->kdc_options, sp, &realms)) {
	    if (strcmp(realms[0], sp->realm) != 0) {
		kdc_log(context, config, 4,
			"Returning a referral to realm %s for "
			"server %s that was not found",
			realms[0], spn);
		krb5_free_principal(context, sp);
                sp = NULL;
		krb5_make_principal(context, &sp, r, KRB5_TGS_NAME,
				    realms[0], NULL);
		free(priv->sname);
                priv->sname = NULL;
		ret = krb5_unparse_name(context, sp, &priv->sname);
		if (ret) {
		    krb5_free_host_realm(context, realms);
		    goto out;
		}
		spn = priv->sname;

                free(ref_realm);
		ref_realm = strdup(realms[0]);

		krb5_free_host_realm(context, realms);
		goto server_lookup;
	    }
	    krb5_free_host_realm(context, realms);
	}
	msg = krb5_get_error_message(context, ret);
	kdc_log(context, config, 3,
		"Server not found in database: %s: %s", spn, msg);
	krb5_free_error_message(context, msg);
	if (ret == HDB_ERR_NOENTRY)
	    ret = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN;
        _kdc_audit_addreason((kdc_request_t)priv,
                             "Service principal unknown");
	goto out;
    }

    /*
     * RFC 6806 notes that names MUST NOT be changed in the response to
     * a TGS request. Hence we ignore the setting of the canonicalize
     * KDC option. However, for legacy interoperability we do allow the
     * backend to override this by setting the force-canonicalize HDB
     * flag in the server entry.
     */
    if (server->entry.flags.force_canonicalize)
	rsp = server->entry.principal;
    else
	rsp = sp;

    /*
     * Select enctype, return key and kvno.
     */

    {
	krb5_enctype etype;

	if(b->kdc_options.enc_tkt_in_skey) {
	    size_t i;
	    ekey = &adtkt.key;
	    for(i = 0; i < b->etype.len; i++)
		if (b->etype.val[i] == adtkt.key.keytype)
		    break;
	    if(i == b->etype.len) {
		kdc_log(context, config, 4,
			"Addition ticket have not matching etypes");
		krb5_clear_error_message(context);
		ret = KRB5KDC_ERR_ETYPE_NOSUPP;
                _kdc_audit_addreason((kdc_request_t)priv,
                                     "No matching enctypes for 2nd ticket");
		goto out;
	    }
	    etype = b->etype.val[i];
	    kvno = 0;
	} else {
	    Key *skey;

	    ret = _kdc_find_etype(priv, krb5_principal_is_krbtgt(context, sp)
							     ? KFE_IS_TGS : 0,
				  b->etype.val, b->etype.len, &etype, NULL,
				  NULL);
	    if(ret) {
		kdc_log(context, config, 4,
			"Server (%s) has no support for etypes", spn);
                _kdc_audit_addreason((kdc_request_t)priv,
                                     "Enctype not supported");
		goto out;
	    }
	    ret = _kdc_get_preferred_key(context, config, server, spn,
					 NULL, &skey);
	    if(ret) {
		kdc_log(context, config, 4,
			"Server (%s) has no supported etypes", spn);
                _kdc_audit_addreason((kdc_request_t)priv,
                                     "Enctype not supported");
		goto out;
	    }
	    ekey = &skey->key;
	    kvno = server->entry.kvno;
	}

	ret = krb5_generate_random_keyblock(context, etype, &sessionkey);
	if (ret)
	    goto out;
    }

    /*
     * Check that service is in the same realm as the krbtgt. If it's
     * not the same, it's someone that is using a uni-directional trust
     * backward.
     */

    /*
     * Validate authorization data
     */

    ret = hdb_enctype2key(context, &krbtgt->entry, NULL, /* XXX use the right kvno! */
			  krbtgt_etype, &tkey_check);
    if(ret) {
	kdc_log(context, config, 4,
		    "Failed to find key for krbtgt PAC check");
        _kdc_audit_addreason((kdc_request_t)priv,
                             "No key for krbtgt PAC check");
	goto out;
    }

    /*
     * Now refetch the primary krbtgt, and get the current kvno (the
     * sign check may have been on an old kvno, and the server may
     * have been an incoming trust)
     */

    ret = krb5_make_principal(context,
                              &krbtgt_out_principal,
                              our_realm,
                              KRB5_TGS_NAME,
                              our_realm,
                              NULL);
    if (ret) {
        kdc_log(context, config, 4,
                "Failed to make krbtgt principal name object for "
                "authz-data signatures");
        goto out;
    }
    ret = krb5_unparse_name(context, krbtgt_out_principal, &krbtgt_out_n);
    if (ret) {
        kdc_log(context, config, 4,
                "Failed to make krbtgt principal name object for "
                "authz-data signatures");
        goto out;
    }

    ret = _kdc_db_fetch(context, config, krbtgt_out_principal,
			HDB_F_GET_KRBTGT, NULL, NULL, &krbtgt_out);
    if (ret) {
	char *ktpn = NULL;
	ret = krb5_unparse_name(context, krbtgt->entry.principal, &ktpn);
	kdc_log(context, config, 4,
		"No such principal %s (needed for authz-data signature keys) "
		"while processing TGS-REQ for service %s with krbtg %s",
		krbtgt_out_n, spn, (ret == 0) ? ktpn : "<unknown>");
	free(ktpn);
	ret = KRB5KRB_AP_ERR_NOT_US;
	goto out;
    }

    /*
     * The first realm is the realm of the service, the second is
     * krbtgt/<this>/@REALM component of the krbtgt DN the request was
     * encrypted to.  The redirection via the krbtgt_out entry allows
     * the DB to possibly correct the case of the realm (Samba4 does
     * this) before the strcmp()
     */
    if (strcmp(krb5_principal_get_realm(context, server->entry.principal),
	       krb5_principal_get_realm(context, krbtgt_out->entry.principal)) != 0) {
	char *ktpn;
	ret = krb5_unparse_name(context, krbtgt_out->entry.principal, &ktpn);
	kdc_log(context, config, 4,
		"Request with wrong krbtgt: %s",
		(ret == 0) ? ktpn : "<unknown>");
	if(ret == 0)
	    free(ktpn);
	ret = KRB5KRB_AP_ERR_NOT_US;
        _kdc_audit_addreason((kdc_request_t)priv, "Request with wrong TGT");
	goto out;
    }

    ret = _kdc_get_preferred_key(context, config, krbtgt_out, krbtgt_out_n,
				 NULL, &tkey_sign);
    if (ret) {
	kdc_log(context, config, 4,
		    "Failed to find key for krbtgt PAC signature");
        _kdc_audit_addreason((kdc_request_t)priv,
                             "Failed to find key for krbtgt PAC signature");
	goto out;
    }
    ret = hdb_enctype2key(context, &krbtgt_out->entry, NULL,
			  tkey_sign->key.keytype, &tkey_sign);
    if(ret) {
	kdc_log(context, config, 4,
		    "Failed to find key for krbtgt PAC signature");
        _kdc_audit_addreason((kdc_request_t)priv,
                             "Failed to find key for krbtgt PAC signature");
	goto out;
    }

    {
        krb5_data verified_cas;

        /*
         * If the client doesn't exist in the HDB but has a TGT and it's
         * obtained with PKINIT then we assume it's a synthetic client -- that
         * is, a client whose name was vouched for by a CA using a PKINIT SAN,
         * but which doesn't exist in the HDB proper.  We'll allow such a
         * client to do TGT requests even though normally we'd reject all
         * clients that don't exist in the HDB.
         */
        ret = krb5_ticket_get_authorization_data_type(context, ticket,
                                                      KRB5_AUTHDATA_INITIAL_VERIFIED_CAS,
                                                      &verified_cas);
        if (ret == 0) {
            krb5_data_free(&verified_cas);
            flags |= HDB_F_SYNTHETIC_OK;
        }
    }
    ret = _kdc_db_fetch(context, config, cp, HDB_F_GET_CLIENT | flags,
			NULL, &clientdb, &client);
    flags &= ~HDB_F_SYNTHETIC_OK;
    priv->client = client;
    if(ret == HDB_ERR_NOT_FOUND_HERE) {
	/* This is OK, we are just trying to find out if they have
	 * been disabled or deleted in the meantime, missing secrets
	 * is OK */
    } else if(ret){
	const char *krbtgt_realm, *msg;

	/*
	 * If the client belongs to the same realm as our krbtgt, it
	 * should exist in the local database.
	 *
	 */

	krbtgt_realm = krb5_principal_get_realm(context, krbtgt_out->entry.principal);

	if(strcmp(krb5_principal_get_realm(context, cp), krbtgt_realm) == 0) {
	    if (ret == HDB_ERR_NOENTRY)
		ret = KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN;
	    kdc_log(context, config, 4, "Client no longer in database: %s",
		    cpn);
            _kdc_audit_addreason((kdc_request_t)priv, "Client no longer in HDB");
	    goto out;
	}

	msg = krb5_get_error_message(context, ret);
	kdc_log(context, config, 4, "Client not found in database: %s", msg);
        _kdc_audit_addreason((kdc_request_t)priv, "Client does not exist");
	krb5_free_error_message(context, msg);
    } else if (ret == 0 &&
               (client->entry.flags.invalid || !client->entry.flags.client)) {
        _kdc_audit_addreason((kdc_request_t)priv, "Client has invalid bit set");
        kdc_log(context, config, 4, "Client has invalid bit set");
        ret = KRB5KDC_ERR_POLICY;
        goto out;
    }

    ret = check_PAC(context, config, cp, NULL,
		    client, server, krbtgt,
		    &tkey_check->key,
		    ekey, &tkey_sign->key,
		    tgt, &rspac, &signedpath);
    if (ret) {
	const char *msg = krb5_get_error_message(context, ret);
        _kdc_audit_addreason((kdc_request_t)priv, "PAC check failed");
	kdc_log(context, config, 4,
		"Verify PAC failed for %s (%s) from %s with %s",
		spn, cpn, from, msg);
	krb5_free_error_message(context, msg);
	goto out;
    }

    /* also check the krbtgt for signature */
    ret = check_KRB5SignedPath(context,
			       config,
			       krbtgt,
			       cp,
			       tgt,
			       &spp,
			       &signedpath);
    if (ret) {
	const char *msg = krb5_get_error_message(context, ret);
        _kdc_audit_addreason((kdc_request_t)priv, "KRB5SignedPath check failed");
	kdc_log(context, config, 4,
		"KRB5SignedPath check failed for %s (%s) from %s with %s",
		spn, cpn, from, msg);
	krb5_free_error_message(context, msg);
	goto out;
    }

    /*
     * Process request
     */

    /* by default the tgt principal matches the client principal */
    tp = cp;
    tpn = cpn;

    if (client) {
	const PA_DATA *sdata;
	int i = 0;

	sdata = _kdc_find_padata(req, &i, KRB5_PADATA_FOR_USER);
	if (sdata) {
	    struct astgs_request_desc imp_req;
	    krb5_crypto crypto;
	    krb5_data datack;
	    PA_S4U2Self self;
	    const char *str;

	    ret = decode_PA_S4U2Self(sdata->padata_value.data,
				     sdata->padata_value.length,
				     &self, NULL);
	    if (ret) {
                _kdc_audit_addreason((kdc_request_t)priv,
                                     "Failed to decode PA-S4U2Self");
		kdc_log(context, config, 4, "Failed to decode PA-S4U2Self");
		goto out;
	    }

	    if (!krb5_checksum_is_keyed(context, self.cksum.cksumtype)) {
		free_PA_S4U2Self(&self);
                _kdc_audit_addreason((kdc_request_t)priv,
                                     "PA-S4U2Self with unkeyed checksum");
		kdc_log(context, config, 4, "Reject PA-S4U2Self with unkeyed checksum");
		ret = KRB5KRB_AP_ERR_INAPP_CKSUM;
		goto out;
	    }

	    ret = _krb5_s4u2self_to_checksumdata(context, &self, &datack);
	    if (ret)
		goto out;

	    ret = krb5_crypto_init(context, &tgt->key, 0, &crypto);
	    if (ret) {
		const char *msg = krb5_get_error_message(context, ret);
		free_PA_S4U2Self(&self);
		krb5_data_free(&datack);
		kdc_log(context, config, 4, "krb5_crypto_init failed: %s", msg);
		krb5_free_error_message(context, msg);
		goto out;
	    }

	    /* Allow HMAC_MD5 checksum with any key type */
	    if (self.cksum.cksumtype == CKSUMTYPE_HMAC_MD5) {
		struct krb5_crypto_iov iov;
		unsigned char csdata[16];
		Checksum cs;

		cs.checksum.length = sizeof(csdata);
		cs.checksum.data = &csdata;

		iov.data.data = datack.data;
		iov.data.length = datack.length;
		iov.flags = KRB5_CRYPTO_TYPE_DATA;

		ret = _krb5_HMAC_MD5_checksum(context, NULL, &crypto->key,
					      KRB5_KU_OTHER_CKSUM, &iov, 1,
					      &cs);
		if (ret == 0 &&
		    krb5_data_ct_cmp(&cs.checksum, &self.cksum.checksum) != 0)
		    ret = KRB5KRB_AP_ERR_BAD_INTEGRITY;
	    }
	    else {
		ret = krb5_verify_checksum(context,
					   crypto,
					   KRB5_KU_OTHER_CKSUM,
					   datack.data,
					   datack.length,
					   &self.cksum);
	    }
	    krb5_data_free(&datack);
	    krb5_crypto_destroy(context, crypto);
	    if (ret) {
		const char *msg = krb5_get_error_message(context, ret);
		free_PA_S4U2Self(&self);
                _kdc_audit_addreason((kdc_request_t)priv,
                                     "S4U2Self checksum failed");
		kdc_log(context, config, 4,
			"krb5_verify_checksum failed for S4U2Self: %s", msg);
		krb5_free_error_message(context, msg);
		goto out;
	    }

	    ret = _krb5_principalname2krb5_principal(context,
						     &tp,
						     self.name,
						     self.realm);
	    free_PA_S4U2Self(&self);
	    if (ret)
		goto out;

	    ret = krb5_unparse_name(context, tp, &tpn);
	    if (ret)
		goto out;

            /*
             * Note no HDB_F_SYNTHETIC_OK -- impersonating non-existent clients
             * is probably not desirable!
             */
	    ret = _kdc_db_fetch(context, config, tp, HDB_F_GET_CLIENT | flags,
				NULL, &s4u2self_impersonated_clientdb,
				&s4u2self_impersonated_client);
	    if (ret) {
		const char *msg;

		/*
		 * If the client belongs to the same realm as our krbtgt, it
		 * should exist in the local database.
		 *
		 */

		if (ret == HDB_ERR_NOENTRY)
		    ret = KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN;
		msg = krb5_get_error_message(context, ret);
                _kdc_audit_addreason((kdc_request_t)priv,
                                     "S4U2Self principal to impersonate not found");
		kdc_log(context, config, 2,
			"S4U2Self principal to impersonate %s not found in database: %s",
			tpn, msg);
		krb5_free_error_message(context, msg);
		goto out;
	    }

	    /* Ignore require_pwchange and pw_end attributes (as Windows does),
	     * since S4U2Self is not password authentication. */
	    s4u2self_impersonated_client->entry.flags.require_pwchange = FALSE;
	    free(s4u2self_impersonated_client->entry.pw_end);
	    s4u2self_impersonated_client->entry.pw_end = NULL;

	    imp_req = *priv;
	    imp_req.client = s4u2self_impersonated_client;
	    imp_req.client_princ = tp;

	    ret = kdc_check_flags(&imp_req, FALSE);
	    if (ret)
		goto out; /* kdc_check_flags() calls _kdc_audit_addreason() */

	    /* If we were about to put a PAC into the ticket, we better fix it to be the right PAC */
	    if(rspac.data) {
		krb5_pac p = NULL;
		krb5_data_free(&rspac);
		ret = _kdc_pac_generate(context, s4u2self_impersonated_client, &p);
		if (ret) {
                    _kdc_audit_addreason((kdc_request_t)priv,
                                         "KRB5SignedPath missing");
		    kdc_log(context, config, 4, "PAC generation failed for -- %s",
			    tpn);
		    goto out;
		}
		if (p != NULL) {
		    ret = _krb5_pac_sign(context, p, ticket->ticket.authtime,
					 s4u2self_impersonated_client->entry.principal,
					 ekey, &tkey_sign->key,
					 &rspac);
		    krb5_pac_free(context, p);
		    if (ret) {
			kdc_log(context, config, 4, "PAC signing failed for -- %s",
				tpn);
			goto out;
		    }
		}
	    }

	    /*
	     * Check that service doing the impersonating is
	     * requesting a ticket to it-self.
	     */
	    ret = check_s4u2self(context, config, clientdb, client, sp);
	    if (ret) {
		kdc_log(context, config, 4, "S4U2Self: %s is not allowed "
			"to impersonate to service "
			"(tried for user %s to service %s)",
			cpn, tpn, spn);
		goto out;
	    }

	    /*
	     * If the service isn't trusted for authentication to
	     * delegation or if the impersonate client is disallowed
	     * forwardable, remove the forwardable flag.
	     */

	    if (client->entry.flags.trusted_for_delegation &&
		s4u2self_impersonated_client->entry.flags.forwardable) {
		str = "[forwardable]";
	    } else {
		b->kdc_options.forwardable = 0;
		str = "";
	    }
	    kdc_log(context, config, 4, "s4u2self %s impersonating %s to "
		    "service %s %s", cpn, tpn, spn, str);
	}
    }

    /*
     * Constrained delegation
     */

    if (client != NULL
	&& b->additional_tickets != NULL
	&& b->additional_tickets->len != 0
	&& b->kdc_options.cname_in_addl_tkt
	&& b->kdc_options.enc_tkt_in_skey == 0)
    {
	int ad_signedpath = 0;
	Key *clientkey;
	Ticket *t;

	/*
	 * Require that the KDC have issued the service's krbtgt (not
	 * self-issued ticket with kimpersonate(1).
	 */
	if (!signedpath) {
	    ret = KRB5KDC_ERR_BADOPTION;
            _kdc_audit_addreason((kdc_request_t)priv, "KRB5SignedPath missing");
	    kdc_log(context, config, 4,
		    "Constrained delegation done on service ticket %s/%s",
		    cpn, spn);
	    goto out;
	}

	t = &b->additional_tickets->val[0];

	ret = hdb_enctype2key(context, &client->entry,
			      hdb_kvno2keys(context, &client->entry,
					    t->enc_part.kvno ? * t->enc_part.kvno : 0),
			      t->enc_part.etype, &clientkey);
	if(ret){
	    ret = KRB5KDC_ERR_ETYPE_NOSUPP; /* XXX */
	    goto out;
	}

	ret = krb5_decrypt_ticket(context, t, &clientkey->key, &adtkt, 0);
	if (ret) {
            _kdc_audit_addreason((kdc_request_t)priv,
                                 "Failed to decrypt constrained delegation ticket");
	    kdc_log(context, config, 4,
		    "failed to decrypt ticket for "
		    "constrained delegation from %s to %s ", cpn, spn);
	    goto out;
	}

	ret = _krb5_principalname2krb5_principal(context,
						 &tp,
						 adtkt.cname,
						 adtkt.crealm);
	if (ret)
	    goto out;

	ret = krb5_unparse_name(context, tp, &tpn);
	if (ret)
	    goto out;

        _kdc_audit_addkv((kdc_request_t)priv, 0, "impersonatee", "%s", tpn);

	ret = _krb5_principalname2krb5_principal(context,
						 &dp,
						 t->sname,
						 t->realm);
	if (ret)
	    goto out;

	ret = krb5_unparse_name(context, dp, &dpn);
	if (ret)
	    goto out;

	/* check that ticket is valid */
	if (adtkt.flags.forwardable == 0) {
            _kdc_audit_addreason((kdc_request_t)priv,
                                 "Missing forwardable flag on ticket for constrained delegation");
	    kdc_log(context, config, 4,
		    "Missing forwardable flag on ticket for "
		    "constrained delegation from %s (%s) as %s to %s ",
		    cpn, dpn, tpn, spn);
	    ret = KRB5KDC_ERR_BADOPTION;
	    goto out;
	}

	ret = check_constrained_delegation(context, config, clientdb,
					   client, server, sp);
	if (ret) {
            _kdc_audit_addreason((kdc_request_t)priv,
                                 "Constrained delegation not allowed");
	    kdc_log(context, config, 4,
		    "constrained delegation from %s (%s) as %s to %s not allowed",
		    cpn, dpn, tpn, spn);
	    goto out;
	}

	ret = verify_flags(context, config, &adtkt, tpn);
	if (ret) {
            _kdc_audit_addreason((kdc_request_t)priv,
                                 "Constrained delegation ticket expired or invalid");
	    goto out;
	}

	krb5_data_free(&rspac);

	/*
	 * generate the PAC for the user.
	 *
	 * TODO: pass in t->sname and t->realm and build
	 * a S4U_DELEGATION_INFO blob to the PAC.
	 */
	ret = check_PAC(context, config, tp, dp,
			client, server, krbtgt,
			&clientkey->key,
			ekey, &tkey_sign->key,
			&adtkt, &rspac, &ad_signedpath);
	if (ret) {
	    const char *msg = krb5_get_error_message(context, ret);
            _kdc_audit_addreason((kdc_request_t)priv,
                                 "Constrained delegation ticket PAC check failed");
	    kdc_log(context, config, 4,
		    "Verify delegated PAC failed to %s for client"
		    "%s (%s) as %s from %s with %s",
		    spn, cpn, dpn, tpn, from, msg);
	    krb5_free_error_message(context, msg);
	    goto out;
	}

	/*
	 * Check that the KDC issued the user's ticket.
	 */
	ret = check_KRB5SignedPath(context,
				   config,
				   krbtgt,
				   cp,
				   &adtkt,
				   NULL,
				   &ad_signedpath);
	if (ret) {
	    const char *msg = krb5_get_error_message(context, ret);
	    kdc_log(context, config, 4,
		    "KRB5SignedPath check from service %s failed "
		    "for delegation to %s for client %s (%s)"
		    "from %s failed with %s",
		    spn, tpn, dpn, cpn, from, msg);
	    krb5_free_error_message(context, msg);
            _kdc_audit_addreason((kdc_request_t)priv,
                                 "KRB5SignedPath check failed");
	    goto out;
	}

	if (!ad_signedpath) {
	    ret = KRB5KDC_ERR_BADOPTION;
	    kdc_log(context, config, 4,
		    "Ticket not signed with PAC nor SignedPath service %s failed "
		    "for delegation to %s for client %s (%s)"
		    "from %s",
		    spn, tpn, dpn, cpn, from);
            _kdc_audit_addreason((kdc_request_t)priv,
                                 "Constrained delegation ticket not signed");
	    goto out;
	}

	kdc_log(context, config, 4, "constrained delegation for %s "
		"from %s (%s) to %s", tpn, cpn, dpn, spn);
    }

    /*
     * Check flags
     */

    ret = kdc_check_flags(priv, FALSE);
    if(ret)
	goto out;

    if((b->kdc_options.validate || b->kdc_options.renew) &&
       !krb5_principal_compare(context,
			       krbtgt->entry.principal,
			       server->entry.principal)){
        _kdc_audit_addreason((kdc_request_t)priv, "Inconsistent request");
	kdc_log(context, config, 4, "Inconsistent request.");
	ret = KRB5KDC_ERR_SERVER_NOMATCH;
	goto out;
    }

    /* check for valid set of addresses */
    if (!_kdc_check_addresses(priv, tgt->caddr, from_addr)) {
        if (config->check_ticket_addresses) {
            ret = KRB5KRB_AP_ERR_BADADDR;
            _kdc_audit_addkv((kdc_request_t)priv, 0, "wrongaddr", "yes");
            kdc_log(context, config, 4, "Request from wrong address");
            _kdc_audit_addreason((kdc_request_t)priv, "Request from wrong address");
            goto out;
        } else if (config->warn_ticket_addresses) {
            _kdc_audit_addkv((kdc_request_t)priv, 0, "wrongaddr", "yes");
        }
    }

    /* check local and per-principal anonymous ticket issuance policy */
    if (is_anon_tgs_request_p(b, tgt)) {
	ret = _kdc_check_anon_policy(priv);
	if (ret)
	    goto out;
    }

    /*
     * If this is an referral, add server referral data to the
     * auth_data reply .
     */
    if (ref_realm) {
	PA_DATA pa;
	krb5_crypto crypto;

	kdc_log(context, config, 3,
		"Adding server referral to %s", ref_realm);

	ret = krb5_crypto_init(context, &sessionkey, 0, &crypto);
	if (ret)
	    goto out;

	ret = build_server_referral(context, config, crypto, ref_realm,
				    NULL, s, &pa.padata_value);
	krb5_crypto_destroy(context, crypto);
	if (ret) {
            _kdc_audit_addreason((kdc_request_t)priv, "Referral build failed");
	    kdc_log(context, config, 4,
		    "Failed building server referral");
	    goto out;
	}
	pa.padata_type = KRB5_PADATA_SERVER_REFERRAL;

	ret = add_METHOD_DATA(&enc_pa_data, &pa);
	krb5_data_free(&pa.padata_value);
	if (ret) {
	    kdc_log(context, config, 4,
		    "Add server referral METHOD-DATA failed");
	    goto out;
	}
    }

    /*
     *
     */

    ret = tgs_make_reply(priv,
			 tp,
			 tgt,
			 replykey,
			 rk_is_subkey,
			 ekey,
			 &sessionkey,
			 kvno,
			 *auth_data,
			 server,
			 rsp,
			 client,
			 cp,
                         tgt_realm,
			 krbtgt_out,
			 tkey_sign->key.keytype,
			 spp,
			 &rspac,
			 &enc_pa_data);

out:
    if (tpn != cpn)
	    free(tpn);
    free(dpn);
    free(krbtgt_out_n);
    _krb5_free_capath(context, capath);

    krb5_data_free(&rspac);
    krb5_free_keyblock_contents(context, &sessionkey);
    if(krbtgt_out)
	_kdc_free_ent(context, krbtgt_out);
    if(server)
	_kdc_free_ent(context, server);
    if(client)
	_kdc_free_ent(context, client);
    if(s4u2self_impersonated_client)
	_kdc_free_ent(context, s4u2self_impersonated_client);

    if (tp && tp != cp)
	krb5_free_principal(context, tp);
    krb5_free_principal(context, cp);
    krb5_free_principal(context, dp);
    krb5_free_principal(context, sp);
    krb5_free_principal(context, krbtgt_out_principal);
    free(ref_realm);
    free_METHOD_DATA(&enc_pa_data);

    free_EncTicketPart(&adtkt);

    return ret;
}

A null pointer de-reference was found in the way samba kerberos server handled missing sname in TGS-REQ (Ticket Granting Server - Request). An authenticated user could use this flaw to crash the samba server.


- Fix Integer Overflows in Size Calculations,
  reported by X41 D-Sec.

dnsc_load_local_data(struct dnsc_env* dnscenv, struct config_file *cfg)
{
    size_t i, j;
	// Insert 'local-zone: "2.dnscrypt-cert.example.com" deny'
    if(!cfg_str2list_insert(&cfg->local_zones,
                            strdup(dnscenv->provider_name),
                            strdup("deny"))) {
        log_err("Could not load dnscrypt local-zone: %s deny",
                dnscenv->provider_name);
        return -1;
    }

    // Add local data entry of type:
    // 2.dnscrypt-cert.example.com 86400 IN TXT "DNSC......"
    for(i=0; i<dnscenv->signed_certs_count; i++) {
        const char *ttl_class_type = " 86400 IN TXT \"";
        int rotated_cert = 0;
	uint32_t serial;
	uint16_t rrlen;
	char* rr;
        struct SignedCert *cert = dnscenv->signed_certs + i;
		// Check if the certificate is being rotated and should not be published
        for(j=0; j<dnscenv->rotated_certs_count; j++){
            if(cert == dnscenv->rotated_certs[j]) {
                rotated_cert = 1;
                break;
            }
        }
		memcpy(&serial, cert->serial, sizeof serial);
		serial = htonl(serial);
        if(rotated_cert) {
            verbose(VERB_OPS,
                "DNSCrypt: not adding cert with serial #%"
                PRIu32
                " to local-data as it is rotated",
                serial
            );
            continue;
        }
        rrlen = strlen(dnscenv->provider_name) +
                         strlen(ttl_class_type) +
                         4 * sizeof(struct SignedCert) + // worst case scenario
                         1 + // trailing double quote
                         1;
        rr = malloc(rrlen);
        if(!rr) {
            log_err("Could not allocate memory");
            return -2;
        }
        snprintf(rr, rrlen - 1, "%s 86400 IN TXT \"", dnscenv->provider_name);
        for(j=0; j<sizeof(struct SignedCert); j++) {
			int c = (int)*((const uint8_t *) cert + j);
            if (isprint(c) && c != '"' && c != '\\') {
                snprintf(rr + strlen(rr), rrlen - 1 - strlen(rr), "%c", c);
            } else {
                snprintf(rr + strlen(rr), rrlen - 1 - strlen(rr), "\\%03d", c);
            }
        }
        verbose(VERB_OPS,
			"DNSCrypt: adding cert with serial #%"
			PRIu32
			" to local-data to config: %s",
			serial, rr
		);
        snprintf(rr + strlen(rr), rrlen - 1 - strlen(rr), "\"");
        cfg_strlist_insert(&cfg->local_data, strdup(rr));
        free(rr);
    }
    return dnscenv->signed_certs_count;
}

Unbound before 1.9.5 allows an integer overflow in a size calculation in dnscrypt/dnscrypt.c. NOTE: The vendor disputes that this is a vulnerability. Although the code may be vulnerable, a running Unbound installation cannot be remotely or locally exploited


patch 8.2.5148: invalid memory access when using expression on command line

Problem:    Invalid memory access when using an expression on the command line.
Solution:   Make sure the position does not go negative.

cmdline_insert_reg(int *gotesc UNUSED)
{
    int		i;
    int		c;

#ifdef USE_ON_FLY_SCROLL
    dont_scroll = TRUE;	// disallow scrolling here
#endif
    putcmdline('"', TRUE);
    ++no_mapping;
    ++allow_keys;
    i = c = plain_vgetc();	// CTRL-R <char>
    if (i == Ctrl_O)
	i = Ctrl_R;		// CTRL-R CTRL-O == CTRL-R CTRL-R
    if (i == Ctrl_R)
	c = plain_vgetc();	// CTRL-R CTRL-R <char>
    extra_char = NUL;
    --no_mapping;
    --allow_keys;
#ifdef FEAT_EVAL
    /*
     * Insert the result of an expression.
     * Need to save the current command line, to be able to enter
     * a new one...
     */
    new_cmdpos = -1;
    if (c == '=')
    {
	if (ccline.cmdfirstc == '='  // can't do this recursively
		|| cmdline_star > 0) // or when typing a password
	{
	    beep_flush();
	    c = ESC;
	}
	else
	    c = get_expr_register();
    }
#endif
    if (c != ESC)	    // use ESC to cancel inserting register
    {
	cmdline_paste(c, i == Ctrl_R, FALSE);

#ifdef FEAT_EVAL
	// When there was a serious error abort getting the
	// command line.
	if (aborting())
	{
	    *gotesc = TRUE;  // will free ccline.cmdbuff after
	    // putting it in history
	    return GOTO_NORMAL_MODE;
	}
#endif
	KeyTyped = FALSE;	// Don't do p_wc completion.
#ifdef FEAT_EVAL
	if (new_cmdpos >= 0)
	{
	    // set_cmdline_pos() was used
	    if (new_cmdpos > ccline.cmdlen)
		ccline.cmdpos = ccline.cmdlen;
	    else
		ccline.cmdpos = new_cmdpos;
	}
#endif
    }
    // remove the double quote
    redrawcmd();

    // The text has been stuffed, the command line didn't change yet.
    return CMDLINE_NOT_CHANGED;
}

Buffer Over-read in GitHub repository vim/vim prior to 8.2.


Fix inappropriate fetch of entities content

For https://bugzilla.gnome.org/show_bug.cgi?id=761430

libfuzzer regression testing exposed another case where the parser would
fetch content of an external entity while not in validating mode.
Plug that hole

xmlStringLenDecodeEntities(xmlParserCtxtPtr ctxt, const xmlChar *str, int len,
		      int what, xmlChar end, xmlChar  end2, xmlChar end3) {
    xmlChar *buffer = NULL;
    size_t buffer_size = 0;
    size_t nbchars = 0;

    xmlChar *current = NULL;
    xmlChar *rep = NULL;
    const xmlChar *last;
    xmlEntityPtr ent;
    int c,l;

    if ((ctxt == NULL) || (str == NULL) || (len < 0))
	return(NULL);
    last = str + len;

    if (((ctxt->depth > 40) &&
         ((ctxt->options & XML_PARSE_HUGE) == 0)) ||
	(ctxt->depth > 1024)) {
	xmlFatalErr(ctxt, XML_ERR_ENTITY_LOOP, NULL);
	return(NULL);
    }

    /*
     * allocate a translation buffer.
     */
    buffer_size = XML_PARSER_BIG_BUFFER_SIZE;
    buffer = (xmlChar *) xmlMallocAtomic(buffer_size);
    if (buffer == NULL) goto mem_error;

    /*
     * OK loop until we reach one of the ending char or a size limit.
     * we are operating on already parsed values.
     */
    if (str < last)
	c = CUR_SCHAR(str, l);
    else
        c = 0;
    while ((c != 0) && (c != end) && /* non input consuming loop */
	   (c != end2) && (c != end3)) {

	if (c == 0) break;
        if ((c == '&') && (str[1] == '#')) {
	    int val = xmlParseStringCharRef(ctxt, &str);
	    if (val != 0) {
		COPY_BUF(0,buffer,nbchars,val);
	    }
	    if (nbchars + XML_PARSER_BUFFER_SIZE > buffer_size) {
	        growBuffer(buffer, XML_PARSER_BUFFER_SIZE);
	    }
	} else if ((c == '&') && (what & XML_SUBSTITUTE_REF)) {
	    if (xmlParserDebugEntities)
		xmlGenericError(xmlGenericErrorContext,
			"String decoding Entity Reference: %.30s\n",
			str);
	    ent = xmlParseStringEntityRef(ctxt, &str);
	    if ((ctxt->lastError.code == XML_ERR_ENTITY_LOOP) ||
	        (ctxt->lastError.code == XML_ERR_INTERNAL_ERROR))
	        goto int_error;
	    xmlParserEntityCheck(ctxt, 0, ent, 0);
	    if (ent != NULL)
	        ctxt->nbentities += ent->checked / 2;
	    if ((ent != NULL) &&
		(ent->etype == XML_INTERNAL_PREDEFINED_ENTITY)) {
		if (ent->content != NULL) {
		    COPY_BUF(0,buffer,nbchars,ent->content[0]);
		    if (nbchars + XML_PARSER_BUFFER_SIZE > buffer_size) {
			growBuffer(buffer, XML_PARSER_BUFFER_SIZE);
		    }
		} else {
		    xmlFatalErrMsg(ctxt, XML_ERR_INTERNAL_ERROR,
			    "predefined entity has no content\n");
		}
	    } else if ((ent != NULL) && (ent->content != NULL)) {
		ctxt->depth++;
		rep = xmlStringDecodeEntities(ctxt, ent->content, what,
			                      0, 0, 0);
		ctxt->depth--;

		if ((ctxt->lastError.code == XML_ERR_ENTITY_LOOP) ||
		    (ctxt->lastError.code == XML_ERR_INTERNAL_ERROR))
		    goto int_error;

		if (rep != NULL) {
		    current = rep;
		    while (*current != 0) { /* non input consuming loop */
			buffer[nbchars++] = *current++;
			if (nbchars + XML_PARSER_BUFFER_SIZE > buffer_size) {
			    if (xmlParserEntityCheck(ctxt, nbchars, ent, 0))
				goto int_error;
			    growBuffer(buffer, XML_PARSER_BUFFER_SIZE);
			}
		    }
		    xmlFree(rep);
		    rep = NULL;
		}
	    } else if (ent != NULL) {
		int i = xmlStrlen(ent->name);
		const xmlChar *cur = ent->name;

		buffer[nbchars++] = '&';
		if (nbchars + i + XML_PARSER_BUFFER_SIZE > buffer_size) {
		    growBuffer(buffer, i + XML_PARSER_BUFFER_SIZE);
		}
		for (;i > 0;i--)
		    buffer[nbchars++] = *cur++;
		buffer[nbchars++] = ';';
	    }
	} else if (c == '%' && (what & XML_SUBSTITUTE_PEREF)) {
	    if (xmlParserDebugEntities)
		xmlGenericError(xmlGenericErrorContext,
			"String decoding PE Reference: %.30s\n", str);
	    ent = xmlParseStringPEReference(ctxt, &str);
	    if (ctxt->lastError.code == XML_ERR_ENTITY_LOOP)
	        goto int_error;
	    xmlParserEntityCheck(ctxt, 0, ent, 0);
	    if (ent != NULL)
	        ctxt->nbentities += ent->checked / 2;
	    if (ent != NULL) {
                if (ent->content == NULL) {
		    xmlLoadEntityContent(ctxt, ent);
		}
		ctxt->depth++;
		rep = xmlStringDecodeEntities(ctxt, ent->content, what,
			                      0, 0, 0);
		ctxt->depth--;
		if (rep != NULL) {
		    current = rep;
		    while (*current != 0) { /* non input consuming loop */
			buffer[nbchars++] = *current++;
			if (nbchars + XML_PARSER_BUFFER_SIZE > buffer_size) {
			    if (xmlParserEntityCheck(ctxt, nbchars, ent, 0))
			        goto int_error;
			    growBuffer(buffer, XML_PARSER_BUFFER_SIZE);
			}
		    }
		    xmlFree(rep);
		    rep = NULL;
		}
	    }
	} else {
	    COPY_BUF(l,buffer,nbchars,c);
	    str += l;
	    if (nbchars + XML_PARSER_BUFFER_SIZE > buffer_size) {
	        growBuffer(buffer, XML_PARSER_BUFFER_SIZE);
	    }
	}
	if (str < last)
	    c = CUR_SCHAR(str, l);
	else
	    c = 0;
    }
    buffer[nbchars] = 0;
    return(buffer);

mem_error:
    xmlErrMemory(ctxt, NULL);
int_error:
    if (rep != NULL)
        xmlFree(rep);
    if (buffer != NULL)
        xmlFree(buffer);
    return(NULL);
}

XML external entity (XXE) vulnerability in the xmlStringLenDecodeEntities function in parser.c in libxml2 before 2.9.4, when not in validating mode, allows context-dependent attackers to read arbitrary files or cause a denial of service (resource consumption) via unspecified vectors.


static RzList *relocs(RzBinFile *bf) {
	rz_return_val_if_fail(bf && bf->o, NULL);
	QnxObj *qo = bf->o->bin_obj;
	return rz_list_clone(qo->fixups);
}

Rizin is a UNIX-like reverse engineering framework and command-line toolset. Versions 0.4.0 and prior are vulnerable to a double free in bobj.c:rz_bin_reloc_storage_free() when freeing relocations generated from qnx binary plugin. A user opening a malicious qnx binary could be affected by this vulnerability, allowing an attacker to execute code on the user's machine. Commit number a3d50c1ea185f3f642f2d8180715f82d98840784 contains a patch for this issue.


PHP_MINIT_FUNCTION(snmp)
{
	netsnmp_log_handler *logh;
	zend_class_entry ce, cex;

	le_snmp_session = zend_register_list_destructors_ex(php_snmp_session_destructor, NULL, PHP_SNMP_SESSION_RES_NAME, module_number);

	init_snmp("snmpapp");

#ifdef NETSNMP_DS_LIB_DONT_PERSIST_STATE
	/* Prevent update of the snmpapp.conf file */
	netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_DONT_PERSIST_STATE, 1);
#endif

	/* Disable logging, use exit status'es and related variabled to detect errors */
	shutdown_snmp_logging();
	logh = netsnmp_register_loghandler(NETSNMP_LOGHANDLER_NONE, LOG_ERR);
	if (logh) {
		logh->pri_max = LOG_ERR;
	}

	memcpy(&php_snmp_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
	php_snmp_object_handlers.read_property = php_snmp_read_property;
	php_snmp_object_handlers.write_property = php_snmp_write_property;
	php_snmp_object_handlers.has_property = php_snmp_has_property;
	php_snmp_object_handlers.get_properties = php_snmp_get_properties;

	/* Register SNMP Class */
	INIT_CLASS_ENTRY(ce, "SNMP", php_snmp_class_methods);
	ce.create_object = php_snmp_object_new;
	php_snmp_object_handlers.clone_obj = NULL;
	php_snmp_ce = zend_register_internal_class(&ce TSRMLS_CC);

	/* Register SNMP Class properties */
	zend_hash_init(&php_snmp_properties, 0, NULL, NULL, 1);
	PHP_SNMP_ADD_PROPERTIES(&php_snmp_properties, php_snmp_property_entries);

	REGISTER_LONG_CONSTANT("SNMP_OID_OUTPUT_SUFFIX",	NETSNMP_OID_OUTPUT_SUFFIX,	CONST_CS | CONST_PERSISTENT);
	REGISTER_LONG_CONSTANT("SNMP_OID_OUTPUT_MODULE",	NETSNMP_OID_OUTPUT_MODULE,	CONST_CS | CONST_PERSISTENT);
	REGISTER_LONG_CONSTANT("SNMP_OID_OUTPUT_FULL",		NETSNMP_OID_OUTPUT_FULL,	CONST_CS | CONST_PERSISTENT);
	REGISTER_LONG_CONSTANT("SNMP_OID_OUTPUT_NUMERIC",	NETSNMP_OID_OUTPUT_NUMERIC,	CONST_CS | CONST_PERSISTENT);
	REGISTER_LONG_CONSTANT("SNMP_OID_OUTPUT_UCD",		NETSNMP_OID_OUTPUT_UCD,		CONST_CS | CONST_PERSISTENT);
	REGISTER_LONG_CONSTANT("SNMP_OID_OUTPUT_NONE",		NETSNMP_OID_OUTPUT_NONE,	CONST_CS | CONST_PERSISTENT);

	REGISTER_LONG_CONSTANT("SNMP_VALUE_LIBRARY",	SNMP_VALUE_LIBRARY,	CONST_CS | CONST_PERSISTENT);
	REGISTER_LONG_CONSTANT("SNMP_VALUE_PLAIN",	SNMP_VALUE_PLAIN,	CONST_CS | CONST_PERSISTENT);
	REGISTER_LONG_CONSTANT("SNMP_VALUE_OBJECT",	SNMP_VALUE_OBJECT,	CONST_CS | CONST_PERSISTENT);

	REGISTER_LONG_CONSTANT("SNMP_BIT_STR",		ASN_BIT_STR,	CONST_CS | CONST_PERSISTENT);
	REGISTER_LONG_CONSTANT("SNMP_OCTET_STR",	ASN_OCTET_STR,	CONST_CS | CONST_PERSISTENT);
	REGISTER_LONG_CONSTANT("SNMP_OPAQUE",		ASN_OPAQUE,	CONST_CS | CONST_PERSISTENT);
	REGISTER_LONG_CONSTANT("SNMP_NULL",		ASN_NULL,	CONST_CS | CONST_PERSISTENT);
	REGISTER_LONG_CONSTANT("SNMP_OBJECT_ID",	ASN_OBJECT_ID,	CONST_CS | CONST_PERSISTENT);
	REGISTER_LONG_CONSTANT("SNMP_IPADDRESS",	ASN_IPADDRESS,	CONST_CS | CONST_PERSISTENT);
	REGISTER_LONG_CONSTANT("SNMP_COUNTER",		ASN_GAUGE,	CONST_CS | CONST_PERSISTENT);
	REGISTER_LONG_CONSTANT("SNMP_UNSIGNED",		ASN_UNSIGNED,	CONST_CS | CONST_PERSISTENT);
	REGISTER_LONG_CONSTANT("SNMP_TIMETICKS",	ASN_TIMETICKS,	CONST_CS | CONST_PERSISTENT);
	REGISTER_LONG_CONSTANT("SNMP_UINTEGER",		ASN_UINTEGER,	CONST_CS | CONST_PERSISTENT);
	REGISTER_LONG_CONSTANT("SNMP_INTEGER",		ASN_INTEGER,	CONST_CS | CONST_PERSISTENT);
	REGISTER_LONG_CONSTANT("SNMP_COUNTER64",	ASN_COUNTER64,	CONST_CS | CONST_PERSISTENT);

	REGISTER_SNMP_CLASS_CONST_LONG("VERSION_1",			SNMP_VERSION_1);
	REGISTER_SNMP_CLASS_CONST_LONG("VERSION_2c",			SNMP_VERSION_2c);
	REGISTER_SNMP_CLASS_CONST_LONG("VERSION_2C",			SNMP_VERSION_2c);
	REGISTER_SNMP_CLASS_CONST_LONG("VERSION_3",			SNMP_VERSION_3);

	REGISTER_SNMP_CLASS_CONST_LONG("ERRNO_NOERROR",			PHP_SNMP_ERRNO_NOERROR);
	REGISTER_SNMP_CLASS_CONST_LONG("ERRNO_ANY",			PHP_SNMP_ERRNO_ANY);
	REGISTER_SNMP_CLASS_CONST_LONG("ERRNO_GENERIC",			PHP_SNMP_ERRNO_GENERIC);
	REGISTER_SNMP_CLASS_CONST_LONG("ERRNO_TIMEOUT",			PHP_SNMP_ERRNO_TIMEOUT);
	REGISTER_SNMP_CLASS_CONST_LONG("ERRNO_ERROR_IN_REPLY",		PHP_SNMP_ERRNO_ERROR_IN_REPLY);
	REGISTER_SNMP_CLASS_CONST_LONG("ERRNO_OID_NOT_INCREASING",	PHP_SNMP_ERRNO_OID_NOT_INCREASING);
	REGISTER_SNMP_CLASS_CONST_LONG("ERRNO_OID_PARSING_ERROR",	PHP_SNMP_ERRNO_OID_PARSING_ERROR);
	REGISTER_SNMP_CLASS_CONST_LONG("ERRNO_MULTIPLE_SET_QUERIES",	PHP_SNMP_ERRNO_MULTIPLE_SET_QUERIES);

	/* Register SNMPException class */
	INIT_CLASS_ENTRY(cex, "SNMPException", NULL);
#ifdef HAVE_SPL
	php_snmp_exception_ce = zend_register_internal_class_ex(&cex, spl_ce_RuntimeException, NULL TSRMLS_CC);
#else
	php_snmp_exception_ce = zend_register_internal_class_ex(&cex, zend_exception_get_default(TSRMLS_C), NULL TSRMLS_CC);
#endif

	return SUCCESS;
}

ext/snmp/snmp.c in PHP before 5.5.38, 5.6.x before 5.6.24, and 7.x before 7.0.9 improperly interacts with the unserialize implementation and garbage collection, which allows remote attackers to cause a denial of service (use-after-free and application crash) or possibly have unspecified other impact via crafted serialized data, a related issue to CVE-2016-5773.


portal: Let --env= and --env-fd= take precedence over extra-args

Previously, if you launched a subsandbox while specifying environment
variable overrides, any environment variable overrides that existed
in the parent Flatpak app would take precedence:

    host$ flatpak run --env=FOO=1 --command=bash example.app
    [📦 example.app ~]$ env | grep FOO
    FOO=1
    [📦 example.app ~]$ flatpak-spawn --env=FOO=x sh -c 'env | grep FOO'
    FOO=1

This does not seem like least-astonishment, and in particular will
cause problems if the app wants to override LD_LIBRARY_PATH in the
subsandbox. Change the precedence so that the environment variables
set by flatpak-spawn will "win":

    host$ flatpak run --env=FOO1=1 --env=FOO2=2 --command=bash example.app
    [📦 example.app ~]$ env | grep FOO
    FOO1=1
    FOO2=2
    [📦 example.app ~]$ flatpak-spawn --env=FOO1=x sh -c 'env | grep FOO'
    FOO1=x
    FOO2=2

This follows up from GHSA-4ppf-fxf6-vxg2 to fix an issue that I noticed
while resolving that vulnerability, but is not required for fixing the
vulnerability.

Signed-off-by: Simon McVittie <smcv@collabora.com>

handle_spawn (PortalFlatpak         *object,
              GDBusMethodInvocation *invocation,
              GUnixFDList           *fd_list,
              const gchar           *arg_cwd_path,
              const gchar *const    *arg_argv,
              GVariant              *arg_fds,
              GVariant              *arg_envs,
              guint                  arg_flags,
              GVariant              *arg_options)
{
  g_autoptr(GError) error = NULL;
  ChildSetupData child_setup_data = { NULL };
  GPid pid;
  PidData *pid_data;
  InstanceIdReadData *instance_id_read_data = NULL;
  gsize i, j, n_fds, n_envs;
  const gint *fds = NULL;
  gint fds_len = 0;
  g_autofree FdMapEntry *fd_map = NULL;
  gchar **env;
  gint32 max_fd;
  GKeyFile *app_info;
  g_autoptr(GPtrArray) flatpak_argv = g_ptr_array_new_with_free_func (g_free);
  g_autofree char *app_id = NULL;
  g_autofree char *branch = NULL;
  g_autofree char *arch = NULL;
  g_autofree char *app_commit = NULL;
  g_autofree char *runtime_ref = NULL;
  g_auto(GStrv) runtime_parts = NULL;
  g_autofree char *runtime_commit = NULL;
  g_autofree char *instance_path = NULL;
  g_auto(GStrv) extra_args = NULL;
  g_auto(GStrv) shares = NULL;
  g_auto(GStrv) sockets = NULL;
  g_auto(GStrv) devices = NULL;
  g_auto(GStrv) sandbox_expose = NULL;
  g_auto(GStrv) sandbox_expose_ro = NULL;
  g_autoptr(GVariant) sandbox_expose_fd = NULL;
  g_autoptr(GVariant) sandbox_expose_fd_ro = NULL;
  g_autoptr(GOutputStream) instance_id_out_stream = NULL;
  guint sandbox_flags = 0;
  gboolean sandboxed;
  gboolean expose_pids;
  gboolean share_pids;
  gboolean notify_start;
  gboolean devel;
  g_autoptr(GString) env_string = g_string_new ("");

  child_setup_data.instance_id_fd = -1;
  child_setup_data.env_fd = -1;

  if (fd_list != NULL)
    fds = g_unix_fd_list_peek_fds (fd_list, &fds_len);

  app_info = g_object_get_data (G_OBJECT (invocation), "app-info");
  g_assert (app_info != NULL);

  app_id = g_key_file_get_string (app_info,
                                  FLATPAK_METADATA_GROUP_APPLICATION,
                                  FLATPAK_METADATA_KEY_NAME, NULL);
  g_assert (app_id != NULL);

  g_debug ("spawn() called from app: '%s'", app_id);
  if (*app_id == 0)
    {
      g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
                                             G_DBUS_ERROR_INVALID_ARGS,
                                             "org.freedesktop.portal.Flatpak.Spawn only works in a flatpak");
      return G_DBUS_METHOD_INVOCATION_HANDLED;
    }

  if (*arg_cwd_path == 0)
    arg_cwd_path = NULL;

  if (arg_argv == NULL || *arg_argv == NULL)
    {
      g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
                                             G_DBUS_ERROR_INVALID_ARGS,
                                             "No command given");
      return G_DBUS_METHOD_INVOCATION_HANDLED;
    }

  if ((arg_flags & ~FLATPAK_SPAWN_FLAGS_ALL) != 0)
    {
      g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
                                             "Unsupported flags enabled: 0x%x", arg_flags & ~FLATPAK_SPAWN_FLAGS_ALL);
      return G_DBUS_METHOD_INVOCATION_HANDLED;
    }

  runtime_ref = g_key_file_get_string (app_info,
                                       FLATPAK_METADATA_GROUP_APPLICATION,
                                       FLATPAK_METADATA_KEY_RUNTIME, NULL);
  if (runtime_ref == NULL)
    {
      g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
                                             "No runtime found");
      return G_DBUS_METHOD_INVOCATION_HANDLED;
    }

  runtime_parts = g_strsplit (runtime_ref, "/", -1);

  branch = g_key_file_get_string (app_info,
                                  FLATPAK_METADATA_GROUP_INSTANCE,
                                  FLATPAK_METADATA_KEY_BRANCH, NULL);
  instance_path = g_key_file_get_string (app_info,
                                         FLATPAK_METADATA_GROUP_INSTANCE,
                                         FLATPAK_METADATA_KEY_INSTANCE_PATH, NULL);
  arch = g_key_file_get_string (app_info,
                                FLATPAK_METADATA_GROUP_INSTANCE,
                                FLATPAK_METADATA_KEY_ARCH, NULL);
  extra_args = g_key_file_get_string_list (app_info,
                                           FLATPAK_METADATA_GROUP_INSTANCE,
                                           FLATPAK_METADATA_KEY_EXTRA_ARGS, NULL, NULL);
  app_commit = g_key_file_get_string (app_info,
                                      FLATPAK_METADATA_GROUP_INSTANCE,
                                      FLATPAK_METADATA_KEY_APP_COMMIT, NULL);
  runtime_commit = g_key_file_get_string (app_info,
                                          FLATPAK_METADATA_GROUP_INSTANCE,
                                          FLATPAK_METADATA_KEY_RUNTIME_COMMIT, NULL);
  shares = g_key_file_get_string_list (app_info, FLATPAK_METADATA_GROUP_CONTEXT,
                                       FLATPAK_METADATA_KEY_SHARED, NULL, NULL);
  sockets = g_key_file_get_string_list (app_info, FLATPAK_METADATA_GROUP_CONTEXT,
                                       FLATPAK_METADATA_KEY_SOCKETS, NULL, NULL);
  devices = g_key_file_get_string_list (app_info, FLATPAK_METADATA_GROUP_CONTEXT,
                                        FLATPAK_METADATA_KEY_DEVICES, NULL, NULL);

  devel = g_key_file_get_boolean (app_info, FLATPAK_METADATA_GROUP_INSTANCE,
                                  FLATPAK_METADATA_KEY_DEVEL, NULL);

  g_variant_lookup (arg_options, "sandbox-expose", "^as", &sandbox_expose);
  g_variant_lookup (arg_options, "sandbox-expose-ro", "^as", &sandbox_expose_ro);
  g_variant_lookup (arg_options, "sandbox-flags", "u", &sandbox_flags);
  sandbox_expose_fd = g_variant_lookup_value (arg_options, "sandbox-expose-fd", G_VARIANT_TYPE ("ah"));
  sandbox_expose_fd_ro = g_variant_lookup_value (arg_options, "sandbox-expose-fd-ro", G_VARIANT_TYPE ("ah"));

  if ((sandbox_flags & ~FLATPAK_SPAWN_SANDBOX_FLAGS_ALL) != 0)
    {
      g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
                                             "Unsupported sandbox flags enabled: 0x%x", arg_flags & ~FLATPAK_SPAWN_SANDBOX_FLAGS_ALL);
      return G_DBUS_METHOD_INVOCATION_HANDLED;
    }

  if (instance_path == NULL &&
      ((sandbox_expose != NULL && sandbox_expose[0] != NULL) ||
       (sandbox_expose_ro != NULL && sandbox_expose_ro[0] != NULL)))
    {
      g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
                                             G_DBUS_ERROR_INVALID_ARGS,
                                             "Invalid sandbox expose, caller has no instance path");
      return G_DBUS_METHOD_INVOCATION_HANDLED;
    }

  for (i = 0; sandbox_expose != NULL && sandbox_expose[i] != NULL; i++)
    {
      const char *expose = sandbox_expose[i];

      g_debug ("exposing %s", expose);
      if (!is_valid_expose (expose, &error))
        {
          g_dbus_method_invocation_return_gerror (invocation, error);
          return G_DBUS_METHOD_INVOCATION_HANDLED;
        }
    }

  for (i = 0; sandbox_expose_ro != NULL && sandbox_expose_ro[i] != NULL; i++)
    {
      const char *expose = sandbox_expose_ro[i];
      g_debug ("exposing %s", expose);
      if (!is_valid_expose (expose, &error))
        {
          g_dbus_method_invocation_return_gerror (invocation, error);
          return G_DBUS_METHOD_INVOCATION_HANDLED;
        }
    }

  g_debug ("Running spawn command %s", arg_argv[0]);

  n_fds = 0;
  if (fds != NULL)
    n_fds = g_variant_n_children (arg_fds);
  fd_map = g_new0 (FdMapEntry, n_fds);

  child_setup_data.fd_map = fd_map;
  child_setup_data.fd_map_len = n_fds;

  max_fd = -1;
  for (i = 0; i < n_fds; i++)
    {
      gint32 handle, dest_fd;
      int handle_fd;

      g_variant_get_child (arg_fds, i, "{uh}", &dest_fd, &handle);

      if (handle >= fds_len || handle < 0)
        {
          g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
                                                 G_DBUS_ERROR_INVALID_ARGS,
                                                 "No file descriptor for handle %d",
                                                 handle);
          return G_DBUS_METHOD_INVOCATION_HANDLED;
        }

      handle_fd = fds[handle];

      fd_map[i].to = dest_fd;
      fd_map[i].from = handle_fd;
      fd_map[i].final = fd_map[i].to;

      /* If stdin/out/err is a tty we try to set it as the controlling
         tty for the app, this way we can use this to run in a terminal. */
      if ((dest_fd == 0 || dest_fd == 1 || dest_fd == 2) &&
          !child_setup_data.set_tty &&
          isatty (handle_fd))
        {
          child_setup_data.set_tty = TRUE;
          child_setup_data.tty = handle_fd;
        }

      max_fd = MAX (max_fd, fd_map[i].to);
      max_fd = MAX (max_fd, fd_map[i].from);
    }

  /* We make a second pass over the fds to find if any "to" fd index
     overlaps an already in use fd (i.e. one in the "from" category
     that are allocated randomly). If a fd overlaps "to" fd then its
     a caller issue and not our fault, so we ignore that. */
  for (i = 0; i < n_fds; i++)
    {
      int to_fd = fd_map[i].to;
      gboolean conflict = FALSE;

      /* At this point we're fine with using "from" values for this
         value (because we handle to==from in the code), or values
         that are before "i" in the fd_map (because those will be
         closed at this point when dup:ing). However, we can't
         reuse a fd that is in "from" for j > i. */
      for (j = i + 1; j < n_fds; j++)
        {
          int from_fd = fd_map[j].from;
          if (from_fd == to_fd)
            {
              conflict = TRUE;
              break;
            }
        }

      if (conflict)
        fd_map[i].to = ++max_fd;
    }

  /* TODO: Ideally we should let `flatpak run` inherit the portal's
   * environment, in case e.g. a LD_LIBRARY_PATH is needed to be able
   * to run `flatpak run`, but tell it to start from a blank environment
   * when running the Flatpak app; but this isn't currently possible, so
   * for now we preserve existing behaviour. */
  if (arg_flags & FLATPAK_SPAWN_FLAGS_CLEAR_ENV)
    {
      char *empty[] = { NULL };
      env = g_strdupv (empty);
    }
  else
    env = g_get_environ ();

  /* Let the environment variables given by the caller override the ones
   * from extra_args. Don't add them to @env, because they are controlled
   * by our caller, which might be trying to use them to inject code into
   * flatpak(1); add them to the environment block instead.
   *
   * We don't use --env= here, so that if the values are something that
   * should not be exposed to other uids, they can remain confidential. */
  n_envs = g_variant_n_children (arg_envs);
  for (i = 0; i < n_envs; i++)
    {
      const char *var = NULL;
      const char *val = NULL;
      g_variant_get_child (arg_envs, i, "{&s&s}", &var, &val);

      if (var[0] == '\0')
        {
          g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
                                                 G_DBUS_ERROR_INVALID_ARGS,
                                                 "Environment variable cannot have empty name");
          return G_DBUS_METHOD_INVOCATION_HANDLED;
        }

      if (strchr (var, '=') != NULL)
        {
          g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
                                                 G_DBUS_ERROR_INVALID_ARGS,
                                                 "Environment variable name cannot contain '='");
          return G_DBUS_METHOD_INVOCATION_HANDLED;
        }

      g_string_append (env_string, var);
      g_string_append_c (env_string, '=');
      g_string_append (env_string, val);
      g_string_append_c (env_string, '\0');
    }

  g_ptr_array_add (flatpak_argv, g_strdup ("flatpak"));
  g_ptr_array_add (flatpak_argv, g_strdup ("run"));

  sandboxed = (arg_flags & FLATPAK_SPAWN_FLAGS_SANDBOX) != 0;

  if (sandboxed)
    {
      g_ptr_array_add (flatpak_argv, g_strdup ("--sandbox"));

      if (sandbox_flags & FLATPAK_SPAWN_SANDBOX_FLAGS_SHARE_DISPLAY)
        {
          if (sockets != NULL && g_strv_contains ((const char * const *) sockets, "wayland"))
            g_ptr_array_add (flatpak_argv, g_strdup ("--socket=wayland"));
          if (sockets != NULL && g_strv_contains ((const char * const *) sockets, "fallback-x11"))
            g_ptr_array_add (flatpak_argv, g_strdup ("--socket=fallback-x11"));
          if (sockets != NULL && g_strv_contains ((const char * const *) sockets, "x11"))
            g_ptr_array_add (flatpak_argv, g_strdup ("--socket=x11"));
          if (shares != NULL && g_strv_contains ((const char * const *) shares, "ipc") &&
              sockets != NULL && (g_strv_contains ((const char * const *) sockets, "fallback-x11") ||
                                  g_strv_contains ((const char * const *) sockets, "x11")))
            g_ptr_array_add (flatpak_argv, g_strdup ("--share=ipc"));
        }
      if (sandbox_flags & FLATPAK_SPAWN_SANDBOX_FLAGS_SHARE_SOUND)
        {
          if (sockets != NULL && g_strv_contains ((const char * const *) sockets, "pulseaudio"))
            g_ptr_array_add (flatpak_argv, g_strdup ("--socket=pulseaudio"));
        }
      if (sandbox_flags & FLATPAK_SPAWN_SANDBOX_FLAGS_SHARE_GPU)
        {
          if (devices != NULL &&
              (g_strv_contains ((const char * const *) devices, "dri") ||
               g_strv_contains ((const char * const *) devices, "all")))
            g_ptr_array_add (flatpak_argv, g_strdup ("--device=dri"));
        }
      if (sandbox_flags & FLATPAK_SPAWN_SANDBOX_FLAGS_ALLOW_DBUS)
        g_ptr_array_add (flatpak_argv, g_strdup ("--session-bus"));
      if (sandbox_flags & FLATPAK_SPAWN_SANDBOX_FLAGS_ALLOW_A11Y)
        g_ptr_array_add (flatpak_argv, g_strdup ("--a11y-bus"));
    }
  else
    {
      for (i = 0; extra_args != NULL && extra_args[i] != NULL; i++)
        {
          if (g_str_has_prefix (extra_args[i], "--env="))
            {
              const char *var_val = extra_args[i] + strlen ("--env=");

              if (var_val[0] == '\0' || var_val[0] == '=')
                {
                  g_warning ("Environment variable in extra-args has empty name");
                  continue;
                }

              if (strchr (var_val, '=') == NULL)
                {
                  g_warning ("Environment variable in extra-args has no value");
                  continue;
                }

              g_string_append (env_string, var_val);
              g_string_append_c (env_string, '\0');
            }
          else
            {
              g_ptr_array_add (flatpak_argv, g_strdup (extra_args[i]));
            }
        }
    }

  if (env_string->len > 0)
    {
      g_auto(GLnxTmpfile) env_tmpf  = { 0, };

      if (!flatpak_buffer_to_sealed_memfd_or_tmpfile (&env_tmpf, "environ",
                                                      env_string->str,
                                                      env_string->len, &error))
        {
          g_dbus_method_invocation_return_gerror (invocation, error);
          return G_DBUS_METHOD_INVOCATION_HANDLED;
        }

      child_setup_data.env_fd = glnx_steal_fd (&env_tmpf.fd);
      g_ptr_array_add (flatpak_argv,
                       g_strdup_printf ("--env-fd=%d",
                                        child_setup_data.env_fd));
    }

  expose_pids = (arg_flags & FLATPAK_SPAWN_FLAGS_EXPOSE_PIDS) != 0;
  share_pids = (arg_flags & FLATPAK_SPAWN_FLAGS_SHARE_PIDS) != 0;

  if (expose_pids || share_pids)
    {
      g_autofree char *instance_id = NULL;
      int sender_pid1 = 0;

      if (!(supports & FLATPAK_SPAWN_SUPPORT_FLAGS_EXPOSE_PIDS))
        {
          g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
                                                 G_DBUS_ERROR_NOT_SUPPORTED,
                                                 "Expose pids not supported with setuid bwrap");
          return G_DBUS_METHOD_INVOCATION_HANDLED;
        }

      instance_id = g_key_file_get_string (app_info,
                                           FLATPAK_METADATA_GROUP_INSTANCE,
                                           FLATPAK_METADATA_KEY_INSTANCE_ID, NULL);

      if (instance_id)
        {
          g_autoptr(FlatpakInstance) instance = flatpak_instance_new_for_id (instance_id);
          sender_pid1 = flatpak_instance_get_child_pid (instance);
        }

      if (sender_pid1 == 0)
        {
          g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
                                                 G_DBUS_ERROR_INVALID_ARGS,
                                                 "Could not find requesting pid");
          return G_DBUS_METHOD_INVOCATION_HANDLED;
        }

      g_ptr_array_add (flatpak_argv, g_strdup_printf ("--parent-pid=%d", sender_pid1));

      if (share_pids)
        g_ptr_array_add (flatpak_argv, g_strdup ("--parent-share-pids"));
      else
        g_ptr_array_add (flatpak_argv, g_strdup ("--parent-expose-pids"));
    }

  notify_start = (arg_flags & FLATPAK_SPAWN_FLAGS_NOTIFY_START) != 0;
  if (notify_start)
    {
      int pipe_fds[2];
      if (pipe (pipe_fds) == -1)
        {
          int errsv = errno;
          g_dbus_method_invocation_return_error (invocation, G_IO_ERROR,
                                                 g_io_error_from_errno (errsv),
                                                 "Failed to create instance ID pipe: %s",
                                                 g_strerror (errsv));
          return G_DBUS_METHOD_INVOCATION_HANDLED;
        }

      GInputStream *in_stream = G_INPUT_STREAM (g_unix_input_stream_new (pipe_fds[0], TRUE));
      /* This is saved to ensure the portal's end gets closed after the exec. */
      instance_id_out_stream = G_OUTPUT_STREAM (g_unix_output_stream_new (pipe_fds[1], TRUE));

      instance_id_read_data = g_new0 (InstanceIdReadData, 1);

      g_input_stream_read_async (in_stream, instance_id_read_data->buffer,
                                 INSTANCE_ID_BUFFER_SIZE - 1, G_PRIORITY_DEFAULT, NULL,
                                 instance_id_read_finish, instance_id_read_data);

      g_ptr_array_add (flatpak_argv, g_strdup_printf ("--instance-id-fd=%d", pipe_fds[1]));
      child_setup_data.instance_id_fd = pipe_fds[1];
    }

  if (devel)
    g_ptr_array_add (flatpak_argv, g_strdup ("--devel"));

  /* Inherit launcher network access from launcher, unless
     NO_NETWORK set. */
  if (shares != NULL && g_strv_contains ((const char * const *) shares, "network") &&
      !(arg_flags & FLATPAK_SPAWN_FLAGS_NO_NETWORK))
    g_ptr_array_add (flatpak_argv, g_strdup ("--share=network"));
  else
    g_ptr_array_add (flatpak_argv, g_strdup ("--unshare=network"));

  if (instance_path)
    {
      for (i = 0; sandbox_expose != NULL && sandbox_expose[i] != NULL; i++)
        g_ptr_array_add (flatpak_argv,
                         filesystem_sandbox_arg (instance_path, sandbox_expose[i], FALSE));
      for (i = 0; sandbox_expose_ro != NULL && sandbox_expose_ro[i] != NULL; i++)
        g_ptr_array_add (flatpak_argv,
                         filesystem_sandbox_arg (instance_path, sandbox_expose_ro[i], TRUE));
    }

  for (i = 0; sandbox_expose_ro != NULL && sandbox_expose_ro[i] != NULL; i++)
    {
      const char *expose = sandbox_expose_ro[i];
      g_debug ("exposing %s", expose);
    }

  if (sandbox_expose_fd != NULL)
    {
      gsize len = g_variant_n_children (sandbox_expose_fd);
      for (i = 0; i < len; i++)
        {
          gint32 handle;
          g_variant_get_child (sandbox_expose_fd, i, "h", &handle);
          if (handle >= 0 && handle < fds_len)
            {
              int handle_fd = fds[handle];
              g_autofree char *path = NULL;
              gboolean writable = FALSE;

              path = get_path_for_fd (handle_fd, &writable, &error);

              if (path)
                {
                  g_ptr_array_add (flatpak_argv, filesystem_arg (path, !writable));
                }
              else
                {
                  g_debug ("unable to get path for sandbox-exposed fd %d, ignoring: %s",
                           handle_fd, error->message);
                  g_clear_error (&error);
                }
            }
          else
            {
              g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
                                                     G_DBUS_ERROR_INVALID_ARGS,
                                                     "No file descriptor for handle %d",
                                                     handle);
              return G_DBUS_METHOD_INVOCATION_HANDLED;
            }
        }
    }

  if (sandbox_expose_fd_ro != NULL)
    {
      gsize len = g_variant_n_children (sandbox_expose_fd_ro);
      for (i = 0; i < len; i++)
        {
          gint32 handle;
          g_variant_get_child (sandbox_expose_fd_ro, i, "h", &handle);
          if (handle >= 0 && handle < fds_len)
            {
              int handle_fd = fds[handle];
              g_autofree char *path = NULL;
              gboolean writable = FALSE;

              path = get_path_for_fd (handle_fd, &writable, &error);

              if (path)
                {
                  g_ptr_array_add (flatpak_argv, filesystem_arg (path, TRUE));
                }
              else
                {
                  g_debug ("unable to get path for sandbox-exposed fd %d, ignoring: %s",
                           handle_fd, error->message);
                  g_clear_error (&error);
                }
            }
          else
            {
              g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
                                                     G_DBUS_ERROR_INVALID_ARGS,
                                                     "No file descriptor for handle %d",
                                                     handle);
              return G_DBUS_METHOD_INVOCATION_HANDLED;
            }
        }
    }

  g_ptr_array_add (flatpak_argv, g_strdup_printf ("--runtime=%s", runtime_parts[1]));
  g_ptr_array_add (flatpak_argv, g_strdup_printf ("--runtime-version=%s", runtime_parts[3]));

  if ((arg_flags & FLATPAK_SPAWN_FLAGS_LATEST_VERSION) == 0)
    {
      if (app_commit)
        g_ptr_array_add (flatpak_argv, g_strdup_printf ("--commit=%s", app_commit));
      if (runtime_commit)
        g_ptr_array_add (flatpak_argv, g_strdup_printf ("--runtime-commit=%s", runtime_commit));
    }

  if (arg_cwd_path != NULL)
    g_ptr_array_add (flatpak_argv, g_strdup_printf ("--cwd=%s", arg_cwd_path));

  if (arg_argv[0][0] != 0)
    g_ptr_array_add (flatpak_argv, g_strdup_printf ("--command=%s", arg_argv[0]));

  g_ptr_array_add (flatpak_argv, g_strdup_printf ("%s/%s/%s", app_id, arch ? arch : "", branch ? branch : ""));
  for (i = 1; arg_argv[i] != NULL; i++)
    g_ptr_array_add (flatpak_argv, g_strdup (arg_argv[i]));
  g_ptr_array_add (flatpak_argv, NULL);

  if (opt_verbose)
    {
      g_autoptr(GString) cmd = g_string_new ("");

      for (i = 0; flatpak_argv->pdata[i] != NULL; i++)
        {
          if (i > 0)
            g_string_append (cmd, " ");
          g_string_append (cmd, flatpak_argv->pdata[i]);
        }

      g_debug ("Starting: %s\n", cmd->str);
    }

  /* We use LEAVE_DESCRIPTORS_OPEN to work around dead-lock, see flatpak_close_fds_workaround */
  if (!g_spawn_async_with_pipes (NULL,
                                 (char **) flatpak_argv->pdata,
                                 env,
                                 G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_LEAVE_DESCRIPTORS_OPEN,
                                 child_setup_func, &child_setup_data,
                                 &pid,
                                 NULL,
                                 NULL,
                                 NULL,
                                 &error))
    {
      gint code = G_DBUS_ERROR_FAILED;
      if (g_error_matches (error, G_SPAWN_ERROR, G_SPAWN_ERROR_ACCES))
        code = G_DBUS_ERROR_ACCESS_DENIED;
      else if (g_error_matches (error, G_SPAWN_ERROR, G_SPAWN_ERROR_NOENT))
        code = G_DBUS_ERROR_FILE_NOT_FOUND;
      g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, code,
                                             "Failed to start command: %s",
                                             error->message);
      return G_DBUS_METHOD_INVOCATION_HANDLED;
    }

  if (instance_id_read_data)
    instance_id_read_data->pid = pid;

  pid_data = g_new0 (PidData, 1);
  pid_data->pid = pid;
  pid_data->client = g_strdup (g_dbus_method_invocation_get_sender (invocation));
  pid_data->watch_bus = (arg_flags & FLATPAK_SPAWN_FLAGS_WATCH_BUS) != 0;
  pid_data->expose_or_share_pids = (expose_pids || share_pids);
  pid_data->child_watch = g_child_watch_add_full (G_PRIORITY_DEFAULT,
                                                  pid,
                                                  child_watch_died,
                                                  pid_data,
                                                  NULL);

  g_debug ("Client Pid is %d", pid_data->pid);

  g_hash_table_replace (client_pid_data_hash, GUINT_TO_POINTER (pid_data->pid),
                        pid_data);

  portal_flatpak_complete_spawn (object, invocation, NULL, pid);
  return G_DBUS_METHOD_INVOCATION_HANDLED;
}

Flatpak is a system for building, distributing, and running sandboxed desktop applications on Linux. A bug was discovered in the `flatpak-portal` service that can allow sandboxed applications to execute arbitrary code on the host system (a sandbox escape). This sandbox-escape bug is present in versions from 0.11.4 and before fixed versions 1.8.5 and 1.10.0. The Flatpak portal D-Bus service (`flatpak-portal`, also known by its D-Bus service name `org.freedesktop.portal.Flatpak`) allows apps in a Flatpak sandbox to launch their own subprocesses in a new sandbox instance, either with the same security settings as the caller or with more restrictive security settings. For example, this is used in Flatpak-packaged web browsers such as Chromium to launch subprocesses that will process untrusted web content, and give those subprocesses a more restrictive sandbox than the browser itself. In vulnerable versions, the Flatpak portal service passes caller-specified environment variables to non-sandboxed processes on the host system, and in particular to the `flatpak run` command that is used to launch the new sandbox instance. A malicious or compromised Flatpak app could set environment variables that are trusted by the `flatpak run` command, and use them to execute arbitrary code that is not in a sandbox. As a workaround, this vulnerability can be mitigated by preventing the `flatpak-portal` service from starting, but that mitigation will prevent many Flatpak apps from working correctly. This is fixed in versions 1.8.5 and 1.10.0.