[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#1031948: marked as done (bullseye-pu: package libgit2/1.1.0+dfsg.1-4+deb11u1)



Your message dated Sat, 29 Apr 2023 10:54:14 +0100
with message-id <502b8fb37ece620c9723446611a9287974ba5a0c.camel@adam-barratt.org.uk>
and subject line Closing p-u requests for fixes included in 11.7
has caused the Debian Bug report #1031948,
regarding bullseye-pu: package libgit2/1.1.0+dfsg.1-4+deb11u1
to be marked as done.

This means that you claim that the problem has been dealt with.
If this is not the case it is now your responsibility to reopen the
Bug report if necessary, and/or fix the problem forthwith.

(NB: If you are a system administrator and have no idea what this
message is talking about, this may indicate a serious mail system
misconfiguration somewhere. Please contact owner@bugs.debian.org
immediately.)


-- 
1031948: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1031948
Debian Bug Tracking System
Contact owner@bugs.debian.org with problems
--- Begin Message ---
Package: release.debian.org
Severity: normal
Tags: bullseye
User: release.debian.org@packages.debian.org
Usertags: pu
X-Debbugs-Cc: libgit2@packages.debian.org
Control: affects -1 + src:libgit2

After fixing CVE-2023-22742 for LTS and ELTS, I'd like to see
this CVE also fixed in stable, for consistency.

The CVE is an inproper ssh certificate validation vulnerabilty,
which allows man-in-the-middle attacks.

[ Tests ]
I've manually tested the patch (checking if git poperations still
work using the ssh transport.

Autopkgtests are green too.

[ Risks ]
Manual tests and test suite works ok.

[ Checklist ]
  [X] *all* changes are documented in the d/changelog
  [X] I reviewed all changes and I approve them
  [X] attach debdiff against the package in (old)stable
  [X] the issue is verified as fixed in unstable

[ Changes ]
The change is a backport of the upstream fix:
https://github.com/libgit2/libgit2/commit/cd6f679af401eda1f172402006ef8265f8bd58ea
diff -Nru libgit2-1.1.0+dfsg.1/debian/changelog libgit2-1.1.0+dfsg.1/debian/changelog
--- libgit2-1.1.0+dfsg.1/debian/changelog	2021-01-06 18:28:58.000000000 +0100
+++ libgit2-1.1.0+dfsg.1/debian/changelog	2023-02-24 16:11:52.000000000 +0100
@@ -1,3 +1,10 @@
+libgit2 (1.1.0+dfsg.1-4+deb11u1) bullseye-security; urgency=high
+
+  * Non-maintainer upload by the Security Team.
+  * Backport patch for CVE-2023-22742 (Closes: #1029368)
+
+ -- Tobias Frost <tobi@debian.org>  Fri, 24 Feb 2023 16:11:52 +0100
+
 libgit2 (1.1.0+dfsg.1-4) unstable; urgency=medium
 
   * Source-only upload for migration.
diff -Nru libgit2-1.1.0+dfsg.1/debian/patches/CVE-2023-22742.patch libgit2-1.1.0+dfsg.1/debian/patches/CVE-2023-22742.patch
--- libgit2-1.1.0+dfsg.1/debian/patches/CVE-2023-22742.patch	1970-01-01 01:00:00.000000000 +0100
+++ libgit2-1.1.0+dfsg.1/debian/patches/CVE-2023-22742.patch	2023-02-24 16:11:52.000000000 +0100
@@ -0,0 +1,523 @@
+Description: CVE-2023-22742 - fails to verify SSH keys by default
+ CVE-Description: libgit2 is a cross-platform, linkable library
+ implementation of Git. When using an SSH remote with the optional libssh2
+ backend, libgit2 does not perform certificate checking by default. Prior
+ versions of libgit2 require the caller to set the `certificate_check` field of
+ libgit2's `git_remote_callbacks` structure - if a certificate check callback is
+ not set, libgit2 does not perform any certificate checking. This means that by
+ default - without configuring a certificate check callback, clients will not
+ perform validation on the server SSH keys and may be subject to a
+ man-in-the-middle attack. 
+ This is a backport of the upstream fix to the Debian stretch version.
+Origin: https://github.com/libgit2/libgit2/pull/6449
+Bug-Debian: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1029368
+Forwarded: <URL|no|not-needed, useless if you have a Bug field, optional>
+Last-Update: 2023-02-21 <YYYY-MM-DD, last update of the meta-information, optional>
+---
+This patch header follows DEP-3: http://dep.debian.net/deps/dep3/
+--- a/src/transports/ssh.c
++++ b/src/transports/ssh.c
+@@ -475,15 +475,122 @@
+ 	return 0;
+ }
+ 
++
++#define KNOWN_HOSTS_FILE ".ssh/known_hosts"
++
++/*
++ * Load the known_hosts file.
++ *
++ * Returns success but leaves the output NULL if we couldn't find the file.
++ */
++static int load_known_hosts(LIBSSH2_KNOWNHOSTS **hosts, LIBSSH2_SESSION *session)
++{
++	git_buf path = GIT_BUF_INIT;
++	LIBSSH2_KNOWNHOSTS *known_hosts = NULL;
++	int error;
++	char *home = getenv("HOME");
++
++	assert(hosts);
++
++	if (!home)
++		return GIT_ENOTFOUND;
++
++
++
++	if ((error = git_buf_join(&path, '/', home, KNOWN_HOSTS_FILE)) < 0)
++		goto out;
++
++	if ((known_hosts = libssh2_knownhost_init(session)) == NULL) {
++		ssh_error(session, "error initializing known hosts");
++		error = -1;
++		goto out;
++	}
++
++	/*
++	 * Try to read the file and consider not finding it as not trusting the
++	 * host rather than an error.
++	 */
++	error = libssh2_knownhost_readfile(known_hosts, path.ptr, LIBSSH2_KNOWNHOST_FILE_OPENSSH);
++	if (error == LIBSSH2_ERROR_FILE)
++		error = 0;
++	if (error < 0)
++		ssh_error(session, "error reading known_hosts");
++
++out:
++	*hosts = known_hosts;
++
++	git_buf_free(&path);
++
++	return error;
++}
++
++static const char *hostkey_type_to_string(int type)
++{
++	switch (type) {
++	case LIBSSH2_KNOWNHOST_KEY_SSHRSA:
++		return "ssh-rsa";
++	case LIBSSH2_KNOWNHOST_KEY_SSHDSS:
++		return "ssh-dss";
++#ifdef LIBSSH2_KNOWNHOST_KEY_ECDSA_256
++	case LIBSSH2_KNOWNHOST_KEY_ECDSA_256:
++		return "ecdsa-sha2-nistp256";
++	case LIBSSH2_KNOWNHOST_KEY_ECDSA_384:
++		return "ecdsa-sha2-nistp384";
++	case LIBSSH2_KNOWNHOST_KEY_ECDSA_521:
++		return "ecdsa-sha2-nistp521";
++#endif
++#ifdef LIBSSH2_KNOWNHOST_KEY_ED25519
++	case LIBSSH2_KNOWNHOST_KEY_ED25519:
++		return "ssh-ed25519";
++#endif
++	}
++
++	return NULL;
++}
++
++/*
++ * We figure out what kind of key we want to ask the remote for by trying to
++ * look it up with a nonsense key and using that mismatch to figure out what key
++ * we do have stored for the host.
++ *
++ * Returns the string to pass to libssh2_session_method_pref or NULL if we were
++ * unable to find anything or an error happened.
++ */
++static const char *find_hostkey_preference(LIBSSH2_KNOWNHOSTS *known_hosts, const char *hostname, int port)
++{
++	struct libssh2_knownhost *host = NULL;
++	/* Specify no key type so we don't filter on that */
++	int type = LIBSSH2_KNOWNHOST_TYPE_PLAIN | LIBSSH2_KNOWNHOST_KEYENC_RAW;
++	const char key = '\0';
++	int error;
++
++	/*
++	 * In case of mismatch, we can find the type of key from known_hosts in
++	 * the returned host's information as it means that an entry was found
++	 * but our nonsense key obviously didn't match.
++	 */
++	error = libssh2_knownhost_checkp(known_hosts, hostname, port, &key, 1, type, &host);
++	if (error == LIBSSH2_KNOWNHOST_CHECK_MISMATCH)
++		return hostkey_type_to_string(host->typemask & LIBSSH2_KNOWNHOST_KEY_MASK);
++
++	return NULL;
++}
++
+ static int _git_ssh_session_create(
+ 	LIBSSH2_SESSION** session,
++	LIBSSH2_KNOWNHOSTS **hosts,
++	const char *hostname,
++	int port,
+ 	git_stream *io)
+ {
+ 	int rc = 0;
+ 	LIBSSH2_SESSION* s;
++	LIBSSH2_KNOWNHOSTS *known_hosts;
+ 	git_socket_stream *socket = GIT_CONTAINER_OF(io, git_socket_stream, parent);
++	const char *keytype = NULL;
+ 
+ 	assert(session);
++	assert(hosts);
+ 
+ 	s = libssh2_session_init();
+ 	if (!s) {
+@@ -491,21 +598,224 @@
+ 		return -1;
+ 	}
+ 
++	if ((rc = load_known_hosts(&known_hosts, s)) < 0) {
++		ssh_error(s, "error loading known_hosts");
++		libssh2_session_free(s);
++		return -1;
++	}
++
++	if ((keytype = find_hostkey_preference(known_hosts, hostname, port)) != NULL) {
++		do {
++			rc = libssh2_session_method_pref(s, LIBSSH2_METHOD_HOSTKEY, keytype);
++		} while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc);
++		if (rc != LIBSSH2_ERROR_NONE) {
++			ssh_error(s, "failed to set hostkey preference");
++			goto on_error;
++		}
++	}
++
+ 	do {
+ 		rc = libssh2_session_handshake(s, socket->s);
+ 	} while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc);
+ 
+ 	if (rc != LIBSSH2_ERROR_NONE) {
+ 		ssh_error(s, "failed to start SSH session");
+-		libssh2_session_free(s);
+-		return -1;
++		goto on_error;
+ 	}
+ 
+ 	libssh2_session_set_blocking(s, 1);
+ 
+ 	*session = s;
++	*hosts = known_hosts;
+ 
+ 	return 0;
++
++on_error:
++	libssh2_knownhost_free(known_hosts);
++	libssh2_session_free(s);
++	return -1;
++}
++
++
++/*
++ * Returns the typemask argument to pass to libssh2_knownhost_check{,p} based on
++ * the type of key that libssh2_session_hostkey returns.
++ */
++static int fingerprint_type_mask(int keytype)
++{
++	int mask = LIBSSH2_KNOWNHOST_TYPE_PLAIN | LIBSSH2_KNOWNHOST_KEYENC_RAW;
++	return mask;
++
++	switch (keytype) {
++	case LIBSSH2_HOSTKEY_TYPE_RSA:
++		mask |= LIBSSH2_KNOWNHOST_KEY_SSHRSA;
++		break;
++	case LIBSSH2_HOSTKEY_TYPE_DSS:
++		mask |= LIBSSH2_KNOWNHOST_KEY_SSHDSS;
++		break;
++#ifdef LIBSSH2_HOSTKEY_TYPE_ECDSA_256
++	case LIBSSH2_HOSTKEY_TYPE_ECDSA_256:
++		mask |= LIBSSH2_KNOWNHOST_KEY_ECDSA_256;
++		break;
++	case LIBSSH2_HOSTKEY_TYPE_ECDSA_384:
++		mask |= LIBSSH2_KNOWNHOST_KEY_ECDSA_384;
++		break;
++	case LIBSSH2_HOSTKEY_TYPE_ECDSA_521:
++		mask |= LIBSSH2_KNOWNHOST_KEY_ECDSA_521;
++		break;
++#endif
++#ifdef LIBSSH2_HOSTKEY_TYPE_ED25519
++	case LIBSSH2_HOSTKEY_TYPE_ED25519:
++		mask |= LIBSSH2_KNOWNHOST_KEY_ED25519;
++		break;
++#endif
++	}
++
++	return mask;
++}
++
++/*
++ * Check the host against the user's known_hosts file.
++ *
++ * Returns 1/0 for valid/''not-valid or <0 for an error
++ */
++static int check_against_known_hosts(
++	LIBSSH2_SESSION *session,
++	LIBSSH2_KNOWNHOSTS *known_hosts,
++	const char *hostname,
++	int port,
++	const char *key,
++	size_t key_len,
++	int key_type)
++{
++	int check, typemask, ret = 0;
++	struct libssh2_knownhost *host = NULL;
++
++	if (known_hosts == NULL)
++		return 0;
++
++	typemask = fingerprint_type_mask(key_type);
++	check = libssh2_knownhost_checkp(known_hosts, hostname, port, key, key_len, typemask, &host);
++	if (check == LIBSSH2_KNOWNHOST_CHECK_FAILURE) {
++		ssh_error(session, "error checking for known host");
++		return -1;
++	}
++
++	ret = check == LIBSSH2_KNOWNHOST_CHECK_MATCH ? 1 : 0;
++
++	return ret;
++}
++
++/*
++ * Perform the check for the session's certificate against known hosts if
++ * possible and then ask the user if they have a callback.
++ *
++ * Returns 1/0 for valid/not-valid or <0 for an error
++ */
++static int check_certificate(
++	LIBSSH2_SESSION *session,
++	LIBSSH2_KNOWNHOSTS *known_hosts,
++	git_transport_certificate_check_cb check_cb,
++	void *check_cb_payload,
++	const char *host,
++	int port)
++{
++	git_cert_hostkey cert = {{ 0 }};
++	const char *key;
++	size_t cert_len;
++	int cert_type, cert_valid = 0, error = 0;
++
++	if ((key = libssh2_session_hostkey(session, &cert_len, &cert_type)) == NULL) {
++		ssh_error(session, "failed to retrieve hostkey");
++		return -1;
++	}
++
++	if ((cert_valid = check_against_known_hosts(session, known_hosts, host, port, key, cert_len, cert_type)) < 0)
++		return -1;
++
++	cert.parent.cert_type = GIT_CERT_HOSTKEY_LIBSSH2;
++	if (key != NULL) {
++		cert.type |= GIT_CERT_SSH_RAW;
++		cert.hostkey = key;
++		cert.hostkey_len = cert_len;
++		switch (cert_type) {
++		case LIBSSH2_HOSTKEY_TYPE_RSA:
++			cert.raw_type = GIT_CERT_SSH_RAW_TYPE_RSA;
++			break;
++		case LIBSSH2_HOSTKEY_TYPE_DSS:
++			cert.raw_type = GIT_CERT_SSH_RAW_TYPE_DSS;
++			break;
++
++#ifdef LIBSSH2_HOSTKEY_TYPE_ECDSA_256
++		case LIBSSH2_HOSTKEY_TYPE_ECDSA_256:
++			cert.raw_type = GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_256;
++			break;
++		case LIBSSH2_HOSTKEY_TYPE_ECDSA_384:
++			cert.raw_type = GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_384;
++			break;
++		case LIBSSH2_KNOWNHOST_KEY_ECDSA_521:
++			cert.raw_type = GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_521;
++			break;
++#endif
++
++#ifdef LIBSSH2_HOSTKEY_TYPE_ED25519
++		case LIBSSH2_HOSTKEY_TYPE_ED25519:
++			cert.raw_type = GIT_CERT_SSH_RAW_TYPE_KEY_ED25519;
++			break;
++#endif
++		default:
++			cert.raw_type = GIT_CERT_SSH_RAW_TYPE_UNKNOWN;
++		}
++	}
++
++#ifdef LIBSSH2_HOSTKEY_HASH_SHA256
++	key = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_SHA256);
++	if (key != NULL) {
++		cert.type |= GIT_CERT_SSH_SHA256;
++		memcpy(&cert.hash_sha256, key, 32);
++	}
++#endif
++
++	key = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_SHA1);
++	if (key != NULL) {
++		cert.type |= GIT_CERT_SSH_SHA1;
++		memcpy(&cert.hash_sha1, key, 20);
++	}
++
++	key = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_MD5);
++	if (key != NULL) {
++		cert.type |= GIT_CERT_SSH_MD5;
++		memcpy(&cert.hash_md5, key, 16);
++	}
++
++	if (cert.type == 0) {
++		git_error_set(GITERR_SSH, "unable to get the host key");
++		return -1;
++	}
++
++	git_error_clear();
++	error = 0;
++	if (!cert_valid) {
++		git_error_set(GITERR_SSH, "invalid or unknown remote ssh hostkey");
++		error = GIT_ECERTIFICATE;
++	}
++
++	if (check_cb != NULL) {
++		git_cert_hostkey *cert_ptr = &cert;
++		git_error_state previous_error = {0};
++
++		git_error_state_capture(&previous_error, error);
++		error = check_cb((git_cert *) cert_ptr, cert_valid, host, check_cb_payload);
++		if (error == GIT_PASSTHROUGH) {
++			error = git_error_state_restore(&previous_error);
++		} else if (error < 0 && !git_error_last()) {
++			git_error_set(GITERR_NET, "user canceled hostkey check");
++		}
++
++		git_error_state_free(&previous_error);
++	}
++
++	return error;
+ }
+ 
+ #define SSH_DEFAULT_PORT "22"
+@@ -517,12 +827,13 @@
+ 	git_smart_subtransport_stream **stream)
+ {
+ 	git_net_url urldata = GIT_NET_URL_INIT;
+-	int auth_methods, error = 0;
++	int auth_methods, error = 0, iport;
+ 	size_t i;
+ 	ssh_stream *s;
+ 	git_credential *cred = NULL;
+ 	LIBSSH2_SESSION* session=NULL;
+ 	LIBSSH2_CHANNEL* channel=NULL;
++	LIBSSH2_KNOWNHOSTS *known_hosts = NULL;
+ 
+ 	t->current_stream = NULL;
+ 
+@@ -557,55 +868,20 @@
+ 	    (error = git_stream_connect(s->io)) < 0)
+ 		goto done;
+ 
+-	if ((error = _git_ssh_session_create(&session, s->io)) < 0)
+-		goto done;
+-
+-	if (t->owner->certificate_check_cb != NULL) {
+-		git_cert_hostkey cert = {{ 0 }}, *cert_ptr;
+-		const char *key;
+-
+-		cert.parent.cert_type = GIT_CERT_HOSTKEY_LIBSSH2;
+-
+-#ifdef LIBSSH2_HOSTKEY_HASH_SHA256
+-		key = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_SHA256);
+-		if (key != NULL) {
+-			cert.type |= GIT_CERT_SSH_SHA256;
+-			memcpy(&cert.hash_sha256, key, 32);
+-		}
+-#endif
+-
+-		key = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_SHA1);
+-		if (key != NULL) {
+-			cert.type |= GIT_CERT_SSH_SHA1;
+-			memcpy(&cert.hash_sha1, key, 20);
+-		}
+-
+-		key = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_MD5);
+-		if (key != NULL) {
+-			cert.type |= GIT_CERT_SSH_MD5;
+-			memcpy(&cert.hash_md5, key, 16);
+-		}
+-
+-		if (cert.type == 0) {
+-			git_error_set(GIT_ERROR_SSH, "unable to get the host key");
+-			error = -1;
+-			goto done;
+-		}
+-
+-		/* We don't currently trust any hostkeys */
+-		git_error_clear();
+-
+-		cert_ptr = &cert;
++	/*
++	 * Try to parse the port as a number, if we can't then fall back to
++	 * default. It would be nice if we could get the port that was resolved
++	 * as part of the stream connection, but that's not something that's
++	 * exposed.
++	 */
++	iport = atol(urldata.port);
++	if(iport == 0) iport = -1;
+ 
+-		error = t->owner->certificate_check_cb((git_cert *) cert_ptr, 0, urldata.host, t->owner->message_cb_payload);
+-
+-		if (error < 0 && error != GIT_PASSTHROUGH) {
+-			if (!git_error_last())
+-				git_error_set(GIT_ERROR_NET, "user cancelled hostkey check");
++	if ((error = _git_ssh_session_create(&session, &known_hosts, urldata.host, iport, s->io)) < 0)
++		goto done;
+ 
+-			goto done;
+-		}
+-	}
++	if ((error = check_certificate(session, known_hosts, t->owner->certificate_check_cb, t->owner->message_cb_payload, urldata.host, iport)) < 0)
++		goto done;
+ 
+ 	/* we need the username to ask for auth methods */
+ 	if (!urldata.username) {
+@@ -677,6 +953,9 @@
+ 	if (error < 0) {
+ 		ssh_stream_free(*stream);
+ 
++		if (known_hosts)
++			libssh2_knownhost_free(known_hosts);
++
+ 		if (session)
+ 			libssh2_session_free(session);
+ 	}
+--- a/include/git2/cert.h
++++ b/include/git2/cert.h
+@@ -80,8 +80,29 @@
+ 	GIT_CERT_SSH_SHA1 = (1 << 1),
+ 	/** SHA-256 is available */
+ 	GIT_CERT_SSH_SHA256 = (1 << 2),
++	/** Raw hostkey is available */
++	GIT_CERT_SSH_RAW = (1 << 3)
+ } git_cert_ssh_t;
+ 
++
++typedef enum {
++	/** The raw key is of an unknown type. */
++	GIT_CERT_SSH_RAW_TYPE_UNKNOWN = 0,
++	/** The raw key is an RSA key. */
++	GIT_CERT_SSH_RAW_TYPE_RSA = 1,
++	/** The raw key is a DSS key. */
++	GIT_CERT_SSH_RAW_TYPE_DSS = 2,
++	/** The raw key is a ECDSA 256 key. */
++	GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_256 = 3,
++	/** The raw key is a ECDSA 384 key. */
++	GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_384 = 4,
++	/** The raw key is a ECDSA 521 key. */
++	GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_521 = 5,
++	/** The raw key is a ED25519 key. */
++	GIT_CERT_SSH_RAW_TYPE_KEY_ED25519 = 6
++} git_cert_ssh_raw_type_t;
++
++
+ /**
+  * Hostkey information taken from libssh2
+  */
+@@ -111,6 +132,28 @@
+ 	 * have the SHA-256 hash of the hostkey.
+ 	 */
+ 	unsigned char hash_sha256[32];
++
++
++	/**
++	 * Raw hostkey type. If `type` has `GIT_CERT_SSH_RAW` set, this will
++	 * have the type of the raw hostkey.
++	 */
++	git_cert_ssh_raw_type_t raw_type;
++
++	/**
++	 * Pointer to the raw hostkey. If `type` has `GIT_CERT_SSH_RAW` set,
++	 * this will have the raw contents of the hostkey.
++	 */
++	const char *hostkey;
++
++	/**
++	 * Raw hostkey length. If `type` has `GIT_CERT_SSH_RAW` set, this will
++	 * have the length of the raw contents of the hostkey.
++	 */
++	size_t hostkey_len;
++
++
++
+ } git_cert_hostkey;
+ 
+ /**
diff -Nru libgit2-1.1.0+dfsg.1/debian/patches/series libgit2-1.1.0+dfsg.1/debian/patches/series
--- libgit2-1.1.0+dfsg.1/debian/patches/series	2020-12-06 23:46:54.000000000 +0100
+++ libgit2-1.1.0+dfsg.1/debian/patches/series	2023-02-24 13:05:26.000000000 +0100
@@ -1,2 +1,3 @@
 disable-online-tests.patch
 enable-repro-builds.patch
+CVE-2023-22742.patch

--- End Message ---
--- Begin Message ---
Package: release.debian.org
Version: 11.7

Hi,

Each of the updates referred to in these requests was included in this
morning's 11.7 point release.

Regards,

Adam

--- End Message ---

Reply to: