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

Bug#1052211: bookworm-pu: package electrum/4.0.9



Attached is the debdiff.


There is a tracking bug filed against Electrum:

https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1052200


--

Soren Stoutner

soren@stoutner.com

diff -Nru electrum-4.3.4+dfsg1/debian/changelog electrum-4.3.4+dfsg1/debian/changelog
--- electrum-4.3.4+dfsg1/debian/changelog	2023-02-02 15:52:07.000000000 -0700
+++ electrum-4.3.4+dfsg1/debian/changelog	2023-09-12 09:51:54.000000000 -0700
@@ -1,3 +1,13 @@
+electrum (4.3.4+dfsg1-1+deb12u1) bookworm; urgency=high
+
+  * Add debian/patches to fix Lightning security problem fixed upstream in
+    4.4.6.  See
+    https://github.com/spesmilo/electrum/security/advisories/GHSA-8r85-vp7r-hjxf
+  * Add myself to uploaders and remove Tristan Seligmann <mithrandi@debian.org>.
+    (Closes: #1041171).
+
+ -- Soren Stoutner <soren@stoutner.com>  Tue, 12 Sep 2023 09:51:54 -0700
+
 electrum (4.3.4+dfsg1-1) unstable; urgency=high
 
   * Team upload
diff -Nru electrum-4.3.4+dfsg1/debian/control electrum-4.3.4+dfsg1/debian/control
--- electrum-4.3.4+dfsg1/debian/control	2023-02-02 15:52:07.000000000 -0700
+++ electrum-4.3.4+dfsg1/debian/control	2023-09-12 09:51:54.000000000 -0700
@@ -1,6 +1,6 @@
 Source: electrum
 Maintainer: Debian Cryptocoin Team <team+cryptocoin@tracker.debian.org>
-Uploaders: Tristan Seligmann <mithrandi@debian.org>
+Uploaders: Soren Stoutner <soren@stoutner.com>
 Section: utils
 Priority: optional
 # libsecp256k1-1: Used to speed up elliptic curve operations.
diff -Nru electrum-4.3.4+dfsg1/debian/patches/Lightning-security-fix.patch electrum-4.3.4+dfsg1/debian/patches/Lightning-security-fix.patch
--- electrum-4.3.4+dfsg1/debian/patches/Lightning-security-fix.patch	1969-12-31 17:00:00.000000000 -0700
+++ electrum-4.3.4+dfsg1/debian/patches/Lightning-security-fix.patch	2023-09-06 10:41:21.000000000 -0700
@@ -0,0 +1,248 @@
+Description: Patch the Lightning security issue fixed upstream in 4.4.6.
+From: Soren Stoutner <soren@stoutner.com>
+Forwarded: https://github.com/spesmilo/electrum/issues/8588
+Last-Update: 2023-09-06
+
+--- a/electrum/lnpeer.py
++++ b/electrum/lnpeer.py
+@@ -1773,13 +1773,25 @@
+             payment_secret_from_onion = None
+ 
+         if total_msat > amt_to_forward:
+-            mpp_status = self.lnworker.check_received_mpp_htlc(payment_secret_from_onion, chan.short_channel_id, htlc, total_msat)
+-            if mpp_status is None:
++            from .lnworker import RecvMPPResolution
++            mpp_resolution = self.lnworker.check_mpp_status(
++                payment_secret=payment_secret_from_onion,
++                short_channel_id=chan.short_channel_id,
++                htlc=htlc,
++                expected_msat=total_msat,
++            )
++            if mpp_resolution == RecvMPPResolution.WAITING:
+                 return None, None
+-            if mpp_status is False:
++            elif mpp_resolution == RecvMPPResolution.EXPIRED:
+                 log_fail_reason(f"MPP_TIMEOUT")
+                 raise OnionRoutingFailure(code=OnionFailureCode.MPP_TIMEOUT, data=b'')
+-            assert mpp_status is True
++            elif mpp_resolution == RecvMPPResolution.FAILED:
++                log_fail_reason(f"mpp_resolution is FAILED")
++                raise exc_incorrect_or_unknown_pd
++            elif mpp_resolution == RecvMPPResolution.ACCEPTED:
++                pass  # continue
++            else:
++                raise Exception(f"unexpected {mpp_resolution=}")
+ 
+         # if there is a trampoline_onion, maybe_fulfill_htlc will be called again
+         if processed_onion.trampoline_onion_packet:
+--- a/electrum/lnworker.py
++++ b/electrum/lnworker.py
+@@ -8,7 +8,8 @@
+ import random
+ import time
+ import operator
+-from enum import IntEnum
++import enum
++from enum import IntEnum, Enum
+ from typing import (Optional, Sequence, Tuple, List, Set, Dict, TYPE_CHECKING,
+                     NamedTuple, Union, Mapping, Any, Iterable, AsyncGenerator, DefaultDict)
+ import threading
+@@ -166,6 +167,19 @@
+     status: int
+ 
+ 
++class RecvMPPResolution(Enum):
++    WAITING = enum.auto()
++    EXPIRED = enum.auto()
++    ACCEPTED = enum.auto()
++    FAILED = enum.auto()
++
++
++class ReceivedMPPStatus(NamedTuple):
++    resolution: RecvMPPResolution
++    expected_msat: int
++    htlc_set: Set[Tuple[ShortChannelID, UpdateAddHtlc]]
++
++
+ class ErrorAddingPeer(Exception): pass
+ 
+ 
+@@ -653,8 +667,8 @@
+ 
+         self.sent_htlcs = defaultdict(asyncio.Queue)  # type: Dict[bytes, asyncio.Queue[HtlcLog]]
+         self.sent_htlcs_info = dict()                 # (RHASH, scid, htlc_id) -> route, payment_secret, amount_msat, bucket_msat, trampoline_fee_level
+-        self.sent_buckets = dict()                    # payment_secret -> (amount_sent, amount_failed)
+-        self.received_mpp_htlcs = dict()                  # RHASH -> mpp_status, htlc_set
++        self.sent_buckets = dict()                    # payment_key -> (amount_sent, amount_failed)
++        self.received_mpp_htlcs = dict()              # type: Dict[bytes, ReceivedMPPStatus]  # payment_key -> ReceivedMPPStatus
+ 
+         self.swap_manager = SwapManager(wallet=self.wallet, lnworker=self)
+         # detect inflight payments
+@@ -1377,13 +1391,14 @@
+ 
+         key = (payment_hash, short_channel_id, htlc.htlc_id)
+         self.sent_htlcs_info[key] = route, payment_secret, amount_msat, total_msat, amount_receiver_msat, trampoline_fee_level, trampoline_route
++        payment_key = payment_hash + payment_secret
+         # if we sent MPP to a trampoline, add item to sent_buckets
+         if self.uses_trampoline() and amount_msat != total_msat:
+-            if payment_secret not in self.sent_buckets:
+-                self.sent_buckets[payment_secret] = (0, 0)
+-            amount_sent, amount_failed = self.sent_buckets[payment_secret]
++            if payment_key not in self.sent_buckets:
++                self.sent_buckets[payment_key] = (0, 0)
++            amount_sent, amount_failed = self.sent_buckets[payment_key]
+             amount_sent += amount_receiver_msat
+-            self.sent_buckets[payment_secret] = amount_sent, amount_failed
++            self.sent_buckets[payment_key] = amount_sent, amount_failed
+         if self.network.path_finder:
+             # add inflight htlcs to liquidity hints
+             self.network.path_finder.update_inflight_htlcs(route, add_htlcs=True)
+@@ -1857,33 +1872,91 @@
+         if write_to_disk:
+             self.wallet.save_db()
+ 
+-    def check_received_mpp_htlc(self, payment_secret, short_channel_id, htlc: UpdateAddHtlc, expected_msat: int) -> Optional[bool]:
+-        """ return MPP status: True (accepted), False (expired) or None """
++    def check_mpp_status(
++            self, *,
++            payment_secret: bytes,
++            short_channel_id: ShortChannelID,
++            htlc: UpdateAddHtlc,
++            expected_msat: int,
++    ) -> RecvMPPResolution:
+         payment_hash = htlc.payment_hash
+-        is_expired, is_accepted, htlc_set = self.received_mpp_htlcs.get(payment_secret, (False, False, set()))
+-        if self.get_payment_status(payment_hash) == PR_PAID:
+-            # payment_status is persisted
+-            is_accepted = True
+-            is_expired = False
+-        key = (short_channel_id, htlc)
+-        if key not in htlc_set:
+-            htlc_set.add(key)
+-        if not is_accepted and not is_expired:
+-            total = sum([_htlc.amount_msat for scid, _htlc in htlc_set])
+-            first_timestamp = min([_htlc.timestamp for scid, _htlc in htlc_set])
+-            if self.stopping_soon:
+-                is_expired = True  # try to time out pending HTLCs before shutting down
++        payment_key = payment_hash + payment_secret
++        self.update_mpp_with_received_htlc(
++            payment_key=payment_key, scid=short_channel_id, htlc=htlc, expected_msat=expected_msat)
++        mpp_resolution = self.received_mpp_htlcs[payment_key].resolution
++        if mpp_resolution == RecvMPPResolution.WAITING:
++            first_timestamp = self.get_first_timestamp_of_mpp(payment_key)
++            if self.get_payment_status(payment_hash) == PR_PAID:
++                mpp_resolution = RecvMPPResolution.ACCEPTED
++            elif self.stopping_soon:
++                # try to time out pending HTLCs before shutting down
++                mpp_resolution = RecvMPPResolution.EXPIRED
++            elif self.is_mpp_amount_reached(payment_key):
++                mpp_resolution = RecvMPPResolution.ACCEPTED
+             elif time.time() - first_timestamp > self.MPP_EXPIRY:
+-                is_expired = True
+-            elif total == expected_msat:
+-                is_accepted = True
+-        if is_accepted or is_expired:
+-            htlc_set.remove(key)
+-        if len(htlc_set) > 0:
+-            self.received_mpp_htlcs[payment_secret] = is_expired, is_accepted, htlc_set
+-        elif payment_secret in self.received_mpp_htlcs:
+-            self.received_mpp_htlcs.pop(payment_secret)
+-        return True if is_accepted else (False if is_expired else None)
++                mpp_resolution = RecvMPPResolution.EXPIRED
++
++            if mpp_resolution != RecvMPPResolution.WAITING:
++                self.set_mpp_resolution(payment_key=payment_key, resolution=mpp_resolution)
++
++        self.maybe_cleanup_mpp_status(payment_key, short_channel_id, htlc)
++        return mpp_resolution
++
++    def update_mpp_with_received_htlc(
++        self,
++        *,
++        payment_key: bytes,
++        scid: ShortChannelID,
++        htlc: UpdateAddHtlc,
++        expected_msat: int,
++    ):
++        # add new htlc to set
++        mpp_status = self.received_mpp_htlcs.get(payment_key)
++        if mpp_status is None:
++            mpp_status = ReceivedMPPStatus(
++                resolution=RecvMPPResolution.WAITING,
++                expected_msat=expected_msat,
++                htlc_set=set(),
++            )
++        if expected_msat != mpp_status.expected_msat:
++            self.logger.info(
++                f"marking received mpp as failed. inconsistent total_msats in bucket. {payment_key.hex()=}")
++            mpp_status = mpp_status._replace(resolution=RecvMPPResolution.FAILED)
++        key = (scid, htlc)
++        if key not in mpp_status.htlc_set:
++            mpp_status.htlc_set.add(key)  # side-effecting htlc_set
++        self.received_mpp_htlcs[payment_key] = mpp_status
++
++    def set_mpp_resolution(self, *, payment_key: bytes, resolution: RecvMPPResolution):
++        mpp_status = self.received_mpp_htlcs[payment_key]
++        self.received_mpp_htlcs[payment_key] = mpp_status._replace(resolution=resolution)
++
++    def is_mpp_amount_reached(self, payment_key: bytes) -> bool:
++        mpp_status = self.received_mpp_htlcs.get(payment_key)
++        if not mpp_status:
++            return False
++        total = sum([_htlc.amount_msat for scid, _htlc in mpp_status.htlc_set])
++        return total >= mpp_status.expected_msat
++
++    def get_first_timestamp_of_mpp(self, payment_key: bytes) -> int:
++        mpp_status = self.received_mpp_htlcs.get(payment_key)
++        if not mpp_status:
++            return int(time.time())
++        return min([_htlc.timestamp for scid, _htlc in mpp_status.htlc_set])
++
++    def maybe_cleanup_mpp_status(
++        self,
++        payment_key: bytes,
++        short_channel_id: ShortChannelID,
++        htlc: UpdateAddHtlc,
++    ) -> None:
++        mpp_status = self.received_mpp_htlcs[payment_key]
++        if mpp_status.resolution == RecvMPPResolution.WAITING:
++            return
++        key = (short_channel_id, htlc)
++        mpp_status.htlc_set.remove(key)  # side-effecting htlc_set
++        if not mpp_status.htlc_set and payment_key in self.received_mpp_htlcs:
++            self.received_mpp_htlcs.pop(payment_key)
+ 
+     def get_payment_status(self, payment_hash: bytes) -> int:
+         info = self.get_payment_info(payment_hash)
+@@ -1988,10 +2061,11 @@
+             self.logger.info(f"htlc_failed {failure_message}")
+ 
+             # check sent_buckets if we use trampoline
+-            if self.uses_trampoline() and payment_secret in self.sent_buckets:
+-                amount_sent, amount_failed = self.sent_buckets[payment_secret]
++            payment_key = payment_hash + payment_secret
++            if self.uses_trampoline() and payment_key in self.sent_buckets:
++                amount_sent, amount_failed = self.sent_buckets[payment_key]
+                 amount_failed += amount_receiver_msat
+-                self.sent_buckets[payment_secret] = amount_sent, amount_failed
++                self.sent_buckets[payment_key] = amount_sent, amount_failed
+                 if amount_sent != amount_failed:
+                     self.logger.info('bucket still active...')
+                     return
+--- a/electrum/__init__.py
++++ b/electrum/__init__.py
+@@ -32,3 +32,14 @@
+ 
+ 
+ __version__ = ELECTRUM_VERSION
++
++
++# Ensure that asserts are enabled. For sanity and paranoia, we require this.
++# Code *should not rely* on asserts being enabled. In particular, safety and security checks should
++# always explicitly raise exceptions. However, this rule is mistakenly broken occasionally...
++try:
++    assert False  # noqa: B011
++except AssertionError:
++    pass
++else:
++    raise ImportError("Running with asserts disabled. Refusing to continue. Exiting...")
diff -Nru electrum-4.3.4+dfsg1/debian/patches/series electrum-4.3.4+dfsg1/debian/patches/series
--- electrum-4.3.4+dfsg1/debian/patches/series	2023-02-02 15:52:07.000000000 -0700
+++ electrum-4.3.4+dfsg1/debian/patches/series	2023-09-06 10:35:47.000000000 -0700
@@ -1,3 +1,6 @@
+# Patch Lightning security vulnerability fixed in 4.4.6 upstream.
+Lightning-security-fix.patch
+
 # This patch makes the error message make more sense on a Debian system.
 Improve-message-about-PyQt5.patch
 

Attachment: signature.asc
Description: This is a digitally signed message part.


Reply to: