From f8926f4b0e3bd01f17eec5affe44ddaeb910ed0b Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Wed, 29 Jul 2020 12:00:09 -0400
Subject: [PATCH 340/377] FIXME: get CVE-2005-1689 to report again

---
 ...R_UPSTREAM-CVE-2005-1689-krb5-recvauth-2.c | 425 ++++++++++++++++++
 gcc/analyzer/analyzer.h                       |   4 +-
 gcc/analyzer/constraint-manager2.cc           |  36 +-
 gcc/analyzer/constraint-manager2.h            |   3 +
 gcc/analyzer/engine.cc                        |  11 +
 gcc/analyzer/exploded-graph.h                 |   2 +
 gcc/analyzer/program-state.cc                 |  80 ++--
 gcc/analyzer/program-state.h                  |   4 +
 gcc/analyzer/region-model2.cc                 | 105 ++++-
 gcc/analyzer/region-model2.h                  |  36 +-
 gcc/analyzer/store2.cc                        |  21 +-
 gcc/analyzer/store2.h                         |   6 +-
 .../gcc.dg/analyzer/CVE-2005-1689-minimal.c   |  52 ++-
 ...FOR_UPSTREAM-CVE-2005-1689-krb5-recvauth.c | 145 ++----
 gcc/testsuite/gcc.dg/analyzer/feasibility-1.c |  21 +-
 gcc/testsuite/gcc.dg/analyzer/unknown-fns-2.c |   8 +-
 gcc/testsuite/gcc.dg/analyzer/unknown-fns-3.c |  67 +++
 gcc/testsuite/lib/multiline.exp               |   2 +-
 18 files changed, 863 insertions(+), 165 deletions(-)
 create mode 100644 NOT_FOR_UPSTREAM-CVE-2005-1689-krb5-recvauth-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/unknown-fns-3.c

diff --git a/NOT_FOR_UPSTREAM-CVE-2005-1689-krb5-recvauth-2.c b/NOT_FOR_UPSTREAM-CVE-2005-1689-krb5-recvauth-2.c
new file mode 100644
index 00000000000..d3426a94489
--- /dev/null
+++ b/NOT_FOR_UPSTREAM-CVE-2005-1689-krb5-recvauth-2.c
@@ -0,0 +1,425 @@
+/* Adapted from krb5-1.4.1/lib/krb5/krb/recvauth.c, which has this
+   license header.  */
+
+/*
+ * lib/krb5/krb/recvauth.c
+ *
+ * Copyright 1991 by the Massachusetts Institute of Technology.
+ * All Rights Reserved.
+ *
+ * Export of this software from the United States of America may
+ *   require a specific license from the United States Government.
+ *   It is the responsibility of any person or organization contemplating
+ *   export to obtain such a license before exporting.
+ * 
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission.  Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose.  It is provided "as is" without express
+ * or implied warranty.
+ * 
+ *
+ * convenience sendauth/recvauth functions
+ */
+
+/* FIXME.  */
+
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+
+/* Minimal version of krb5 types.  */
+
+typedef int krb5_int32;
+typedef unsigned char krb5_octet;
+typedef void * krb5_pointer;
+
+typedef krb5_int32 krb5_error_code;
+typedef krb5_int32 krb5_flags;
+typedef krb5_int32 krb5_timestamp;
+typedef unsigned int krb5_ui_4;
+typedef long errcode_t;
+
+typedef krb5_error_code krb5_magic;
+
+typedef struct _krb5_data {
+#if 0
+  krb5_magic magic;
+#endif
+  unsigned int length;
+  char *data;
+} krb5_data;
+
+struct _krb5_context;
+typedef struct _krb5_context * krb5_context;
+
+struct _krb5_auth_context;
+typedef struct _krb5_auth_context * krb5_auth_context;
+
+struct _krb5_kt_ops;
+typedef struct _krb5_kt {
+#if 0
+    krb5_magic magic;
+    const struct _krb5_kt_ops *ops;
+    krb5_pointer data;
+#endif
+} *krb5_keytab;
+
+typedef struct krb5_principal_data {
+#if 0
+    krb5_magic magic;
+    krb5_data realm;
+#endif
+    krb5_data *data;
+    krb5_int32 length;
+#if 0
+    krb5_int32 type;
+#endif
+} krb5_principal_data;
+typedef krb5_principal_data * krb5_principal;
+
+typedef struct _krb5_ticket {
+  krb5_magic magic;
+  
+  krb5_principal server;
+  //krb5_enc_data enc_part;
+
+  //krb5_enc_tkt_part *enc_part2;
+
+} krb5_ticket;
+
+typedef struct krb5_rc_st *krb5_rcache;
+
+typedef struct _krb5_error {
+#if 0
+    krb5_magic magic;
+
+    krb5_timestamp ctime;
+    krb5_int32 cusec;
+#endif
+    krb5_int32 susec;
+    krb5_timestamp stime;
+    krb5_ui_4 error;
+#if 0
+    krb5_principal client;
+#endif
+    krb5_principal server;
+    krb5_data text;
+#if 0
+    krb5_data e_data;
+#endif
+} krb5_error;
+
+typedef const krb5_principal_data *krb5_const_principal;
+
+/* Function prototypes.  */
+
+krb5_error_code krb5_read_message (krb5_context, krb5_pointer, krb5_data *);
+krb5_error_code krb5_write_message (krb5_context, krb5_pointer, krb5_data *);
+int krb5_net_write (krb5_context, int , const char *, int);
+krb5_error_code krb5_auth_con_init
+ (krb5_context,
+  krb5_auth_context *);
+krb5_error_code krb5_auth_con_free
+ (krb5_context,
+  krb5_auth_context);
+krb5_error_code krb5_auth_con_getrcache
+ (krb5_context,
+  krb5_auth_context,
+  krb5_rcache *);
+krb5_error_code krb5_get_server_rcache
+ (krb5_context,
+  const krb5_data *, krb5_rcache *);
+krb5_error_code krb5_auth_con_setrcache
+ (krb5_context,
+  krb5_auth_context,
+  krb5_rcache);
+krb5_error_code krb5_rd_req
+ (krb5_context,
+  krb5_auth_context *,
+  const krb5_data *,
+  krb5_const_principal,
+  krb5_keytab,
+  krb5_flags *,
+  krb5_ticket **);
+krb5_error_code krb5_us_timeofday
+ (krb5_context,
+  krb5_int32 *,
+  krb5_int32 * );
+krb5_error_code krb5_parse_name
+ (krb5_context,
+  const char *,
+  krb5_principal * );
+const char * error_message (errcode_t);
+krb5_error_code krb5_mk_error
+ (krb5_context,
+  const krb5_error *,
+  krb5_data * );
+void krb5_free_principal
+ (krb5_context, krb5_principal );
+krb5_error_code krb5_mk_rep
+ (krb5_context,
+  krb5_auth_context,
+  krb5_data *);
+krb5_error_code krb5_rc_close
+ (krb5_context, krb5_rcache);
+
+/* Macros.  */
+
+#define KRB5_RECVAUTH_SKIP_VERSION	0x0001
+#define KRB5_RECVAUTH_BADAUTHVERS	0x0002
+#define KRB5_SENDAUTH_BADAUTHVERS                (-1765328180L)
+#define KRB5_SENDAUTH_BADAPPLVERS                (-1765328179L)
+#define ERROR_TABLE_BASE_krb5 (-1765328384L)
+#define KRB_ERR_GENERIC		60
+#define	AP_OPTS_MUTUAL_REQUIRED		0x20000000
+
+#define	krb5_princ_size(context, princ) (princ)->length
+#define	krb5_princ_component(context, princ,i)		\
+	    (((i) < krb5_princ_size(context, princ))	\
+	     ? (princ)->data + (i)			\
+	     : NULL)
+#define krb5_xfree(val) free((char *)(val))
+
+/* The code of the source file.  */
+
+static const char sendauth_version[] = "KRB5_SENDAUTH_V1.0";
+
+static krb5_error_code
+recvauth_common(krb5_context context,
+		krb5_auth_context * auth_context,
+		/* IN */
+		krb5_pointer fd,
+		char *appl_version,
+		krb5_principal server,
+		krb5_int32 flags,
+		krb5_keytab keytab,
+		/* OUT */
+		krb5_ticket ** ticket,
+		krb5_data *version)
+{
+    krb5_auth_context	  new_auth_context;
+    krb5_flags		  ap_option;
+    krb5_error_code	  retval, problem;
+    krb5_data		  inbuf;
+    krb5_data		  outbuf;
+    krb5_rcache 	  rcache = 0;
+    krb5_octet		  response;
+    krb5_data		  null_server;
+    int                   need_error_free = 0;
+    int			  local_rcache = 0, local_authcon = 0;
+	
+	/*
+	 * Zero out problem variable.  If problem is set at the end of
+	 * the intial version negotiation section, it means that we
+	 * need to send an error code back to the client application
+	 * and exit.
+	 */
+	problem = 0;
+
+	if (!(flags & KRB5_RECVAUTH_SKIP_VERSION)) {
+	    /*
+	     * First read the sendauth version string and check it.
+	     */
+	    if ((retval = krb5_read_message(context, fd, &inbuf)))
+		return(retval);
+	    if (strcmp(inbuf.data, sendauth_version)) {
+		krb5_xfree(inbuf.data);
+		problem = KRB5_SENDAUTH_BADAUTHVERS;
+	    }
+	    krb5_xfree(inbuf.data);
+	}
+	if (flags & KRB5_RECVAUTH_BADAUTHVERS)
+	    problem = KRB5_SENDAUTH_BADAUTHVERS;
+	
+	/*
+	 * Do the same thing for the application version string.
+	 */
+	if ((retval = krb5_read_message(context, fd, &inbuf)))
+		return(retval);
+	if (appl_version && strcmp(inbuf.data, appl_version)) {
+		krb5_xfree(inbuf.data);
+		if (!problem)
+			problem = KRB5_SENDAUTH_BADAPPLVERS;
+	}
+	if (version && !problem)
+	    *version = inbuf;
+	else
+	    krb5_xfree(inbuf.data);
+	/*
+	 * OK, now check the problem variable.  If it's zero, we're
+	 * fine and we can continue.  Otherwise, we have to signal an
+	 * error to the client side and bail out.
+	 */
+	switch (problem) {
+	case 0:
+		response = 0;
+		break;
+	case KRB5_SENDAUTH_BADAUTHVERS:
+		response = 1;
+		break;
+	case KRB5_SENDAUTH_BADAPPLVERS:
+		response = 2;
+		break;
+	default:
+		/*
+		 * Should never happen!
+		 */
+		response = 255;
+#ifdef SENDAUTH_DEBUG
+		fprintf(stderr, "Programming botch in recvauth!  problem = %d",
+			problem);
+		abort();
+#endif
+		break;
+	}
+	/*
+	 * Now we actually write the response.  If the response is non-zero,
+	 * exit with a return value of problem
+	 */
+	if ((krb5_net_write(context, *((int *)fd), (char *)&response, 1)) < 0) {
+		return(problem); /* We'll return the top-level problem */
+	}
+	if (problem)
+	    return(problem);
+
+    /* We are clear of errors here */
+
+    /*
+     * Now, let's read the AP_REQ message and decode it
+     */
+    if ((retval = krb5_read_message(context, fd, &inbuf)))
+        return retval;
+
+    if (*auth_context == NULL) {
+	problem = krb5_auth_con_init(context, &new_auth_context);
+	*auth_context = new_auth_context;
+	local_authcon = 1;
+    }
+    krb5_auth_con_getrcache(context, *auth_context, &rcache);
+    if ((!problem) && rcache == NULL) {
+        /*
+         * Setup the replay cache.
+         */
+        if (server) {
+            problem = krb5_get_server_rcache(context, 
+			krb5_princ_component(context, server, 0), &rcache);
+        } else {
+    	    null_server.length = 7;
+    	    null_server.data = "default";
+    	    problem = krb5_get_server_rcache(context, &null_server, &rcache);
+        }
+        if (!problem) 
+	    problem = krb5_auth_con_setrcache(context, *auth_context, rcache);
+	local_rcache = 1;
+    }
+    if (!problem) {
+	problem = krb5_rd_req(context, auth_context, &inbuf, server,
+			      keytab, &ap_option, ticket);
+	krb5_xfree(inbuf.data);
+    }
+	
+    /*
+     * If there was a problem, send back a krb5_error message,
+     * preceeded by the length of the krb5_error message.  If
+     * everything's ok, send back 0 for the length.
+     */
+    if (problem) {
+	krb5_error	error;
+	const	char *message;
+
+	memset((char *)&error, 0, sizeof(error));
+	krb5_us_timeofday(context, &error.stime, &error.susec);
+	if(server) 
+		error.server = server;
+	else {
+		/* If this fails - ie. ENOMEM we are hosed
+		   we cannot even send the error if we wanted to... */
+		(void) krb5_parse_name(context, "????", &error.server);
+		need_error_free = 1;
+	}
+
+	error.error = problem - ERROR_TABLE_BASE_krb5;
+	if (error.error > 127)
+		error.error = KRB_ERR_GENERIC;
+	message = error_message(problem);
+	error.text.length  = strlen(message) + 1;
+	if (!(error.text.data = malloc(error.text.length))) {
+	    retval = ENOMEM;
+	    goto cleanup;
+	}
+	strcpy(error.text.data, message);
+	if ((retval = krb5_mk_error(context, &error, &outbuf))) {
+	    free(error.text.data);
+	    goto cleanup;
+	}
+	free(error.text.data);
+	if(need_error_free) 
+		krb5_free_principal(context, error.server);
+
+    } else {
+	outbuf.length = 0;
+	outbuf.data = 0;
+    }
+
+    retval = krb5_write_message(context, fd, &outbuf);
+    if (outbuf.data) {
+	krb5_xfree(outbuf.data);
+    	/* We sent back an error, we need cleanup then return */
+    	retval = problem;
+    	goto cleanup;
+    }
+    if (retval)
+	goto cleanup;
+
+    /* Here lies the mutual authentication stuff... */
+    if ((ap_option & AP_OPTS_MUTUAL_REQUIRED)) {
+	if ((retval = krb5_mk_rep(context, *auth_context, &outbuf))) {
+	    return(retval);
+	}
+	retval = krb5_write_message(context, fd, &outbuf);
+	krb5_xfree(outbuf.data);
+    }
+
+cleanup:;
+    if (retval) {
+	if (local_authcon) {
+	    krb5_auth_con_free(context, *auth_context);
+	} else if (local_rcache && rcache != NULL) {
+	    krb5_rc_close(context, rcache);
+	    krb5_auth_con_setrcache(context, *auth_context, NULL);
+	}
+    }
+    return retval;
+}
+
+krb5_error_code
+krb5_recvauth(krb5_context context, krb5_auth_context *auth_context, krb5_pointer fd, char *appl_version, krb5_principal server, krb5_int32 flags, krb5_keytab keytab, krb5_ticket **ticket)
+{
+    return recvauth_common (context, auth_context, fd, appl_version,
+			    server, flags, keytab, ticket, 0);
+}
+
+krb5_error_code
+krb5_recvauth_version(krb5_context context,
+		      krb5_auth_context *auth_context,
+		      /* IN */
+		      krb5_pointer fd,
+		      krb5_principal server,
+		      krb5_int32 flags,
+		      krb5_keytab keytab,
+		      /* OUT */
+		      krb5_ticket **ticket,
+		      krb5_data *version)
+{
+    return recvauth_common (context, auth_context, fd, 0,
+			    server, flags, keytab, ticket, version);
+}
diff --git a/gcc/analyzer/analyzer.h b/gcc/analyzer/analyzer.h
index 3baba6ce7a1..9b7116a7d16 100644
--- a/gcc/analyzer/analyzer.h
+++ b/gcc/analyzer/analyzer.h
@@ -22,7 +22,8 @@ along with GCC; see the file COPYING3.  If not see
 #define GCC_ANALYZER_ANALYZER_H
 
 #define ITERATED_BINOPS 0
-#define NULL_POINTER_TYPE_CONSOLIDATION 0
+#define NULL_POINTER_TYPE_CONSOLIDATION 1
+#define CONJURED_VALUES_FOR_UNKNOWN_FNCALLS 1
 
 class graphviz_out;
 
@@ -72,6 +73,7 @@ class svalue2;
   class widening_svalue2;
   class compound_svalue2;
   class conjured_svalue2;
+typedef hash_set<const svalue2 *> svalue2_set;
 class region2;
   class frame_region2;
   class function_region2;
diff --git a/gcc/analyzer/constraint-manager2.cc b/gcc/analyzer/constraint-manager2.cc
index b4267fa7dd4..e9f71c14a9a 100644
--- a/gcc/analyzer/constraint-manager2.cc
+++ b/gcc/analyzer/constraint-manager2.cc
@@ -1497,15 +1497,15 @@ constraint_manager2::purge (const purge_criteria2 &p, purge_stats2 *stats)
       equiv_class2 *ec = m_equiv_classes[ec_idx];
 
       int i;
-      const svalue2 *pv;
+      const svalue2 *sval;
       bool delete_ec = false;
-      FOR_EACH_VEC_ELT (ec->m_vars, i, pv)
+      FOR_EACH_VEC_ELT (ec->m_vars, i, sval)
 	{
-	  if (pv == ec->m_cst_sval)
+	  if (sval == ec->m_cst_sval)
 	    continue;
-	  if (p.should_purge_p (pv))
+	  if (p.should_purge_p (sval))
 	    {
-	      if (ec->del (pv))
+	      if (ec->del (sval))
 		if (!ec->m_constant)
 		  delete_ec = true;
 	    }
@@ -1608,6 +1608,32 @@ constraint_manager2::purge (const purge_criteria2 &p, purge_stats2 *stats)
   validate ();
 }
 
+/* FIXME.  */
+
+void
+constraint_manager2::
+on_svalue_leak (const svalue2 *dead_sval,
+		const hash_set<const svalue2 *> &live_svalues)
+{
+#if 0
+  /* If implicitly still live, bail.  */
+  if (dead_sval->live_p (live_svalues))
+    return;
+
+  equiv_class2_id ec_id (-1);
+  if (get_equiv_class2_by_svalue2 (dead_sval, &ec_id))
+    {
+      // TODO:
+#if 0
+      purge_one_svalue p (dead_sval);
+      purge (p, NULL);
+#endif
+    }
+  /* FIXME: maybe rework this to purge all of them at once based on
+     on_liveness_change.  */
+#endif
+}
+
 /* Comparator for use by constraint_manager2::canonicalize.
    Sort a pair of equiv_class2 instances, using the representative
    svalue2_id as a sort key.  */
diff --git a/gcc/analyzer/constraint-manager2.h b/gcc/analyzer/constraint-manager2.h
index dcf209edbd5..3523490c375 100644
--- a/gcc/analyzer/constraint-manager2.h
+++ b/gcc/analyzer/constraint-manager2.h
@@ -270,6 +270,9 @@ public:
 
   void purge (const purge_criteria2 &p, purge_stats2 *stats);
 
+  void on_svalue_leak (const svalue2 *dead_sval,
+		       const hash_set<const svalue2 *> &live_svalues);
+
   void canonicalize ();
 
   static void merge (const constraint_manager2 &cm_a,
diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc
index 6a357223efe..16be6b64ca6 100644
--- a/gcc/analyzer/engine.cc
+++ b/gcc/analyzer/engine.cc
@@ -211,6 +211,17 @@ impl_region_model2_context::on_svalue2_leak (const svalue2 *sval)
 #endif
 }
 
+void
+impl_region_model2_context::
+on_liveness_change (const hash_set<const svalue2 *> &live_svalues,
+		    const region_model2 *model)
+{
+  int sm_idx;
+  sm_state_map2 *smap;
+  FOR_EACH_VEC_ELT (m_new_state->m_checker_states2, sm_idx, smap)
+    smap->on_liveness_change (live_svalues, model, this);
+}
+
 void
 impl_region_model2_context::on_unknown_change (const svalue2 *sval,
 					       bool is_mutable)
diff --git a/gcc/analyzer/exploded-graph.h b/gcc/analyzer/exploded-graph.h
index 744c2cfd8aa..bfdcee3af87 100644
--- a/gcc/analyzer/exploded-graph.h
+++ b/gcc/analyzer/exploded-graph.h
@@ -118,6 +118,8 @@ class impl_region_model2_context : public region_model2_context
 
   void warn (pending_diagnostic *d) FINAL OVERRIDE;
   void on_svalue2_leak (const svalue2 *) OVERRIDE;
+  void on_liveness_change (const hash_set<const svalue2 *> &live_svalues,
+			   const region_model2 *model) FINAL OVERRIDE;
   logger *get_logger () FINAL OVERRIDE
   {
     return m_logger.get_logger ();
diff --git a/gcc/analyzer/program-state.cc b/gcc/analyzer/program-state.cc
index b4a10cf5b66..d034b294b6f 100644
--- a/gcc/analyzer/program-state.cc
+++ b/gcc/analyzer/program-state.cc
@@ -1014,42 +1014,37 @@ sm_state_map2::on_svalue2_leak (const svalue2 *sval,
 	ctxt->on_state_leak (m_sm, m_sm_idx, sval, state);
       m_map.remove (sval);
     }
+}
+
+/* FIXME.  */
+
+void
+sm_state_map2::
+on_liveness_change (const svalue2_set &live_svalues,
+		    const region_model2 *model,
+		    impl_region_model2_context *ctxt)
+{
+  hash_set <const svalue2 *> svals_to_unset;
 
-  // TODO
-#if 0
-  /* TODO: ideally remove the slot directly; for now
-     do it in two stages.  */
-  auto_vec<svalue_id> to_remove;
   for (map_t::iterator iter = m_map.begin ();
        iter != m_map.end ();
        ++iter)
     {
-      svalue_id dst_sid ((*iter).first);
-      if (dst_sid.as_int () >= first_unused_sid.as_int ())
+      const svalue2 *iter_sval = (*iter).first;
+      if (!iter_sval->live_p (live_svalues, model))
 	{
-	  /* Complain about leaks here.  */
-	  entry_t e = (*iter).second;
+	  svals_to_unset.add (iter_sval);
 
-	  if (!sm.can_purge_p (e.m_state))
-	    ctxt->on_state_leak (sm, sm_idx, dst_sid, first_unused_sid,
-				 map, e.m_state);
-
-	  to_remove.safe_push (dst_sid);
-	}
-      else if ((*iter).second.m_origin.as_int () >= first_unused_sid.as_int ())
-	{
-	  /* If the origin svalue is being purged, then reset it to null.  */
-	  (*iter).second.m_origin = svalue_id::null ();
+	  entry_t e = (*iter).second;
+	  // FIXME: should we report these as leaks?
+	  if (!m_sm.can_purge_p (e.m_state))
+	    ctxt->on_state_leak (m_sm, m_sm_idx, iter_sval, e.m_state);
 	}
     }
 
-  int i;
-  svalue_id *dst_sid;
-  FOR_EACH_VEC_ELT (to_remove, i, dst_sid)
-    m_map.remove (*dst_sid);
-
-  return to_remove.length ();
-#endif
+  for (hash_set <const svalue2 *>::iterator iter = svals_to_unset.begin ();
+       iter != svals_to_unset.end (); ++iter)
+    m_map.remove (*iter);
 }
 
 #if 0
@@ -2097,11 +2092,11 @@ program_state::validate (const extrinsic_state &ext_state) const
 
 static void
 log_set_of_svalue2s (logger *logger, const char *name,
-		     const hash_set<const svalue2 *> &set)
+		     const svalue2_set &set)
 {
   logger->log (name);
   logger->inc_indent ();
-  for (hash_set<const svalue2 *>::iterator iter = set.begin ();
+  for (svalue2_set::iterator iter = set.begin ();
        iter != set.end (); ++iter)
     {
       logger->start_log_line ();
@@ -2147,8 +2142,8 @@ program_state::detect_leaks2 (const program_state &src_state,
     }
 
   /* Get svalues reachable from each of src_state and dst_state.  */
-  hash_set<const svalue2 *> src_svalues;
-  hash_set<const svalue2 *> dest_svalues;
+  svalue2_set src_svalues;
+  svalue2_set dest_svalues;
   src_state.m_region_model2->get_reachable_svalues (&src_svalues, NULL);
   dest_state.m_region_model2->get_reachable_svalues (&dest_svalues,
 						     extra_sval);
@@ -2160,12 +2155,13 @@ program_state::detect_leaks2 (const program_state &src_state,
 			   dest_svalues);
     }
 
-  for (hash_set<const svalue2 *>::iterator iter = src_svalues.begin ();
+  for (svalue2_set::iterator iter = src_svalues.begin ();
        iter != src_svalues.end (); ++iter)
     {
       const svalue2 *sval = (*iter);
-      if (!dest_svalues.contains (sval))
+      if (!sval->live_p (dest_svalues, dest_state.m_region_model2))
 	{
+#if 0
 	  /* If src_svalues contains a use of INIT_VAL(REGION) and
 	     dest_svalues doesn't, we don't want to complain about a leak
 	     if the region still implicitly has that value.  */
@@ -2184,6 +2180,21 @@ program_state::detect_leaks2 (const program_state &src_state,
 		    continue;
 		}
 	    }
+
+#if CONJURED_VALUES_FOR_UNKNOWN_FNCALLS
+	  /* If we have explicit use of a sub_svalue going away, the
+	     value might still be implicitly reachable, and we don't
+	     want to treat this case as a leak.  */
+	  if (const sub_svalue2 *sub_sval
+		= sval->dyn_cast_sub_svalue2 ())
+	    {
+	      if (dest_svalues.contains (sub_sval->get_parent ()))
+		continue;
+	      // TODO: should we recurse?
+	    }
+#endif
+#endif
+
 #if 0
 	  pretty_printer pp1, pp2, pp3;
 	  pp_format_decoder (&pp1) = default_tree_printer;
@@ -2198,8 +2209,13 @@ program_state::detect_leaks2 (const program_state &src_state,
 		 pp_formatted_text (&pp3));
 #endif
 	  ctxt->on_svalue2_leak (sval);
+
+	  /* Purge sval from constraints.  */
+	  dest_state.m_region_model2->get_constraints2 ()->on_svalue_leak
+	    (sval, dest_svalues);
 	}
     }
+  ctxt->on_liveness_change (dest_svalues, dest_state.m_region_model2);
 }
 
 /* Dump this sm_change to PP.  */
diff --git a/gcc/analyzer/program-state.h b/gcc/analyzer/program-state.h
index fdf330155c9..f6edf8de65b 100644
--- a/gcc/analyzer/program-state.h
+++ b/gcc/analyzer/program-state.h
@@ -303,6 +303,10 @@ public:
 
   void on_svalue2_leak (const svalue2 *sval,
 			impl_region_model2_context *ctxt);
+  void on_liveness_change (const hash_set<const svalue2 *> &live_svalues,
+			   const region_model2 *model,
+			   impl_region_model2_context *ctxt);
+
 #if 0
   void on_inherited_svalue (svalue_id parent_sid,
 			    svalue_id child_sid);
diff --git a/gcc/analyzer/region-model2.cc b/gcc/analyzer/region-model2.cc
index 5156ba1b146..31d3421eb78 100644
--- a/gcc/analyzer/region-model2.cc
+++ b/gcc/analyzer/region-model2.cc
@@ -360,6 +360,89 @@ svalue2::can_merge_p (const svalue2 *other,
   return mgr->get_or_create_unknown_svalue2 (get_type ());
 }
 
+/* FIXME.  */
+
+bool
+svalue2::live_p (const hash_set<const svalue2 *> &live_svalues,
+		 const region_model2 *model) const
+{
+  /* Determine if SVAL is explicitly live.  */
+  if (const_cast<hash_set<const svalue2 *> &> (live_svalues).contains (this))
+    return true;
+
+  /* Otherwise, determine if SVAL is implicitly live due to being made of
+     other live svalues.  */
+  return implicitly_live_p (live_svalues, model);
+}
+
+/* Base implementation of svalue2::implicitly_live_p.  */
+
+bool
+svalue2::implicitly_live_p (const svalue2_set &, const region_model2 *) const
+{
+  return false;
+}
+
+/* FIXME: group these here, or move them to their subclass groupings.  */
+
+/* Constants are implicitly live.  */
+
+bool
+constant_svalue2::implicitly_live_p (const svalue2_set &,
+				     const region_model2 *) const
+{
+  return true;
+}
+
+bool
+initial_svalue2::implicitly_live_p (const svalue2_set &live_svalues,
+				    const region_model2 *model) const
+{
+  /* This svalue2 may be implicitly live if the region still implicitly
+     has its initial value and is reachable.  */
+
+  /* It must be a region that exists; we don't want to consider
+     INIT_VAL(R) as still being implicitly reachable if R is in
+     a popped stack frame.  */
+  if (model->region_exists_p (m_reg))
+    {
+      const svalue2 *reg_sval = model->get_store_value (m_reg);
+      if (reg_sval == this)
+	return true;
+    }
+
+  return false;
+}
+
+bool
+unaryop_svalue2::implicitly_live_p (const svalue2_set &live_svalues,
+				    const region_model2 *model) const
+{
+  return get_arg ()->live_p (live_svalues, model);
+}
+
+bool
+binop_svalue2::implicitly_live_p (const svalue2_set &live_svalues,
+				  const region_model2 *model) const
+{
+  return (get_arg0 ()->live_p (live_svalues, model)
+	  && get_arg1 ()->live_p (live_svalues, model));
+}
+
+bool
+sub_svalue2::implicitly_live_p (const svalue2_set &live_svalues,
+				const region_model2 *model) const
+{
+  return get_parent ()->live_p (live_svalues, model);
+}
+
+bool
+unmergeable_svalue2::implicitly_live_p (const svalue2_set &live_svalues,
+					const region_model2 *model) const
+{
+  return get_arg ()->live_p (live_svalues, model);
+}
+
 /* class region_svalue2 : public svalue2.  */
 
 /* FIXME.  */
@@ -5545,7 +5628,7 @@ region_model2::handle_unrecognized_call (const gcall *call,
 
   /* Update bindings for all clusters that have escaped, whether above,
      or previously.  */
-  m_store.on_unknown_fncall ();
+  m_store.on_unknown_fncall (call, m_mgr->get_store2_manager ());
 
 #if 0
   /* Purge sm-state for any remaining svalue2s that point to region2s that
@@ -6170,7 +6253,7 @@ region_model2::get_rvalue (tree expr,
 /* FIXME.  */
 
 const svalue2 *
-region_model2::get_store_value (const region2 *reg)
+region_model2::get_store_value (const region2 *reg) const
 {
   const svalue2 *sval
     = m_store.get_any_binding (m_mgr->get_store2_manager (), reg);
@@ -7041,6 +7124,24 @@ region_model2::get_representative_path_var (const svalue2 *sval,
 			 pv.m_stack_depth);
     }
 
+#if CONJURED_VALUES_FOR_UNKNOWN_FNCALLS
+  /* If we have a sub_svalue, look for ways to represent the parent.  */
+  if (const sub_svalue2 *sub_sval = sval->dyn_cast_sub_svalue2 ())
+    {
+      const svalue2 *parent_sval = sub_sval->get_parent ();
+      const region2 *subreg = sub_sval->get_subregion ();
+      if (path_var parent_pv
+	    = get_representative_path_var (parent_sval, visited))
+	if (const field_region2 *field_reg = subreg->dyn_cast_field_region2 ())
+	  return path_var (build3 (COMPONENT_REF,
+				   sval->get_type (),
+				   parent_pv.m_tree,
+				   field_reg->get_field (),
+				   NULL_TREE),
+			   parent_pv.m_stack_depth);
+    }
+#endif
+
   if (pvs.length () < 1)
     return path_var (NULL_TREE, 0);
 
diff --git a/gcc/analyzer/region-model2.h b/gcc/analyzer/region-model2.h
index f0798cb7470..c066cb507ae 100644
--- a/gcc/analyzer/region-model2.h
+++ b/gcc/analyzer/region-model2.h
@@ -274,6 +274,11 @@ public:
 
   virtual void accept (visitor *v) const  = 0;
 
+  bool live_p (const svalue2_set &live_svalues,
+	       const region_model2 *model) const;
+  virtual bool implicitly_live_p (const svalue2_set &live_svalues,
+				  const region_model2 *model) const;
+
  protected:
   svalue2 (complexity c, tree type)
   : m_complexity (c), m_type (type)
@@ -362,6 +367,9 @@ public:
 
   void accept (visitor *v) const FINAL OVERRIDE;
 
+  bool implicitly_live_p (const svalue2_set &,
+			  const region_model2 *) const FINAL OVERRIDE;
+
   tree get_constant () const { return m_cst_expr; }
 
 #if 0
@@ -604,6 +612,9 @@ public:
 
   void accept (visitor *v) const FINAL OVERRIDE;
 
+  bool implicitly_live_p (const svalue2_set &,
+			  const region_model2 *) const FINAL OVERRIDE;
+
   const region2 * get_region () const { return m_reg; }
 
 #if 0
@@ -680,6 +691,9 @@ public:
 
   void accept (visitor *v) const FINAL OVERRIDE;
 
+  bool implicitly_live_p (const svalue2_set &,
+			  const region_model2 *) const FINAL OVERRIDE;
+
 #if 0
   void dump_dot_to_pp (const region_model2 &model,
 		       pretty_printer *pp) const
@@ -772,6 +786,9 @@ public:
 
   void accept (visitor *v) const FINAL OVERRIDE;
 
+  bool implicitly_live_p (const svalue2_set &,
+			  const region_model2 *) const FINAL OVERRIDE;
+
   enum tree_code get_op () const { return m_op; }
   const svalue2 *get_arg0 () const { return m_arg0; }
   const svalue2 *get_arg1 () const { return m_arg1; }
@@ -862,6 +879,8 @@ public:
     return this;
   }
   void accept (visitor *v) const FINAL OVERRIDE;
+  bool implicitly_live_p (const svalue2_set &,
+			  const region_model2 *) const FINAL OVERRIDE;
 
 #if 0
   void dump_dot_to_pp (const region_model2 &model,
@@ -884,7 +903,7 @@ public:
 template <>
 template <>
 inline bool
-is_a_helper <sub_svalue2 *>::test (svalue2 *sval)
+is_a_helper <const sub_svalue2 *>::test (const svalue2 *sval)
 {
   return sval->get_kind () == svalue2::SK_SUB;
 }
@@ -928,6 +947,8 @@ public:
   const unmergeable_svalue2 *
   dyn_cast_unmergeable_svalue2 () const FINAL OVERRIDE { return this; }
   void accept (visitor *v) const FINAL OVERRIDE;
+  bool implicitly_live_p (const svalue2_set &,
+			  const region_model2 *) const FINAL OVERRIDE;
 
 #if 0
   void dump_dot_to_pp (const region_model2 &model,
@@ -2575,7 +2596,7 @@ class region_model2
 #endif
   void handle_unrecognized_call (const gcall *call,
 				 region_model2_context *ctxt);
-  void get_reachable_svalues (hash_set<const svalue2 *> *out,
+  void get_reachable_svalues (svalue2_set *out,
 			      const svalue2 *extra_sval);
   bool on_greturn (const greturn *stmt,
 		   region_model2_context *ctxt);
@@ -2733,7 +2754,7 @@ class region_model2
   static void append_ssa_names_cb (const region2 *base_reg,
 				   struct append_ssa_names_cb_data *data);
 
-  const svalue2 *get_store_value (const region2 *reg);
+  const svalue2 *get_store_value (const region2 *reg) const;
 
   bool region_exists_p (const region2 *reg) const;
 
@@ -2840,6 +2861,13 @@ class region_model2_context
      the client needs to figure out the old IDs.  */
   virtual void on_svalue2_leak (const svalue2 *sval) = 0;
 
+  /* Hook for clients to be notified when the set of explicitly live
+     svalue2s changes, so that they can purge state relating to dead
+     svalues.  */
+  virtual void
+  on_liveness_change (const svalue2_set &live_svalues,
+		      const region_model2 *model) = 0;
+
   virtual logger *get_logger () = 0;
 
 #if 0
@@ -2888,6 +2916,8 @@ class noop_region_model2_context : public region_model2_context
 public:
   void warn (pending_diagnostic *) OVERRIDE {}
   void on_svalue2_leak (const svalue2 *) OVERRIDE {}
+  void on_liveness_change (const svalue2_set &,
+			   const region_model2 *) OVERRIDE {}
   logger *get_logger () OVERRIDE { return NULL; }
 #if 0
   void on_inherited_svalue2 (svalue2_id parent_sid ATTRIBUTE_UNUSED,
diff --git a/gcc/analyzer/store2.cc b/gcc/analyzer/store2.cc
index 16eaa25f7af..e19797a3597 100644
--- a/gcc/analyzer/store2.cc
+++ b/gcc/analyzer/store2.cc
@@ -909,12 +909,20 @@ binding_cluster2::mark_as_escaped ()
    initial_svalue2.  */
 
 void
-binding_cluster2::on_unknown_fncall ()
+binding_cluster2::on_unknown_fncall (const gcall *call,
+				     store2_manager *mgr)
 {
   if (m_escaped)
     {
       m_map.empty ();
-      // TODO: need to add an "unknown" default value
+
+#if CONJURED_VALUES_FOR_UNKNOWN_FNCALLS
+      // TODO: bind it to a new "conjured" value using CALL
+      const svalue2 *sval
+	= mgr->get_svalue2_manager ()->get_or_create_conjured_svalue2
+	    (m_base_region->get_type (), call, m_base_region);
+      bind (mgr, m_base_region, sval, BK_direct);
+#endif
 
       m_touched = true;
     }
@@ -1331,10 +1339,11 @@ store2::summarize_to_pp (pretty_printer *pp, bool simple) const
 /* FIXME.  */
 
 const svalue2 *
-store2::get_any_binding (store2_manager *mgr, const region2 *reg)
+store2::get_any_binding (store2_manager *mgr, const region2 *reg) const
 {
   const region2 *base_reg = reg->get_base_region ();
-  binding_cluster2 **cluster_slot = m_cluster_map.get (base_reg);
+  binding_cluster2 **cluster_slot
+    = const_cast <cluster_map_t &> (m_cluster_map).get (base_reg);
   if (!cluster_slot)
     return NULL;
   return (*cluster_slot)->get_any_binding (mgr, reg);
@@ -1657,13 +1666,13 @@ store2::mark_as_escaped (const region2 *base_reg)
    (either in this fncall, or in a prior one).  */
 
 void
-store2::on_unknown_fncall ()
+store2::on_unknown_fncall (const gcall *call, store2_manager *mgr)
 {
   m_called_unknown_fn = true;
 
   for (cluster_map_t::iterator iter = m_cluster_map.begin ();
        iter != m_cluster_map.end (); ++iter)
-    (*iter).second->on_unknown_fncall ();
+    (*iter).second->on_unknown_fncall (call, mgr);
 }
 
 /* Return true if a non-const pointer to BASE_REG (or something within it)
diff --git a/gcc/analyzer/store2.h b/gcc/analyzer/store2.h
index aa7f8011a66..cb39a102d51 100644
--- a/gcc/analyzer/store2.h
+++ b/gcc/analyzer/store2.h
@@ -388,7 +388,7 @@ public:
 				 store2_manager *mgr);
 
   void mark_as_escaped ();
-  void on_unknown_fncall ();
+  void on_unknown_fncall (const gcall *call, store2_manager *mgr);
 
   bool escaped_p () const { return m_escaped; }
   bool touched_p () const { return m_touched; }
@@ -478,7 +478,7 @@ public:
 
   const svalue2 *get_direct_binding (store2_manager *mgr, const region2 *reg);
   const svalue2 *get_default_binding (store2_manager *mgr, const region2 *reg);
-  const svalue2 *get_any_binding (store2_manager *mgr, const region2 *reg);
+  const svalue2 *get_any_binding (store2_manager *mgr, const region2 *reg) const;
 
   bool called_unknown_fn_p () const { return m_called_unknown_fn; }
 
@@ -508,7 +508,7 @@ public:
 			   model2_merger *merger);
 
   void mark_as_escaped (const region2 *base_reg);
-  void on_unknown_fncall ();
+  void on_unknown_fncall (const gcall *call, store2_manager *mgr);
   bool escaped_p (const region2 *reg) const;
 
   void get_representative_path_vars (const region_model2 *model,
diff --git a/gcc/testsuite/gcc.dg/analyzer/CVE-2005-1689-minimal.c b/gcc/testsuite/gcc.dg/analyzer/CVE-2005-1689-minimal.c
index f84d530c392..f25019161c6 100644
--- a/gcc/testsuite/gcc.dg/analyzer/CVE-2005-1689-minimal.c
+++ b/gcc/testsuite/gcc.dg/analyzer/CVE-2005-1689-minimal.c
@@ -1,5 +1,5 @@
 #include <stdlib.h>
-
+#include "analyzer-decls.h"
 typedef struct _krb5_data {
   char *data;
 } krb5_data;
@@ -38,3 +38,53 @@ test_4 (krb5_data inbuf)
   free(inbuf.data); /* { dg-message "first 'free' here" } */
   free(inbuf.data); /* { dg-warning "double-'free' of 'inbuf.data'" } */
 }
+
+void
+test_5 (krb5_data inbuf)
+{
+  unknown_fn (&inbuf);
+  free(inbuf.data); /* { dg-message "first 'free' here" } */
+  free(inbuf.data); /* { dg-warning "double-'free' of 'inbuf.data'" "inbuf.data" { xfail *-*-* } } */
+  /* { dg-bogus "double-'free' of 'inbuf'" "inbuf" { xfail *-*-* } .-1 } */
+}
+
+typedef struct _padded_krb5_data {
+  int pad;
+  char *data;
+} padded_krb5_data;
+
+void
+test_6 (padded_krb5_data inbuf)
+{
+  unknown_fn (&inbuf.data);
+  free((char *)inbuf.data); /* { dg-message "first 'free' here" } */
+  free((char *)inbuf.data); /* { dg-warning "double-'free' of 'inbuf.data'" "inbuf.data" } */
+}
+
+void
+test_7 (padded_krb5_data inbuf)
+{
+  unknown_fn (&inbuf.data);
+  free((char *)inbuf.data);
+
+  unknown_fn (&inbuf.data);
+  free((char *)inbuf.data);  
+}
+
+void
+test_8 (padded_krb5_data inbuf, int flag)
+{
+  if (flag)
+    {
+      unknown_fn (&inbuf.data);
+      free((char *)inbuf.data);
+    }
+  /* Should have two enodes, one for the explicit "freed" state, and one
+     for the implicit "start" state.  */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 processed enodes" } */
+
+  unknown_fn (&inbuf.data);
+
+  /* Should have just one enode, for the implicit "start" state.  */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-CVE-2005-1689-krb5-recvauth.c b/gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-CVE-2005-1689-krb5-recvauth.c
index f6053b432a5..da45c0f43f3 100644
--- a/gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-CVE-2005-1689-krb5-recvauth.c
+++ b/gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-CVE-2005-1689-krb5-recvauth.c
@@ -10462,7 +10462,7 @@ recvauth_common(krb5_context context,
   free((char *)(inbuf.data));
   problem = (-1765328180L);
      }
-     free((char *)(inbuf.data));
+     free((char *)(inbuf.data)); /* { dg-message "double-'free' of 'inbuf.data'" } */
  }
  if (flags & 0x0002)
      problem = (-1765328180L);
@@ -10639,7 +10639,8 @@ recvauth_common(krb5_context context,
     if (!problem) {
  problem = krb5_rd_req(context, auth_context, &inbuf, server,
          keytab, &ap_option, ticket);
- free((char *)(inbuf.data)); /* { dg-message "double-'free' of 'inbuf.data'" } */
+ free((char *)(inbuf.data)); /* { dg-message "double-'free' of 'inbuf.data'" "" { xfail *-*-* } } */
+      /* XFAIL is PR analyzer/96374.  */
     }
 
 
@@ -10746,108 +10747,40 @@ krb5_recvauth_version(krb5_context context,
 }
 
 /* { dg-begin-multiline-output "" }
-  'krb5_recvauth': events 1-2
-         |
-         |   NN | krb5_recvauth(krb5_context context, krb5_auth_context *auth_context, krb5_pointer fd, char *appl_version, krb5_principal server, krb5_int32 flags, krb5_keytab keytab, krb5_ticket **ticket)
-         |      | ^~~~~~~~~~~~~
-         |      | |
-         |      | (1) entry to 'krb5_recvauth'
-         |   NN | {
-         |   NN |     return recvauth_common (context, auth_context, fd, appl_version,
-         |      |            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-         |      |            |
-         |      |            (2) calling 'recvauth_common' from 'krb5_recvauth'
-         |   NN |        server, flags, keytab, ticket, 0);
-         |      |        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-         |
-         +--> 'recvauth_common': events 3-20
-                      |
-                      |   NN | recvauth_common(krb5_context context,
-                      |      | ^~~~~~~~~~~~~~~
-                      |      | |
-                      |      | (3) entry to 'recvauth_common'
-                      |......
-                      |   NN |  if (!(flags & 0x0001)) {
-                      |      |     ~
-                      |      |     |
-                      |      |     (4) following 'false' branch
-                      |......
-                      |   NN |  if (flags & 0x0002)
-                      |      |     ~
-                      |      |     |
-                      |      |     (5) following 'false' branch
-                      |......
-                      |   NN |  if ((retval = krb5_read_message(context, fd, &inbuf)))
-                      |      |     ~
-                      |      |     |
-                      |      |     (6) following 'false' branch
-                      |   NN |   return(retval);
-                      |   NN |  if (appl_version &&
-                      |      |     ~
-                      |      |     |
-                      |      |     (7) following 'false' branch
-                      |......
-                      |   NN |  if (version && !problem)
-                      |      |     ~
-                      |      |     |
-                      |      |     (8) following 'false' branch
-                      |......
-                      |   NN |      free((char *)(inbuf.data));
-                      |      |      ~~~~~~~~~~~~~~~~~~~~~~~~~~
-                      |      |      |
-                      |      |      (9) first 'free' here
-                      |......
-                      |   NN |  switch (problem) {
-                      |      |  ~~~~~~
-                      |      |  |
-                      |      |  (10) following 'case 0:' branch
-                      |......
-                      |   NN |  if ((krb5_net_write(context, *((int *)fd), (char *)&response, 1)) < 0) {
-                      |      |     ~
-                      |      |     |
-                      |      |     (11) following 'false' branch
-                      |......
-                      |   NN |  if (problem)
-                      |      |     ~
-                      |      |     |
-                      |      |     (12) following 'false' branch
-                      |......
-                      |   NN |     if ((retval = krb5_read_message(context, fd, &inbuf)))
-                      |      |        ~
-                      |      |        |
-                      |      |        (13) following 'false' branch
-                      |......
-                      |   NN |     if (*auth_context ==
-                      |      |        ~
-                      |      |        |
-                      |      |        (14) following 'false' branch
-                      |......
-                      |   NN |     if ((!problem) && rcache ==
-                      |      |        ~~~~~~~~~~~~~~~~~~~~~~~~
-                      |      |        |           |
-                      |      |        |           (16) following 'true' branch
-                      |      |        (15) following 'true' branch
-                      |   NN |                                ((void *)0)
-                      |      |                                ~~~~~~~~~~~
-                      |......
-                      |   NN |         if (server) {
-                      |      |            ~
-                      |      |            |
-                      |      |            (17) following 'false' branch
-                      |......
-                      |   NN |         if (!problem)
-                      |      |            ~
-                      |      |            |
-                      |      |            (18) following 'true' branch
-                      |......
-                      |   NN |     if (!problem) {
-                      |      |        ~
-                      |      |        |
-                      |      |        (19) following 'true' branch
-                      |......
-                      |   NN |  free((char *)(inbuf.data));
-                      |      |  ~~~~~~~~~~~~~~~~~~~~~~~~~~
-                      |      |  |
-                      |      |  (20) second 'free' here; first 'free' was at (9)
-                      |
+   NN |      free((char *)(inbuf.data));
+      |      ^~~~~~~~~~~~~~~~~~~~~~~~~~
+  'recvauth_common': events 1-8
+    |
+    |   NN |  if (!(flags & 0x0001)) {
+    |      |     ^
+    |      |     |
+    |      |     (1) following 'true' branch...
+    |......
+    |   NN |      if ((retval = krb5_read_message(context, fd, &inbuf)))
+    |      |         ~          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+    |      |         |          |
+    |      |         |          (2) ...to here
+    |      |         (3) following 'false' branch (when 'retval == 0')...
+    |   NN |   return(retval);
+    |   NN |      if (
+    |      |         ~
+    |      |         |
+    |      |         (5) following 'true' branch...
+    |......
+    |   NN |         inbuf.data
+    |      |         ~~~~~~~~~~
+    |      |              |
+    |      |              (4) ...to here
+    |......
+    |   NN |   free((char *)(inbuf.data));
+    |      |   ~~~~~~~~~~~~~~~~~~~~~~~~~~
+    |      |   |    |
+    |      |   |    (6) ...to here
+    |      |   (7) first 'free' here
+    |......
+    |   NN |      free((char *)(inbuf.data));
+    |      |      ~~~~~~~~~~~~~~~~~~~~~~~~~~
+    |      |      |
+    |      |      (8) second 'free' here; first 'free' was at (7)
+    |
   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/feasibility-1.c b/gcc/testsuite/gcc.dg/analyzer/feasibility-1.c
index ea60b5a1148..f2a8a4cb0be 100644
--- a/gcc/testsuite/gcc.dg/analyzer/feasibility-1.c
+++ b/gcc/testsuite/gcc.dg/analyzer/feasibility-1.c
@@ -39,5 +39,24 @@ void test_5 (void)
   for (int i = 0; i < 1024; i++)
     {
     }
-  __analyzer_dump_path (); /* { dg-message "path" } */  
+  __analyzer_dump_path (); /* { dg-message "path" } */
+}
+
+/* Reproducer for an issue seen with CVE-2005-1689 (PR analyzer/96374): if we
+   take the shortest path and update state and check feasibility per-edge, we
+   can erroneously reject valid diagnostics.  */
+
+int test_6 (int a, int b)
+{
+  int problem = 0;
+  if (a)
+    problem = 1;
+  if (b)
+    {
+      if (!problem)
+	problem = 2;
+      __analyzer_dump_path (); /* { dg-message "path" "" { xfail *-*-* } } */
+      /* XFAIL is PR analyzer/96374.  */
+    }
+  return problem;
 }
diff --git a/gcc/testsuite/gcc.dg/analyzer/unknown-fns-2.c b/gcc/testsuite/gcc.dg/analyzer/unknown-fns-2.c
index be722c2215b..64960f02e6a 100644
--- a/gcc/testsuite/gcc.dg/analyzer/unknown-fns-2.c
+++ b/gcc/testsuite/gcc.dg/analyzer/unknown-fns-2.c
@@ -1,9 +1,9 @@
 /* Tests for data model handling of unknown fns.  */
 
-#include <stdlib.h>
-#include <string.h>
-#include <stdio.h>
-#include <alloca.h>
+//#include <stdlib.h> // TODO
+//#include <string.h>
+//#include <stdio.h>
+#include <stddef.h>
 #include "analyzer-decls.h"
 
 void unknown_fn (void *);
diff --git a/gcc/testsuite/gcc.dg/analyzer/unknown-fns-3.c b/gcc/testsuite/gcc.dg/analyzer/unknown-fns-3.c
new file mode 100644
index 00000000000..095df5e77a7
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/unknown-fns-3.c
@@ -0,0 +1,67 @@
+/* Tests for handling constraints on results of unknown fns.  */
+
+#include <stddef.h>
+#include "analyzer-decls.h"
+
+void unknown_fn (void *);
+
+void test_1 (void)
+{
+  int i;
+  unknown_fn (&i);
+  if (i)
+    __analyzer_eval (i); /* { dg-warning "TRUE" } */
+  else
+    __analyzer_eval (i); /* { dg-warning "FALSE" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+}
+
+struct foo
+{
+  int i;
+  int j;
+};
+
+void test_2 (void)
+{
+  struct foo f;
+  unknown_fn (&f);
+  if (f.j)
+    __analyzer_eval (f.j); /* { dg-warning "TRUE" } */
+  else
+    __analyzer_eval (f.j); /* { dg-warning "FALSE" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+}
+
+void test_3 (int flag)
+{
+  int i;
+  unknown_fn (&i);
+  if (i)
+    {
+      __analyzer_eval (i); /* { dg-warning "TRUE" } */
+      if (flag)
+	__analyzer_eval (flag); /* { dg-warning "TRUE" } */
+      else
+	__analyzer_eval (flag); /* { dg-warning "FALSE" } */
+    }
+  else
+    __analyzer_eval (i); /* { dg-warning "FALSE" } */
+  if (flag)
+    __analyzer_eval (flag); /* { dg-warning "TRUE" } */
+  else
+    __analyzer_eval (flag); /* { dg-warning "FALSE" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+}
+
+void test_4 (int y)
+{
+  int x;
+  unknown_fn (&x);
+  if (x)
+    {
+      __analyzer_eval (x); /* { dg-warning "TRUE" } */
+      x = 0;
+    }
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+}
diff --git a/gcc/testsuite/lib/multiline.exp b/gcc/testsuite/lib/multiline.exp
index 627ad68817f..46598004676 100644
--- a/gcc/testsuite/lib/multiline.exp
+++ b/gcc/testsuite/lib/multiline.exp
@@ -256,7 +256,7 @@ proc maybe-handle-nn-line-numbers { text } {
     #  "   25 |"
     # and convert them to:
     #  "   NN |"
-    set rexp {([ ]+[0-9]+ \|)}
+    set rexp {([ ]*[0-9]+ \|)}
     set count_b [regsub -all $rexp $text "   NN |" text]
     verbose "maybe-handle-nn-line-numbers: count_b: $count_b" 4
 
-- 
2.26.2

