Merged
21 changes: 20 additions & 1 deletion CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -71,7 +71,19 @@

## Security
0.2.3 fixes several underestimates of the anchor reserves required to ensure we
can reliably close channels and a sanitization issue.
can reliably close channels, several denial-of-service vulnerabilities and a
sanitization issue.
* `Bolt11Invoice::recover_payee_pub_key` no longer panics if called on an
invoice which set an explicit public key, rather than relying on public key
recovery. Note that this method is called from
`PaymentParameters::from_bolt11_invoice` (#4717).
* Maliciously-crafted unpayable invoices which have overflowing feerates will
no longer cause an `unwrap` failure panic (#4716).
* `possiblyrandom` did not properly generate random data except when it was
explicitly configured to. By default this means LDK is vulnerable to various
HashDoS attacks (#4719).
* `OMNameResolver` will no longer panic when looking up payment instructions
which include unicode characters at the start of a TXT record (#4718).
* When using the `anchor_channel_reserves` module to calculate reserves
required to pay for fees when closing anchor channels, zero-fee-commitment
channels were not considered. This could allow a counterparty to open many
Expand All@@ -80,6 +92,13 @@ can reliably close channels and a sanitization issue.
the wallet by ignoring the `TxIn` cost to spend them (#4670).
* `PrintableString` did not properly sanitize unicode format characters,
allowing an attacker to corrupt the rendering of logs or UI (#4593, #4605).
* RGS data is now limited in how large of a graph it is able to cause a client
to store in memory. Note that RGS data is still considered a DoS vector in
general and you should only use semi-trusted RGS data (#4713).
* Counterparty-provided strings in failure messages are no longer logged in
full, reducing the ability of such a counterparty to spam our logs (#4714).
* Reading a corrupted `ChannelManager` or `ProbabilisticScorer` can no longer
cause us to allocate large amounts of memory (#4712).

Thanks to Project Loupe for reporting most of the issues fixed in this release.

Expand Down
2 changes: 1 addition & 1 deletion lightning-background-processor/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning-background-processor"
version = "0.2.0"
version = "0.2.3"
authors = ["Valentine Wallace <vwallace@protonmail.com>"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning"
Expand Down
2 changes: 1 addition & 1 deletion lightning-custom-message/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning-custom-message"
version = "0.2.0"
version = "0.2.3"
authors = ["Jeffrey Czyz"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning"
Expand Down
2 changes: 1 addition & 1 deletion lightning-dns-resolver/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning-dns-resolver"
version = "0.3.0"
version = "0.3.1"
authors = ["Matt Corallo"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning/"
Expand Down
2 changes: 1 addition & 1 deletion lightning-invoice/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
[package]
name = "lightning-invoice"
description = "Data structures to parse and serialize BOLT11 lightning invoices"
version = "0.34.0"
version = "0.34.1"
authors = ["Sebastian Geisler <sgeisler@wh2.tu-dresden.de>"]
documentation = "https://docs.rs/lightning-invoice/"
license = "MIT OR Apache-2.0"
Expand Down
53 changes: 48 additions & 5 deletions lightning-invoice/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1498,17 +1498,22 @@ impl Bolt11Invoice {
self.signed_invoice.features()
}

/// Recover the payee's public key (only to be used if none was included in the invoice)
/// Get the invoice's payee public key.
///
/// This uses the explicitly included payee public key, if present, otherwise it recovers the
/// payee public key from the signature. Prefer [`Self::get_payee_pub_key`] for clarity.
pub fn recover_payee_pub_key(&self) -> PublicKey {
self.signed_invoice.recover_payee_pub_key().expect("was checked by constructor").0
self.get_payee_pub_key()
}

/// Recover the payee's public key if one was included in the invoice, otherwise return the
/// recovered public key from the signature
/// Get the invoice's payee public key, preferring an explicitly included payee public key and
/// falling back to recovering the key from the signature.
pub fn get_payee_pub_key(&self) -> PublicKey {
match self.payee_pub_key() {
Some(pk) => *pk,
None => self.recover_payee_pub_key(),
None => {
self.signed_invoice.recover_payee_pub_key().expect("was checked by constructor").0
},
}
}

Expand DownExpand Up@@ -2044,6 +2049,44 @@ mod test {
assert!(new_signed.check_signature());
}

#[test]
fn recover_payee_pub_key_uses_included_payee_pub_key() {
use crate::*;
use bitcoin::secp256k1::ecdsa::{RecoverableSignature, RecoveryId};
use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};
use core::time::Duration;

let secp_ctx = Secp256k1::new();
let private_key = SecretKey::from_slice(&[42; 32]).unwrap();
let public_key = PublicKey::from_secret_key(&secp_ctx, &private_key);

let invoice = InvoiceBuilder::new(Currency::Bitcoin)
.description("Test".to_string())
.payment_hash(sha256::Hash::from_slice(&[0; 32][..]).unwrap())
.payment_secret(PaymentSecret([21; 32]))
.payee_pub_key(public_key)
.min_final_cltv_expiry_delta(144)
.duration_since_epoch(Duration::from_secs(1234567))
.build_signed(|hash| secp_ctx.sign_ecdsa_recoverable(hash, &private_key))
.unwrap();

let signed_raw = invoice.into_signed_raw();
let (raw_invoice, hash, signature) = signed_raw.into_parts();
let (_orig_rid, sig_bytes) = signature.0.serialize_compact();
let bad_rid = RecoveryId::from_i32(2).unwrap();
let bad_sig = RecoverableSignature::from_compact(&sig_bytes, bad_rid).unwrap();
let bad_signed_raw = SignedRawBolt11Invoice {
raw_invoice,
hash,
signature: Bolt11InvoiceSignature(bad_sig),
};
let bad_invoice = Bolt11Invoice::from_signed(bad_signed_raw).unwrap();

assert_eq!(bad_invoice.payee_pub_key(), Some(&public_key));
assert_eq!(bad_invoice.recover_payee_pub_key(), public_key);
assert_eq!(bad_invoice.get_payee_pub_key(), public_key);
}

#[test]
fn test_check_feature_bits() {
use crate::TaggedField::*;
Expand Down
2 changes: 1 addition & 1 deletion lightning-liquidity/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning-liquidity"
version = "0.2.0"
version = "0.2.3"
authors = ["John Cantrell <johncantrell97@gmail.com>", "Elias Rohrer <dev@tnull.de>"]
homepage = "https://lightningdevkit.org/"
license = "MIT OR Apache-2.0"
Expand Down
2 changes: 1 addition & 1 deletion lightning-persister/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning-persister"
version = "0.2.0"
version = "0.2.3"
authors = ["Valentine Wallace", "Matt Corallo"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning"
Expand Down
2 changes: 1 addition & 1 deletion lightning-rapid-gossip-sync/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning-rapid-gossip-sync"
version = "0.2.0"
version = "0.2.3"
authors = ["Arik Sosman <git@arik.io>"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning"
Expand Down
12 changes: 12 additions & 0 deletions lightning-rapid-gossip-sync/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -153,6 +153,10 @@ where
/// Sync gossip data from a file.
/// Returns the last sync timestamp to be used the next time rapid sync data is queried.
///
/// You should consider the gossip data source as semi-trusted. It is generally the case that it
/// can DoS the client either by omitting data which leads to pathfinding failure or by bloating
/// the graph such that it leads to eventual OOM on the client.
///
/// `network_graph`: The network graph to apply the updates to
///
/// `sync_path`: Path to the file where the gossip update data is located
Expand All@@ -172,6 +176,10 @@ where
/// Update network graph from binary data.
/// Returns the last sync timestamp to be used the next time rapid sync data is queried.
///
/// You should consider the gossip data source as semi-trusted. It is generally the case that it
/// can DoS the client either by omitting data which leads to pathfinding failure or by bloating
/// the graph such that it leads to eventual OOM on the client.
///
/// `update_data`: `&[u8]` binary stream that comprises the update data
#[cfg(feature = "std")]
pub fn update_network_graph(&self, update_data: &[u8]) -> Result<u32, GraphSyncError> {
Expand All@@ -182,6 +190,10 @@ where
/// Update network graph from binary data.
/// Returns the last sync timestamp to be used the next time rapid sync data is queried.
///
/// You should consider the gossip data source as semi-trusted. It is generally the case that it
/// can DoS the client either by omitting data which leads to pathfinding failure or by bloating
/// the graph such that it leads to eventual OOM on the client.
///
/// `update_data`: `&[u8]` binary stream that comprises the update data
/// `current_time_unix`: `Option<u64>` optional current timestamp to verify data age
pub fn update_network_graph_no_std(
Expand Down
34 changes: 30 additions & 4 deletions lightning-rapid-gossip-sync/src/processing.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,7 +9,9 @@ use lightning::ln::msgs::{
DecodeError, ErrorAction, LightningError, SocketAddress, UnsignedChannelUpdate,
UnsignedNodeAnnouncement,
};
use lightning::routing::gossip::{NetworkGraph, NodeAlias, NodeId};
use lightning::routing::gossip::{
NetworkGraph, NodeAlias, NodeId, CHAN_COUNT_ESTIMATE, NODE_COUNT_ESTIMATE,
};
use lightning::util::logger::Logger;
use lightning::util::ser::{BigSize, FixedLengthReader, Readable};
use lightning::{log_debug, log_given_level, log_gossip, log_trace, log_warn};
Expand DownExpand Up@@ -115,17 +117,27 @@ where
}
};

const MAX_NODE_COUNT: u32 = (NODE_COUNT_ESTIMATE as u32) * 10;
const MAX_CHANNEL_COUNT: u64 = (CHAN_COUNT_ESTIMATE as u64) * 10;

let node_id_count: u32 = Readable::read(read_cursor)?;
if node_id_count > MAX_NODE_COUNT {
return Err(LightningError {
err: "RGS data contained nonsense number of nodes to update".to_owned(),
action: ErrorAction::IgnoreError,
}
.into());
}
let mut node_ids: Vec<NodeId> = Vec::with_capacity(core::cmp::min(
node_id_count,
MAX_INITIAL_NODE_ID_VECTOR_CAPACITY,
) as usize);

let network_graph = &self.network_graph;
let mut node_modifications: Vec<UnsignedNodeAnnouncement> = Vec::new();

let read_only_network_graph = network_graph.read_only();

if parse_node_details {
let read_only_network_graph = network_graph.read_only();
for _ in 0..node_id_count {
let mut pubkey_bytes = [0u8; 33];
read_cursor.read_exact(&mut pubkey_bytes)?;
Expand DownExpand Up@@ -237,9 +249,12 @@ where
}
}

let original_graph_channel_count = read_only_network_graph.channels().len() as u32;
core::mem::drop(read_only_network_graph);

let mut previous_scid: u64 = 0;
let announcement_count: u32 = Readable::read(read_cursor)?;
for _ in 0..announcement_count {
for i in 0..announcement_count {
let features = Readable::read(read_cursor)?;

// handle SCID
Expand DownExpand Up@@ -284,6 +299,10 @@ where
}
}

if (original_graph_channel_count as u64) + (i as u64) > MAX_CHANNEL_COUNT {
continue;
}

let announcement_result = network_graph.add_channel_from_partial_announcement(
short_channel_id,
funding_sats,
Expand DownExpand Up@@ -329,6 +348,13 @@ where
previous_scid = 0;

let update_count: u32 = Readable::read(read_cursor)?;
if update_count as u64 > MAX_CHANNEL_COUNT {
return Err(LightningError {
err: "RGS data contained nonsense number of channels to update".to_owned(),
action: ErrorAction::IgnoreError,
}
.into());
}
log_debug!(self.logger, "Processing RGS update from {} with {} nodes, {} channel announcements and {} channel updates.",
latest_seen_timestamp, node_id_count, announcement_count, update_count);
if update_count == 0 {
Expand Down
2 changes: 1 addition & 1 deletion lightning-types/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning-types"
version = "0.3.1"
version = "0.3.2"
authors = ["Matt Corallo"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning/"
Expand Down
2 changes: 1 addition & 1 deletion lightning/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning"
version = "0.2.2"
version = "0.2.3"
authors = ["Matt Corallo"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning/"
Expand Down
9 changes: 6 additions & 3 deletions lightning/src/ln/channel.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1959,9 +1959,12 @@ where

let tx_abort = should_ack.then(|| {
let logger = WithChannelContext::from(logger, &self.context(), None);
let reason =
types::string::UntrustedString(String::from_utf8_lossy(&msg.data).to_string());
log_info!(logger, "Counterparty failed interactive transaction negotiation: {reason}");
let reason = String::from_utf8_lossy(&msg.data);
log_info!(
logger,
"Counterparty failed interactive transaction negotiation: {}",
log_msg!(reason)
);
msgs::TxAbort {
channel_id: msg.channel_id,
data: "Acknowledged tx_abort".to_string().into_bytes(),
Expand Down
4 changes: 2 additions & 2 deletions lightning/src/ln/invoice_utils.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1288,7 +1288,7 @@ mod test {
assert!(!invoice.features().unwrap().supports_basic_mpp());

let payment_params = PaymentParameters::from_node_id(
invoice.recover_payee_pub_key(),
invoice.get_payee_pub_key(),
invoice.min_final_cltv_expiry_delta() as u32,
)
.with_bolt11_features(invoice.features().unwrap().clone())
Expand DownExpand Up@@ -1350,7 +1350,7 @@ mod test {
payment_secret,
payment_amt,
payment_preimage_opt,
invoice.recover_payee_pub_key(),
invoice.get_payee_pub_key(),
);
do_claim_payment_along_route(ClaimAlongRouteArgs::new(
&nodes[0],
Expand Down
13 changes: 6 additions & 7 deletions lightning/src/ln/peer_handler.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -45,7 +45,6 @@ use crate::onion_message::packet::OnionMessageContents;
use crate::routing::gossip::{NodeAlias, NodeId};
use crate::sign::{NodeSigner, Recipient};
use crate::types::features::{InitFeatures, NodeFeatures};
use crate::types::string::PrintableString;
use crate::util::atomic_counter::AtomicCounter;
use crate::util::logger::{Level, Logger, WithContext};
use crate::util::ser::{VecWriter, Writeable, Writer};
Expand DownExpand Up@@ -2415,7 +2414,7 @@ where
logger,
"Got Err message from {}: {}",
their_node_id,
PrintableString(&msg.data)
log_msg!(msg.data)
);
self.message_handler.chan_handler.handle_error(their_node_id, &msg);
if msg.channel_id.is_zero() {
Expand All@@ -2427,7 +2426,7 @@ where
logger,
"Got warning message from {}: {}",
their_node_id,
PrintableString(&msg.data)
log_msg!(msg.data)
);
},

Expand DownExpand Up@@ -3213,7 +3212,7 @@ where
msgs::ErrorAction::DisconnectPeer { msg } => {
if let Some(msg) = msg.as_ref() {
log_trace!(logger, "Handling DisconnectPeer HandleError event in peer_handler for node {} with message {}",
node_id, msg.data);
node_id, log_msg!(msg.data));
} else {
log_trace!(logger, "Handling DisconnectPeer HandleError event in peer_handler for node {}",
node_id);
Expand All@@ -3228,7 +3227,7 @@ where
},
msgs::ErrorAction::DisconnectPeerWithWarning { msg } => {
log_trace!(logger, "Handling DisconnectPeer HandleError event in peer_handler for node {} with message {}",
node_id, msg.data);
node_id, log_msg!(msg.data));
// We do not have the peers write lock, so we just store that we're
// about to disconnect the peer and do it after we finish
// processing most messages.
Expand All@@ -3254,7 +3253,7 @@ where
msgs::ErrorAction::SendErrorMessage { ref msg } => {
log_trace!(logger, "Handling SendErrorMessage HandleError event in peer_handler for node {} with message {}",
node_id,
msg.data);
log_msg!(msg.data));
self.enqueue_message(
&mut *get_peer_for_forwarding!(&node_id)?,
msg,
Expand All@@ -3266,7 +3265,7 @@ where
} => {
log_given_level!(logger, *log_level, "Handling SendWarningMessage HandleError event in peer_handler for node {} with message {}",
node_id,
msg.data);
log_msg!(msg.data));
self.enqueue_message(
&mut *get_peer_for_forwarding!(&node_id)?,
msg,
Expand Down
3 changes: 2 additions & 1 deletion lightning/src/onion_message/dns_resolution.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -520,7 +520,8 @@ impl OMNameResolver {
.filter_map(|data| String::from_utf8(data).ok())
.filter(|data_string| data_string.len() > URI_PREFIX.len())
.filter(|data_string| {
data_string[..URI_PREFIX.len()].eq_ignore_ascii_case(URI_PREFIX)
let pfx = &data_string.as_bytes()[..URI_PREFIX.len()];
pfx.eq_ignore_ascii_case(URI_PREFIX.as_bytes())
});
// Check that there is exactly one TXT record that begins with
// bitcoin: as required by BIP 353 (and is valid UTF-8).
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
21 changes: 20 additions & 1 deletion CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -71,7 +71,19 @@

## Security
0.2.3 fixes several underestimates of the anchor reserves required to ensure we
can reliably close channels and a sanitization issue.
can reliably close channels, several denial-of-service vulnerabilities and a
sanitization issue.
* `Bolt11Invoice::recover_payee_pub_key` no longer panics if called on an
invoice which set an explicit public key, rather than relying on public key
recovery. Note that this method is called from
`PaymentParameters::from_bolt11_invoice` (#4717).
* Maliciously-crafted unpayable invoices which have overflowing feerates will
no longer cause an `unwrap` failure panic (#4716).
* `possiblyrandom` did not properly generate random data except when it was
explicitly configured to. By default this means LDK is vulnerable to various
HashDoS attacks (#4719).
* `OMNameResolver` will no longer panic when looking up payment instructions
which include unicode characters at the start of a TXT record (#4718).
* When using the `anchor_channel_reserves` module to calculate reserves
required to pay for fees when closing anchor channels, zero-fee-commitment
channels were not considered. This could allow a counterparty to open many
Expand All@@ -80,6 +92,13 @@ can reliably close channels and a sanitization issue.
the wallet by ignoring the `TxIn` cost to spend them (#4670).
* `PrintableString` did not properly sanitize unicode format characters,
allowing an attacker to corrupt the rendering of logs or UI (#4593, #4605).
* RGS data is now limited in how large of a graph it is able to cause a client
to store in memory. Note that RGS data is still considered a DoS vector in
general and you should only use semi-trusted RGS data (#4713).
* Counterparty-provided strings in failure messages are no longer logged in
full, reducing the ability of such a counterparty to spam our logs (#4714).
* Reading a corrupted `ChannelManager` or `ProbabilisticScorer` can no longer
cause us to allocate large amounts of memory (#4712).

Thanks to Project Loupe for reporting most of the issues fixed in this release.

Expand Down
2 changes: 1 addition & 1 deletion lightning-background-processor/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning-background-processor"
version = "0.2.0"
version = "0.2.3"
authors = ["Valentine Wallace <vwallace@protonmail.com>"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning"
Expand Down
2 changes: 1 addition & 1 deletion lightning-custom-message/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning-custom-message"
version = "0.2.0"
version = "0.2.3"
authors = ["Jeffrey Czyz"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning"
Expand Down
2 changes: 1 addition & 1 deletion lightning-dns-resolver/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning-dns-resolver"
version = "0.3.0"
version = "0.3.1"
authors = ["Matt Corallo"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning/"
Expand Down
2 changes: 1 addition & 1 deletion lightning-invoice/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
[package]
name = "lightning-invoice"
description = "Data structures to parse and serialize BOLT11 lightning invoices"
version = "0.34.0"
version = "0.34.1"
authors = ["Sebastian Geisler <sgeisler@wh2.tu-dresden.de>"]
documentation = "https://docs.rs/lightning-invoice/"
license = "MIT OR Apache-2.0"
Expand Down
53 changes: 48 additions & 5 deletions lightning-invoice/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1498,17 +1498,22 @@ impl Bolt11Invoice {
self.signed_invoice.features()
}

/// Recover the payee's public key (only to be used if none was included in the invoice)
/// Get the invoice's payee public key.
///
/// This uses the explicitly included payee public key, if present, otherwise it recovers the
/// payee public key from the signature. Prefer [`Self::get_payee_pub_key`] for clarity.
pub fn recover_payee_pub_key(&self) -> PublicKey {
self.signed_invoice.recover_payee_pub_key().expect("was checked by constructor").0
self.get_payee_pub_key()
}

/// Recover the payee's public key if one was included in the invoice, otherwise return the
/// recovered public key from the signature
/// Get the invoice's payee public key, preferring an explicitly included payee public key and
/// falling back to recovering the key from the signature.
pub fn get_payee_pub_key(&self) -> PublicKey {
match self.payee_pub_key() {
Some(pk) => *pk,
None => self.recover_payee_pub_key(),
None => {
self.signed_invoice.recover_payee_pub_key().expect("was checked by constructor").0
},
}
}

Expand DownExpand Up@@ -2044,6 +2049,44 @@ mod test {
assert!(new_signed.check_signature());
}

#[test]
fn recover_payee_pub_key_uses_included_payee_pub_key() {
use crate::*;
use bitcoin::secp256k1::ecdsa::{RecoverableSignature, RecoveryId};
use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};
use core::time::Duration;

let secp_ctx = Secp256k1::new();
let private_key = SecretKey::from_slice(&[42; 32]).unwrap();
let public_key = PublicKey::from_secret_key(&secp_ctx, &private_key);

let invoice = InvoiceBuilder::new(Currency::Bitcoin)
.description("Test".to_string())
.payment_hash(sha256::Hash::from_slice(&[0; 32][..]).unwrap())
.payment_secret(PaymentSecret([21; 32]))
.payee_pub_key(public_key)
.min_final_cltv_expiry_delta(144)
.duration_since_epoch(Duration::from_secs(1234567))
.build_signed(|hash| secp_ctx.sign_ecdsa_recoverable(hash, &private_key))
.unwrap();

let signed_raw = invoice.into_signed_raw();
let (raw_invoice, hash, signature) = signed_raw.into_parts();
let (_orig_rid, sig_bytes) = signature.0.serialize_compact();
let bad_rid = RecoveryId::from_i32(2).unwrap();
let bad_sig = RecoverableSignature::from_compact(&sig_bytes, bad_rid).unwrap();
let bad_signed_raw = SignedRawBolt11Invoice {
raw_invoice,
hash,
signature: Bolt11InvoiceSignature(bad_sig),
};
let bad_invoice = Bolt11Invoice::from_signed(bad_signed_raw).unwrap();

assert_eq!(bad_invoice.payee_pub_key(), Some(&public_key));
assert_eq!(bad_invoice.recover_payee_pub_key(), public_key);
assert_eq!(bad_invoice.get_payee_pub_key(), public_key);
}

#[test]
fn test_check_feature_bits() {
use crate::TaggedField::*;
Expand Down
2 changes: 1 addition & 1 deletion lightning-liquidity/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning-liquidity"
version = "0.2.0"
version = "0.2.3"
authors = ["John Cantrell <johncantrell97@gmail.com>", "Elias Rohrer <dev@tnull.de>"]
homepage = "https://lightningdevkit.org/"
license = "MIT OR Apache-2.0"
Expand Down
2 changes: 1 addition & 1 deletion lightning-persister/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning-persister"
version = "0.2.0"
version = "0.2.3"
authors = ["Valentine Wallace", "Matt Corallo"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning"
Expand Down
2 changes: 1 addition & 1 deletion lightning-rapid-gossip-sync/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning-rapid-gossip-sync"
version = "0.2.0"
version = "0.2.3"
authors = ["Arik Sosman <git@arik.io>"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning"
Expand Down
12 changes: 12 additions & 0 deletions lightning-rapid-gossip-sync/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -153,6 +153,10 @@ where
/// Sync gossip data from a file.
/// Returns the last sync timestamp to be used the next time rapid sync data is queried.
///
/// You should consider the gossip data source as semi-trusted. It is generally the case that it
/// can DoS the client either by omitting data which leads to pathfinding failure or by bloating
/// the graph such that it leads to eventual OOM on the client.
///
/// `network_graph`: The network graph to apply the updates to
///
/// `sync_path`: Path to the file where the gossip update data is located
Expand All@@ -172,6 +176,10 @@ where
/// Update network graph from binary data.
/// Returns the last sync timestamp to be used the next time rapid sync data is queried.
///
/// You should consider the gossip data source as semi-trusted. It is generally the case that it
/// can DoS the client either by omitting data which leads to pathfinding failure or by bloating
/// the graph such that it leads to eventual OOM on the client.
///
/// `update_data`: `&[u8]` binary stream that comprises the update data
#[cfg(feature = "std")]
pub fn update_network_graph(&self, update_data: &[u8]) -> Result<u32, GraphSyncError> {
Expand All@@ -182,6 +190,10 @@ where
/// Update network graph from binary data.
/// Returns the last sync timestamp to be used the next time rapid sync data is queried.
///
/// You should consider the gossip data source as semi-trusted. It is generally the case that it
/// can DoS the client either by omitting data which leads to pathfinding failure or by bloating
/// the graph such that it leads to eventual OOM on the client.
///
/// `update_data`: `&[u8]` binary stream that comprises the update data
/// `current_time_unix`: `Option<u64>` optional current timestamp to verify data age
pub fn update_network_graph_no_std(
Expand Down
34 changes: 30 additions & 4 deletions lightning-rapid-gossip-sync/src/processing.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,7 +9,9 @@ use lightning::ln::msgs::{
DecodeError, ErrorAction, LightningError, SocketAddress, UnsignedChannelUpdate,
UnsignedNodeAnnouncement,
};
use lightning::routing::gossip::{NetworkGraph, NodeAlias, NodeId};
use lightning::routing::gossip::{
NetworkGraph, NodeAlias, NodeId, CHAN_COUNT_ESTIMATE, NODE_COUNT_ESTIMATE,
};
use lightning::util::logger::Logger;
use lightning::util::ser::{BigSize, FixedLengthReader, Readable};
use lightning::{log_debug, log_given_level, log_gossip, log_trace, log_warn};
Expand DownExpand Up@@ -115,17 +117,27 @@ where
}
};

const MAX_NODE_COUNT: u32 = (NODE_COUNT_ESTIMATE as u32) * 10;
const MAX_CHANNEL_COUNT: u64 = (CHAN_COUNT_ESTIMATE as u64) * 10;

let node_id_count: u32 = Readable::read(read_cursor)?;
if node_id_count > MAX_NODE_COUNT {
return Err(LightningError {
err: "RGS data contained nonsense number of nodes to update".to_owned(),
action: ErrorAction::IgnoreError,
}
.into());
}
let mut node_ids: Vec<NodeId> = Vec::with_capacity(core::cmp::min(
node_id_count,
MAX_INITIAL_NODE_ID_VECTOR_CAPACITY,
) as usize);

let network_graph = &self.network_graph;
let mut node_modifications: Vec<UnsignedNodeAnnouncement> = Vec::new();

let read_only_network_graph = network_graph.read_only();

if parse_node_details {
let read_only_network_graph = network_graph.read_only();
for _ in 0..node_id_count {
let mut pubkey_bytes = [0u8; 33];
read_cursor.read_exact(&mut pubkey_bytes)?;
Expand DownExpand Up@@ -237,9 +249,12 @@ where
}
}

let original_graph_channel_count = read_only_network_graph.channels().len() as u32;
core::mem::drop(read_only_network_graph);

let mut previous_scid: u64 = 0;
let announcement_count: u32 = Readable::read(read_cursor)?;
for _ in 0..announcement_count {
for i in 0..announcement_count {
let features = Readable::read(read_cursor)?;

// handle SCID
Expand DownExpand Up@@ -284,6 +299,10 @@ where
}
}

if (original_graph_channel_count as u64) + (i as u64) > MAX_CHANNEL_COUNT {
continue;
}

let announcement_result = network_graph.add_channel_from_partial_announcement(
short_channel_id,
funding_sats,
Expand DownExpand Up@@ -329,6 +348,13 @@ where
previous_scid = 0;

let update_count: u32 = Readable::read(read_cursor)?;
if update_count as u64 > MAX_CHANNEL_COUNT {
return Err(LightningError {
err: "RGS data contained nonsense number of channels to update".to_owned(),
action: ErrorAction::IgnoreError,
}
.into());
}
log_debug!(self.logger, "Processing RGS update from {} with {} nodes, {} channel announcements and {} channel updates.",
latest_seen_timestamp, node_id_count, announcement_count, update_count);
if update_count == 0 {
Expand Down
2 changes: 1 addition & 1 deletion lightning-types/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning-types"
version = "0.3.1"
version = "0.3.2"
authors = ["Matt Corallo"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning/"
Expand Down
2 changes: 1 addition & 1 deletion lightning/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning"
version = "0.2.2"
version = "0.2.3"
authors = ["Matt Corallo"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning/"
Expand Down
9 changes: 6 additions & 3 deletions lightning/src/ln/channel.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1959,9 +1959,12 @@ where

let tx_abort = should_ack.then(|| {
let logger = WithChannelContext::from(logger, &self.context(), None);
let reason =
types::string::UntrustedString(String::from_utf8_lossy(&msg.data).to_string());
log_info!(logger, "Counterparty failed interactive transaction negotiation: {reason}");
let reason = String::from_utf8_lossy(&msg.data);
log_info!(
logger,
"Counterparty failed interactive transaction negotiation: {}",
log_msg!(reason)
);
msgs::TxAbort {
channel_id: msg.channel_id,
data: "Acknowledged tx_abort".to_string().into_bytes(),
Expand Down
4 changes: 2 additions & 2 deletions lightning/src/ln/invoice_utils.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1288,7 +1288,7 @@ mod test {
assert!(!invoice.features().unwrap().supports_basic_mpp());

let payment_params = PaymentParameters::from_node_id(
invoice.recover_payee_pub_key(),
invoice.get_payee_pub_key(),
invoice.min_final_cltv_expiry_delta() as u32,
)
.with_bolt11_features(invoice.features().unwrap().clone())
Expand DownExpand Up@@ -1350,7 +1350,7 @@ mod test {
payment_secret,
payment_amt,
payment_preimage_opt,
invoice.recover_payee_pub_key(),
invoice.get_payee_pub_key(),
);
do_claim_payment_along_route(ClaimAlongRouteArgs::new(
&nodes[0],
Expand Down
13 changes: 6 additions & 7 deletions lightning/src/ln/peer_handler.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -45,7 +45,6 @@ use crate::onion_message::packet::OnionMessageContents;
use crate::routing::gossip::{NodeAlias, NodeId};
use crate::sign::{NodeSigner, Recipient};
use crate::types::features::{InitFeatures, NodeFeatures};
use crate::types::string::PrintableString;
use crate::util::atomic_counter::AtomicCounter;
use crate::util::logger::{Level, Logger, WithContext};
use crate::util::ser::{VecWriter, Writeable, Writer};
Expand DownExpand Up@@ -2415,7 +2414,7 @@ where
logger,
"Got Err message from {}: {}",
their_node_id,
PrintableString(&msg.data)
log_msg!(msg.data)
);
self.message_handler.chan_handler.handle_error(their_node_id, &msg);
if msg.channel_id.is_zero() {
Expand All@@ -2427,7 +2426,7 @@ where
logger,
"Got warning message from {}: {}",
their_node_id,
PrintableString(&msg.data)
log_msg!(msg.data)
);
},

Expand DownExpand Up@@ -3213,7 +3212,7 @@ where
msgs::ErrorAction::DisconnectPeer { msg } => {
if let Some(msg) = msg.as_ref() {
log_trace!(logger, "Handling DisconnectPeer HandleError event in peer_handler for node {} with message {}",
node_id, msg.data);
node_id, log_msg!(msg.data));
} else {
log_trace!(logger, "Handling DisconnectPeer HandleError event in peer_handler for node {}",
node_id);
Expand All@@ -3228,7 +3227,7 @@ where
},
msgs::ErrorAction::DisconnectPeerWithWarning { msg } => {
log_trace!(logger, "Handling DisconnectPeer HandleError event in peer_handler for node {} with message {}",
node_id, msg.data);
node_id, log_msg!(msg.data));
// We do not have the peers write lock, so we just store that we're
// about to disconnect the peer and do it after we finish
// processing most messages.
Expand All@@ -3254,7 +3253,7 @@ where
msgs::ErrorAction::SendErrorMessage { ref msg } => {
log_trace!(logger, "Handling SendErrorMessage HandleError event in peer_handler for node {} with message {}",
node_id,
msg.data);
log_msg!(msg.data));
self.enqueue_message(
&mut *get_peer_for_forwarding!(&node_id)?,
msg,
Expand All@@ -3266,7 +3265,7 @@ where
} => {
log_given_level!(logger, *log_level, "Handling SendWarningMessage HandleError event in peer_handler for node {} with message {}",
node_id,
msg.data);
log_msg!(msg.data));
self.enqueue_message(
&mut *get_peer_for_forwarding!(&node_id)?,
msg,
Expand Down
3 changes: 2 additions & 1 deletion lightning/src/onion_message/dns_resolution.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -520,7 +520,8 @@ impl OMNameResolver {
.filter_map(|data| String::from_utf8(data).ok())
.filter(|data_string| data_string.len() > URI_PREFIX.len())
.filter(|data_string| {
data_string[..URI_PREFIX.len()].eq_ignore_ascii_case(URI_PREFIX)
let pfx = &data_string.as_bytes()[..URI_PREFIX.len()];
pfx.eq_ignore_ascii_case(URI_PREFIX.as_bytes())
});
// Check that there is exactly one TXT record that begins with
// bitcoin: as required by BIP 353 (and is valid UTF-8).
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
21 changes: 20 additions & 1 deletion CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -71,7 +71,19 @@

## Security
0.2.3 fixes several underestimates of the anchor reserves required to ensure we
can reliably close channels and a sanitization issue.
can reliably close channels, several denial-of-service vulnerabilities and a
sanitization issue.
* `Bolt11Invoice::recover_payee_pub_key` no longer panics if called on an
invoice which set an explicit public key, rather than relying on public key
recovery. Note that this method is called from
`PaymentParameters::from_bolt11_invoice` (#4717).
* Maliciously-crafted unpayable invoices which have overflowing feerates will
no longer cause an `unwrap` failure panic (#4716).
* `possiblyrandom` did not properly generate random data except when it was
explicitly configured to. By default this means LDK is vulnerable to various
HashDoS attacks (#4719).
* `OMNameResolver` will no longer panic when looking up payment instructions
which include unicode characters at the start of a TXT record (#4718).
* When using the `anchor_channel_reserves` module to calculate reserves
required to pay for fees when closing anchor channels, zero-fee-commitment
channels were not considered. This could allow a counterparty to open many
Expand All@@ -80,6 +92,13 @@ can reliably close channels and a sanitization issue.
the wallet by ignoring the `TxIn` cost to spend them (#4670).
* `PrintableString` did not properly sanitize unicode format characters,
allowing an attacker to corrupt the rendering of logs or UI (#4593, #4605).
* RGS data is now limited in how large of a graph it is able to cause a client
to store in memory. Note that RGS data is still considered a DoS vector in
general and you should only use semi-trusted RGS data (#4713).
* Counterparty-provided strings in failure messages are no longer logged in
full, reducing the ability of such a counterparty to spam our logs (#4714).
* Reading a corrupted `ChannelManager` or `ProbabilisticScorer` can no longer
cause us to allocate large amounts of memory (#4712).

Thanks to Project Loupe for reporting most of the issues fixed in this release.

Expand Down
2 changes: 1 addition & 1 deletion lightning-background-processor/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning-background-processor"
version = "0.2.0"
version = "0.2.3"
authors = ["Valentine Wallace <vwallace@protonmail.com>"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning"
Expand Down
2 changes: 1 addition & 1 deletion lightning-custom-message/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning-custom-message"
version = "0.2.0"
version = "0.2.3"
authors = ["Jeffrey Czyz"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning"
Expand Down
2 changes: 1 addition & 1 deletion lightning-dns-resolver/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning-dns-resolver"
version = "0.3.0"
version = "0.3.1"
authors = ["Matt Corallo"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning/"
Expand Down
2 changes: 1 addition & 1 deletion lightning-invoice/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
[package]
name = "lightning-invoice"
description = "Data structures to parse and serialize BOLT11 lightning invoices"
version = "0.34.0"
version = "0.34.1"
authors = ["Sebastian Geisler <sgeisler@wh2.tu-dresden.de>"]
documentation = "https://docs.rs/lightning-invoice/"
license = "MIT OR Apache-2.0"
Expand Down
53 changes: 48 additions & 5 deletions lightning-invoice/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1498,17 +1498,22 @@ impl Bolt11Invoice {
self.signed_invoice.features()
}

/// Recover the payee's public key (only to be used if none was included in the invoice)
/// Get the invoice's payee public key.
///
/// This uses the explicitly included payee public key, if present, otherwise it recovers the
/// payee public key from the signature. Prefer [`Self::get_payee_pub_key`] for clarity.
pub fn recover_payee_pub_key(&self) -> PublicKey {
self.signed_invoice.recover_payee_pub_key().expect("was checked by constructor").0
self.get_payee_pub_key()
}

/// Recover the payee's public key if one was included in the invoice, otherwise return the
/// recovered public key from the signature
/// Get the invoice's payee public key, preferring an explicitly included payee public key and
/// falling back to recovering the key from the signature.
pub fn get_payee_pub_key(&self) -> PublicKey {
match self.payee_pub_key() {
Some(pk) => *pk,
None => self.recover_payee_pub_key(),
None => {
self.signed_invoice.recover_payee_pub_key().expect("was checked by constructor").0
},
}
}

Expand DownExpand Up@@ -2044,6 +2049,44 @@ mod test {
assert!(new_signed.check_signature());
}

#[test]
fn recover_payee_pub_key_uses_included_payee_pub_key() {
use crate::*;
use bitcoin::secp256k1::ecdsa::{RecoverableSignature, RecoveryId};
use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};
use core::time::Duration;

let secp_ctx = Secp256k1::new();
let private_key = SecretKey::from_slice(&[42; 32]).unwrap();
let public_key = PublicKey::from_secret_key(&secp_ctx, &private_key);

let invoice = InvoiceBuilder::new(Currency::Bitcoin)
.description("Test".to_string())
.payment_hash(sha256::Hash::from_slice(&[0; 32][..]).unwrap())
.payment_secret(PaymentSecret([21; 32]))
.payee_pub_key(public_key)
.min_final_cltv_expiry_delta(144)
.duration_since_epoch(Duration::from_secs(1234567))
.build_signed(|hash| secp_ctx.sign_ecdsa_recoverable(hash, &private_key))
.unwrap();

let signed_raw = invoice.into_signed_raw();
let (raw_invoice, hash, signature) = signed_raw.into_parts();
let (_orig_rid, sig_bytes) = signature.0.serialize_compact();
let bad_rid = RecoveryId::from_i32(2).unwrap();
let bad_sig = RecoverableSignature::from_compact(&sig_bytes, bad_rid).unwrap();
let bad_signed_raw = SignedRawBolt11Invoice {
raw_invoice,
hash,
signature: Bolt11InvoiceSignature(bad_sig),
};
let bad_invoice = Bolt11Invoice::from_signed(bad_signed_raw).unwrap();

assert_eq!(bad_invoice.payee_pub_key(), Some(&public_key));
assert_eq!(bad_invoice.recover_payee_pub_key(), public_key);
assert_eq!(bad_invoice.get_payee_pub_key(), public_key);
}

#[test]
fn test_check_feature_bits() {
use crate::TaggedField::*;
Expand Down
2 changes: 1 addition & 1 deletion lightning-liquidity/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning-liquidity"
version = "0.2.0"
version = "0.2.3"
authors = ["John Cantrell <johncantrell97@gmail.com>", "Elias Rohrer <dev@tnull.de>"]
homepage = "https://lightningdevkit.org/"
license = "MIT OR Apache-2.0"
Expand Down
2 changes: 1 addition & 1 deletion lightning-persister/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning-persister"
version = "0.2.0"
version = "0.2.3"
authors = ["Valentine Wallace", "Matt Corallo"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning"
Expand Down
2 changes: 1 addition & 1 deletion lightning-rapid-gossip-sync/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning-rapid-gossip-sync"
version = "0.2.0"
version = "0.2.3"
authors = ["Arik Sosman <git@arik.io>"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning"
Expand Down
12 changes: 12 additions & 0 deletions lightning-rapid-gossip-sync/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -153,6 +153,10 @@ where
/// Sync gossip data from a file.
/// Returns the last sync timestamp to be used the next time rapid sync data is queried.
///
/// You should consider the gossip data source as semi-trusted. It is generally the case that it
/// can DoS the client either by omitting data which leads to pathfinding failure or by bloating
/// the graph such that it leads to eventual OOM on the client.
///
/// `network_graph`: The network graph to apply the updates to
///
/// `sync_path`: Path to the file where the gossip update data is located
Expand All@@ -172,6 +176,10 @@ where
/// Update network graph from binary data.
/// Returns the last sync timestamp to be used the next time rapid sync data is queried.
///
/// You should consider the gossip data source as semi-trusted. It is generally the case that it
/// can DoS the client either by omitting data which leads to pathfinding failure or by bloating
/// the graph such that it leads to eventual OOM on the client.
///
/// `update_data`: `&[u8]` binary stream that comprises the update data
#[cfg(feature = "std")]
pub fn update_network_graph(&self, update_data: &[u8]) -> Result<u32, GraphSyncError> {
Expand All@@ -182,6 +190,10 @@ where
/// Update network graph from binary data.
/// Returns the last sync timestamp to be used the next time rapid sync data is queried.
///
/// You should consider the gossip data source as semi-trusted. It is generally the case that it
/// can DoS the client either by omitting data which leads to pathfinding failure or by bloating
/// the graph such that it leads to eventual OOM on the client.
///
/// `update_data`: `&[u8]` binary stream that comprises the update data
/// `current_time_unix`: `Option<u64>` optional current timestamp to verify data age
pub fn update_network_graph_no_std(
Expand Down
34 changes: 30 additions & 4 deletions lightning-rapid-gossip-sync/src/processing.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,7 +9,9 @@ use lightning::ln::msgs::{
DecodeError, ErrorAction, LightningError, SocketAddress, UnsignedChannelUpdate,
UnsignedNodeAnnouncement,
};
use lightning::routing::gossip::{NetworkGraph, NodeAlias, NodeId};
use lightning::routing::gossip::{
NetworkGraph, NodeAlias, NodeId, CHAN_COUNT_ESTIMATE, NODE_COUNT_ESTIMATE,
};
use lightning::util::logger::Logger;
use lightning::util::ser::{BigSize, FixedLengthReader, Readable};
use lightning::{log_debug, log_given_level, log_gossip, log_trace, log_warn};
Expand DownExpand Up@@ -115,17 +117,27 @@ where
}
};

const MAX_NODE_COUNT: u32 = (NODE_COUNT_ESTIMATE as u32) * 10;
const MAX_CHANNEL_COUNT: u64 = (CHAN_COUNT_ESTIMATE as u64) * 10;

let node_id_count: u32 = Readable::read(read_cursor)?;
if node_id_count > MAX_NODE_COUNT {
return Err(LightningError {
err: "RGS data contained nonsense number of nodes to update".to_owned(),
action: ErrorAction::IgnoreError,
}
.into());
}
let mut node_ids: Vec<NodeId> = Vec::with_capacity(core::cmp::min(
node_id_count,
MAX_INITIAL_NODE_ID_VECTOR_CAPACITY,
) as usize);

let network_graph = &self.network_graph;
let mut node_modifications: Vec<UnsignedNodeAnnouncement> = Vec::new();

let read_only_network_graph = network_graph.read_only();

if parse_node_details {
let read_only_network_graph = network_graph.read_only();
for _ in 0..node_id_count {
let mut pubkey_bytes = [0u8; 33];
read_cursor.read_exact(&mut pubkey_bytes)?;
Expand DownExpand Up@@ -237,9 +249,12 @@ where
}
}

let original_graph_channel_count = read_only_network_graph.channels().len() as u32;
core::mem::drop(read_only_network_graph);

let mut previous_scid: u64 = 0;
let announcement_count: u32 = Readable::read(read_cursor)?;
for _ in 0..announcement_count {
for i in 0..announcement_count {
let features = Readable::read(read_cursor)?;

// handle SCID
Expand DownExpand Up@@ -284,6 +299,10 @@ where
}
}

if (original_graph_channel_count as u64) + (i as u64) > MAX_CHANNEL_COUNT {
continue;
}

let announcement_result = network_graph.add_channel_from_partial_announcement(
short_channel_id,
funding_sats,
Expand DownExpand Up@@ -329,6 +348,13 @@ where
previous_scid = 0;

let update_count: u32 = Readable::read(read_cursor)?;
if update_count as u64 > MAX_CHANNEL_COUNT {
return Err(LightningError {
err: "RGS data contained nonsense number of channels to update".to_owned(),
action: ErrorAction::IgnoreError,
}
.into());
}
log_debug!(self.logger, "Processing RGS update from {} with {} nodes, {} channel announcements and {} channel updates.",
latest_seen_timestamp, node_id_count, announcement_count, update_count);
if update_count == 0 {
Expand Down
2 changes: 1 addition & 1 deletion lightning-types/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning-types"
version = "0.3.1"
version = "0.3.2"
authors = ["Matt Corallo"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning/"
Expand Down
2 changes: 1 addition & 1 deletion lightning/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning"
version = "0.2.2"
version = "0.2.3"
authors = ["Matt Corallo"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning/"
Expand Down
9 changes: 6 additions & 3 deletions lightning/src/ln/channel.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1959,9 +1959,12 @@ where

let tx_abort = should_ack.then(|| {
let logger = WithChannelContext::from(logger, &self.context(), None);
let reason =
types::string::UntrustedString(String::from_utf8_lossy(&msg.data).to_string());
log_info!(logger, "Counterparty failed interactive transaction negotiation: {reason}");
let reason = String::from_utf8_lossy(&msg.data);
log_info!(
logger,
"Counterparty failed interactive transaction negotiation: {}",
log_msg!(reason)
);
msgs::TxAbort {
channel_id: msg.channel_id,
data: "Acknowledged tx_abort".to_string().into_bytes(),
Expand Down
4 changes: 2 additions & 2 deletions lightning/src/ln/invoice_utils.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1288,7 +1288,7 @@ mod test {
assert!(!invoice.features().unwrap().supports_basic_mpp());

let payment_params = PaymentParameters::from_node_id(
invoice.recover_payee_pub_key(),
invoice.get_payee_pub_key(),
invoice.min_final_cltv_expiry_delta() as u32,
)
.with_bolt11_features(invoice.features().unwrap().clone())
Expand DownExpand Up@@ -1350,7 +1350,7 @@ mod test {
payment_secret,
payment_amt,
payment_preimage_opt,
invoice.recover_payee_pub_key(),
invoice.get_payee_pub_key(),
);
do_claim_payment_along_route(ClaimAlongRouteArgs::new(
&nodes[0],
Expand Down
13 changes: 6 additions & 7 deletions lightning/src/ln/peer_handler.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -45,7 +45,6 @@ use crate::onion_message::packet::OnionMessageContents;
use crate::routing::gossip::{NodeAlias, NodeId};
use crate::sign::{NodeSigner, Recipient};
use crate::types::features::{InitFeatures, NodeFeatures};
use crate::types::string::PrintableString;
use crate::util::atomic_counter::AtomicCounter;
use crate::util::logger::{Level, Logger, WithContext};
use crate::util::ser::{VecWriter, Writeable, Writer};
Expand DownExpand Up@@ -2415,7 +2414,7 @@ where
logger,
"Got Err message from {}: {}",
their_node_id,
PrintableString(&msg.data)
log_msg!(msg.data)
);
self.message_handler.chan_handler.handle_error(their_node_id, &msg);
if msg.channel_id.is_zero() {
Expand All@@ -2427,7 +2426,7 @@ where
logger,
"Got warning message from {}: {}",
their_node_id,
PrintableString(&msg.data)
log_msg!(msg.data)
);
},

Expand DownExpand Up@@ -3213,7 +3212,7 @@ where
msgs::ErrorAction::DisconnectPeer { msg } => {
if let Some(msg) = msg.as_ref() {
log_trace!(logger, "Handling DisconnectPeer HandleError event in peer_handler for node {} with message {}",
node_id, msg.data);
node_id, log_msg!(msg.data));
} else {
log_trace!(logger, "Handling DisconnectPeer HandleError event in peer_handler for node {}",
node_id);
Expand All@@ -3228,7 +3227,7 @@ where
},
msgs::ErrorAction::DisconnectPeerWithWarning { msg } => {
log_trace!(logger, "Handling DisconnectPeer HandleError event in peer_handler for node {} with message {}",
node_id, msg.data);
node_id, log_msg!(msg.data));
// We do not have the peers write lock, so we just store that we're
// about to disconnect the peer and do it after we finish
// processing most messages.
Expand All@@ -3254,7 +3253,7 @@ where
msgs::ErrorAction::SendErrorMessage { ref msg } => {
log_trace!(logger, "Handling SendErrorMessage HandleError event in peer_handler for node {} with message {}",
node_id,
msg.data);
log_msg!(msg.data));
self.enqueue_message(
&mut *get_peer_for_forwarding!(&node_id)?,
msg,
Expand All@@ -3266,7 +3265,7 @@ where
} => {
log_given_level!(logger, *log_level, "Handling SendWarningMessage HandleError event in peer_handler for node {} with message {}",
node_id,
msg.data);
log_msg!(msg.data));
self.enqueue_message(
&mut *get_peer_for_forwarding!(&node_id)?,
msg,
Expand Down
3 changes: 2 additions & 1 deletion lightning/src/onion_message/dns_resolution.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -520,7 +520,8 @@ impl OMNameResolver {
.filter_map(|data| String::from_utf8(data).ok())
.filter(|data_string| data_string.len() > URI_PREFIX.len())
.filter(|data_string| {
data_string[..URI_PREFIX.len()].eq_ignore_ascii_case(URI_PREFIX)
let pfx = &data_string.as_bytes()[..URI_PREFIX.len()];
pfx.eq_ignore_ascii_case(URI_PREFIX.as_bytes())
});
// Check that there is exactly one TXT record that begins with
// bitcoin: as required by BIP 353 (and is valid UTF-8).
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
21 changes: 20 additions & 1 deletion CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -71,7 +71,19 @@

## Security
0.2.3 fixes several underestimates of the anchor reserves required to ensure we
can reliably close channels and a sanitization issue.
can reliably close channels, several denial-of-service vulnerabilities and a
sanitization issue.
* `Bolt11Invoice::recover_payee_pub_key` no longer panics if called on an
invoice which set an explicit public key, rather than relying on public key
recovery. Note that this method is called from
`PaymentParameters::from_bolt11_invoice` (#4717).
* Maliciously-crafted unpayable invoices which have overflowing feerates will
no longer cause an `unwrap` failure panic (#4716).
* `possiblyrandom` did not properly generate random data except when it was
explicitly configured to. By default this means LDK is vulnerable to various
HashDoS attacks (#4719).
* `OMNameResolver` will no longer panic when looking up payment instructions
which include unicode characters at the start of a TXT record (#4718).
* When using the `anchor_channel_reserves` module to calculate reserves
required to pay for fees when closing anchor channels, zero-fee-commitment
channels were not considered. This could allow a counterparty to open many
Expand All@@ -80,6 +92,13 @@ can reliably close channels and a sanitization issue.
the wallet by ignoring the `TxIn` cost to spend them (#4670).
* `PrintableString` did not properly sanitize unicode format characters,
allowing an attacker to corrupt the rendering of logs or UI (#4593, #4605).
* RGS data is now limited in how large of a graph it is able to cause a client
to store in memory. Note that RGS data is still considered a DoS vector in
general and you should only use semi-trusted RGS data (#4713).
* Counterparty-provided strings in failure messages are no longer logged in
full, reducing the ability of such a counterparty to spam our logs (#4714).
* Reading a corrupted `ChannelManager` or `ProbabilisticScorer` can no longer
cause us to allocate large amounts of memory (#4712).

Thanks to Project Loupe for reporting most of the issues fixed in this release.

Expand Down
2 changes: 1 addition & 1 deletion lightning-background-processor/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning-background-processor"
version = "0.2.0"
version = "0.2.3"
authors = ["Valentine Wallace <vwallace@protonmail.com>"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning"
Expand Down
2 changes: 1 addition & 1 deletion lightning-custom-message/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning-custom-message"
version = "0.2.0"
version = "0.2.3"
authors = ["Jeffrey Czyz"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning"
Expand Down
2 changes: 1 addition & 1 deletion lightning-dns-resolver/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning-dns-resolver"
version = "0.3.0"
version = "0.3.1"
authors = ["Matt Corallo"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning/"
Expand Down
2 changes: 1 addition & 1 deletion lightning-invoice/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
[package]
name = "lightning-invoice"
description = "Data structures to parse and serialize BOLT11 lightning invoices"
version = "0.34.0"
version = "0.34.1"
authors = ["Sebastian Geisler <sgeisler@wh2.tu-dresden.de>"]
documentation = "https://docs.rs/lightning-invoice/"
license = "MIT OR Apache-2.0"
Expand Down
53 changes: 48 additions & 5 deletions lightning-invoice/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1498,17 +1498,22 @@ impl Bolt11Invoice {
self.signed_invoice.features()
}

/// Recover the payee's public key (only to be used if none was included in the invoice)
/// Get the invoice's payee public key.
///
/// This uses the explicitly included payee public key, if present, otherwise it recovers the
/// payee public key from the signature. Prefer [`Self::get_payee_pub_key`] for clarity.
pub fn recover_payee_pub_key(&self) -> PublicKey {
self.signed_invoice.recover_payee_pub_key().expect("was checked by constructor").0
self.get_payee_pub_key()
}

/// Recover the payee's public key if one was included in the invoice, otherwise return the
/// recovered public key from the signature
/// Get the invoice's payee public key, preferring an explicitly included payee public key and
/// falling back to recovering the key from the signature.
pub fn get_payee_pub_key(&self) -> PublicKey {
match self.payee_pub_key() {
Some(pk) => *pk,
None => self.recover_payee_pub_key(),
None => {
self.signed_invoice.recover_payee_pub_key().expect("was checked by constructor").0
},
}
}

Expand DownExpand Up@@ -2044,6 +2049,44 @@ mod test {
assert!(new_signed.check_signature());
}

#[test]
fn recover_payee_pub_key_uses_included_payee_pub_key() {
use crate::*;
use bitcoin::secp256k1::ecdsa::{RecoverableSignature, RecoveryId};
use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};
use core::time::Duration;

let secp_ctx = Secp256k1::new();
let private_key = SecretKey::from_slice(&[42; 32]).unwrap();
let public_key = PublicKey::from_secret_key(&secp_ctx, &private_key);

let invoice = InvoiceBuilder::new(Currency::Bitcoin)
.description("Test".to_string())
.payment_hash(sha256::Hash::from_slice(&[0; 32][..]).unwrap())
.payment_secret(PaymentSecret([21; 32]))
.payee_pub_key(public_key)
.min_final_cltv_expiry_delta(144)
.duration_since_epoch(Duration::from_secs(1234567))
.build_signed(|hash| secp_ctx.sign_ecdsa_recoverable(hash, &private_key))
.unwrap();

let signed_raw = invoice.into_signed_raw();
let (raw_invoice, hash, signature) = signed_raw.into_parts();
let (_orig_rid, sig_bytes) = signature.0.serialize_compact();
let bad_rid = RecoveryId::from_i32(2).unwrap();
let bad_sig = RecoverableSignature::from_compact(&sig_bytes, bad_rid).unwrap();
let bad_signed_raw = SignedRawBolt11Invoice {
raw_invoice,
hash,
signature: Bolt11InvoiceSignature(bad_sig),
};
let bad_invoice = Bolt11Invoice::from_signed(bad_signed_raw).unwrap();

assert_eq!(bad_invoice.payee_pub_key(), Some(&public_key));
assert_eq!(bad_invoice.recover_payee_pub_key(), public_key);
assert_eq!(bad_invoice.get_payee_pub_key(), public_key);
}

#[test]
fn test_check_feature_bits() {
use crate::TaggedField::*;
Expand Down
2 changes: 1 addition & 1 deletion lightning-liquidity/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning-liquidity"
version = "0.2.0"
version = "0.2.3"
authors = ["John Cantrell <johncantrell97@gmail.com>", "Elias Rohrer <dev@tnull.de>"]
homepage = "https://lightningdevkit.org/"
license = "MIT OR Apache-2.0"
Expand Down
2 changes: 1 addition & 1 deletion lightning-persister/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning-persister"
version = "0.2.0"
version = "0.2.3"
authors = ["Valentine Wallace", "Matt Corallo"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning"
Expand Down
2 changes: 1 addition & 1 deletion lightning-rapid-gossip-sync/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning-rapid-gossip-sync"
version = "0.2.0"
version = "0.2.3"
authors = ["Arik Sosman <git@arik.io>"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning"
Expand Down
12 changes: 12 additions & 0 deletions lightning-rapid-gossip-sync/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -153,6 +153,10 @@ where
/// Sync gossip data from a file.
/// Returns the last sync timestamp to be used the next time rapid sync data is queried.
///
/// You should consider the gossip data source as semi-trusted. It is generally the case that it
/// can DoS the client either by omitting data which leads to pathfinding failure or by bloating
/// the graph such that it leads to eventual OOM on the client.
///
/// `network_graph`: The network graph to apply the updates to
///
/// `sync_path`: Path to the file where the gossip update data is located
Expand All@@ -172,6 +176,10 @@ where
/// Update network graph from binary data.
/// Returns the last sync timestamp to be used the next time rapid sync data is queried.
///
/// You should consider the gossip data source as semi-trusted. It is generally the case that it
/// can DoS the client either by omitting data which leads to pathfinding failure or by bloating
/// the graph such that it leads to eventual OOM on the client.
///
/// `update_data`: `&[u8]` binary stream that comprises the update data
#[cfg(feature = "std")]
pub fn update_network_graph(&self, update_data: &[u8]) -> Result<u32, GraphSyncError> {
Expand All@@ -182,6 +190,10 @@ where
/// Update network graph from binary data.
/// Returns the last sync timestamp to be used the next time rapid sync data is queried.
///
/// You should consider the gossip data source as semi-trusted. It is generally the case that it
/// can DoS the client either by omitting data which leads to pathfinding failure or by bloating
/// the graph such that it leads to eventual OOM on the client.
///
/// `update_data`: `&[u8]` binary stream that comprises the update data
/// `current_time_unix`: `Option<u64>` optional current timestamp to verify data age
pub fn update_network_graph_no_std(
Expand Down
34 changes: 30 additions & 4 deletions lightning-rapid-gossip-sync/src/processing.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,7 +9,9 @@ use lightning::ln::msgs::{
DecodeError, ErrorAction, LightningError, SocketAddress, UnsignedChannelUpdate,
UnsignedNodeAnnouncement,
};
use lightning::routing::gossip::{NetworkGraph, NodeAlias, NodeId};
use lightning::routing::gossip::{
NetworkGraph, NodeAlias, NodeId, CHAN_COUNT_ESTIMATE, NODE_COUNT_ESTIMATE,
};
use lightning::util::logger::Logger;
use lightning::util::ser::{BigSize, FixedLengthReader, Readable};
use lightning::{log_debug, log_given_level, log_gossip, log_trace, log_warn};
Expand DownExpand Up@@ -115,17 +117,27 @@ where
}
};

const MAX_NODE_COUNT: u32 = (NODE_COUNT_ESTIMATE as u32) * 10;
const MAX_CHANNEL_COUNT: u64 = (CHAN_COUNT_ESTIMATE as u64) * 10;

let node_id_count: u32 = Readable::read(read_cursor)?;
if node_id_count > MAX_NODE_COUNT {
return Err(LightningError {
err: "RGS data contained nonsense number of nodes to update".to_owned(),
action: ErrorAction::IgnoreError,
}
.into());
}
let mut node_ids: Vec<NodeId> = Vec::with_capacity(core::cmp::min(
node_id_count,
MAX_INITIAL_NODE_ID_VECTOR_CAPACITY,
) as usize);

let network_graph = &self.network_graph;
let mut node_modifications: Vec<UnsignedNodeAnnouncement> = Vec::new();

let read_only_network_graph = network_graph.read_only();

if parse_node_details {
let read_only_network_graph = network_graph.read_only();
for _ in 0..node_id_count {
let mut pubkey_bytes = [0u8; 33];
read_cursor.read_exact(&mut pubkey_bytes)?;
Expand DownExpand Up@@ -237,9 +249,12 @@ where
}
}

let original_graph_channel_count = read_only_network_graph.channels().len() as u32;
core::mem::drop(read_only_network_graph);

let mut previous_scid: u64 = 0;
let announcement_count: u32 = Readable::read(read_cursor)?;
for _ in 0..announcement_count {
for i in 0..announcement_count {
let features = Readable::read(read_cursor)?;

// handle SCID
Expand DownExpand Up@@ -284,6 +299,10 @@ where
}
}

if (original_graph_channel_count as u64) + (i as u64) > MAX_CHANNEL_COUNT {
continue;
}

let announcement_result = network_graph.add_channel_from_partial_announcement(
short_channel_id,
funding_sats,
Expand DownExpand Up@@ -329,6 +348,13 @@ where
previous_scid = 0;

let update_count: u32 = Readable::read(read_cursor)?;
if update_count as u64 > MAX_CHANNEL_COUNT {
return Err(LightningError {
err: "RGS data contained nonsense number of channels to update".to_owned(),
action: ErrorAction::IgnoreError,
}
.into());
}
log_debug!(self.logger, "Processing RGS update from {} with {} nodes, {} channel announcements and {} channel updates.",
latest_seen_timestamp, node_id_count, announcement_count, update_count);
if update_count == 0 {
Expand Down
2 changes: 1 addition & 1 deletion lightning-types/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning-types"
version = "0.3.1"
version = "0.3.2"
authors = ["Matt Corallo"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning/"
Expand Down
2 changes: 1 addition & 1 deletion lightning/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning"
version = "0.2.2"
version = "0.2.3"
authors = ["Matt Corallo"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning/"
Expand Down
9 changes: 6 additions & 3 deletions lightning/src/ln/channel.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1959,9 +1959,12 @@ where

let tx_abort = should_ack.then(|| {
let logger = WithChannelContext::from(logger, &self.context(), None);
let reason =
types::string::UntrustedString(String::from_utf8_lossy(&msg.data).to_string());
log_info!(logger, "Counterparty failed interactive transaction negotiation: {reason}");
let reason = String::from_utf8_lossy(&msg.data);
log_info!(
logger,
"Counterparty failed interactive transaction negotiation: {}",
log_msg!(reason)
);
msgs::TxAbort {
channel_id: msg.channel_id,
data: "Acknowledged tx_abort".to_string().into_bytes(),
Expand Down
4 changes: 2 additions & 2 deletions lightning/src/ln/invoice_utils.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1288,7 +1288,7 @@ mod test {
assert!(!invoice.features().unwrap().supports_basic_mpp());

let payment_params = PaymentParameters::from_node_id(
invoice.recover_payee_pub_key(),
invoice.get_payee_pub_key(),
invoice.min_final_cltv_expiry_delta() as u32,
)
.with_bolt11_features(invoice.features().unwrap().clone())
Expand DownExpand Up@@ -1350,7 +1350,7 @@ mod test {
payment_secret,
payment_amt,
payment_preimage_opt,
invoice.recover_payee_pub_key(),
invoice.get_payee_pub_key(),
);
do_claim_payment_along_route(ClaimAlongRouteArgs::new(
&nodes[0],
Expand Down
13 changes: 6 additions & 7 deletions lightning/src/ln/peer_handler.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -45,7 +45,6 @@ use crate::onion_message::packet::OnionMessageContents;
use crate::routing::gossip::{NodeAlias, NodeId};
use crate::sign::{NodeSigner, Recipient};
use crate::types::features::{InitFeatures, NodeFeatures};
use crate::types::string::PrintableString;
use crate::util::atomic_counter::AtomicCounter;
use crate::util::logger::{Level, Logger, WithContext};
use crate::util::ser::{VecWriter, Writeable, Writer};
Expand DownExpand Up@@ -2415,7 +2414,7 @@ where
logger,
"Got Err message from {}: {}",
their_node_id,
PrintableString(&msg.data)
log_msg!(msg.data)
);
self.message_handler.chan_handler.handle_error(their_node_id, &msg);
if msg.channel_id.is_zero() {
Expand All@@ -2427,7 +2426,7 @@ where
logger,
"Got warning message from {}: {}",
their_node_id,
PrintableString(&msg.data)
log_msg!(msg.data)
);
},

Expand DownExpand Up@@ -3213,7 +3212,7 @@ where
msgs::ErrorAction::DisconnectPeer { msg } => {
if let Some(msg) = msg.as_ref() {
log_trace!(logger, "Handling DisconnectPeer HandleError event in peer_handler for node {} with message {}",
node_id, msg.data);
node_id, log_msg!(msg.data));
} else {
log_trace!(logger, "Handling DisconnectPeer HandleError event in peer_handler for node {}",
node_id);
Expand All@@ -3228,7 +3227,7 @@ where
},
msgs::ErrorAction::DisconnectPeerWithWarning { msg } => {
log_trace!(logger, "Handling DisconnectPeer HandleError event in peer_handler for node {} with message {}",
node_id, msg.data);
node_id, log_msg!(msg.data));
// We do not have the peers write lock, so we just store that we're
// about to disconnect the peer and do it after we finish
// processing most messages.
Expand All@@ -3254,7 +3253,7 @@ where
msgs::ErrorAction::SendErrorMessage { ref msg } => {
log_trace!(logger, "Handling SendErrorMessage HandleError event in peer_handler for node {} with message {}",
node_id,
msg.data);
log_msg!(msg.data));
self.enqueue_message(
&mut *get_peer_for_forwarding!(&node_id)?,
msg,
Expand All@@ -3266,7 +3265,7 @@ where
} => {
log_given_level!(logger, *log_level, "Handling SendWarningMessage HandleError event in peer_handler for node {} with message {}",
node_id,
msg.data);
log_msg!(msg.data));
self.enqueue_message(
&mut *get_peer_for_forwarding!(&node_id)?,
msg,
Expand Down
3 changes: 2 additions & 1 deletion lightning/src/onion_message/dns_resolution.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -520,7 +520,8 @@ impl OMNameResolver {
.filter_map(|data| String::from_utf8(data).ok())
.filter(|data_string| data_string.len() > URI_PREFIX.len())
.filter(|data_string| {
data_string[..URI_PREFIX.len()].eq_ignore_ascii_case(URI_PREFIX)
let pfx = &data_string.as_bytes()[..URI_PREFIX.len()];
pfx.eq_ignore_ascii_case(URI_PREFIX.as_bytes())
});
// Check that there is exactly one TXT record that begins with
// bitcoin: as required by BIP 353 (and is valid UTF-8).
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
21 changes: 20 additions & 1 deletion CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -71,7 +71,19 @@

## Security
0.2.3 fixes several underestimates of the anchor reserves required to ensure we
can reliably close channels and a sanitization issue.
can reliably close channels, several denial-of-service vulnerabilities and a
sanitization issue.
* `Bolt11Invoice::recover_payee_pub_key` no longer panics if called on an
invoice which set an explicit public key, rather than relying on public key
recovery. Note that this method is called from
`PaymentParameters::from_bolt11_invoice` (#4717).
* Maliciously-crafted unpayable invoices which have overflowing feerates will
no longer cause an `unwrap` failure panic (#4716).
* `possiblyrandom` did not properly generate random data except when it was
explicitly configured to. By default this means LDK is vulnerable to various
HashDoS attacks (#4719).
* `OMNameResolver` will no longer panic when looking up payment instructions
which include unicode characters at the start of a TXT record (#4718).
* When using the `anchor_channel_reserves` module to calculate reserves
required to pay for fees when closing anchor channels, zero-fee-commitment
channels were not considered. This could allow a counterparty to open many
Expand All@@ -80,6 +92,13 @@ can reliably close channels and a sanitization issue.
the wallet by ignoring the `TxIn` cost to spend them (#4670).
* `PrintableString` did not properly sanitize unicode format characters,
allowing an attacker to corrupt the rendering of logs or UI (#4593, #4605).
* RGS data is now limited in how large of a graph it is able to cause a client
to store in memory. Note that RGS data is still considered a DoS vector in
general and you should only use semi-trusted RGS data (#4713).
* Counterparty-provided strings in failure messages are no longer logged in
full, reducing the ability of such a counterparty to spam our logs (#4714).
* Reading a corrupted `ChannelManager` or `ProbabilisticScorer` can no longer
cause us to allocate large amounts of memory (#4712).

Thanks to Project Loupe for reporting most of the issues fixed in this release.

Expand Down
2 changes: 1 addition & 1 deletion lightning-background-processor/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning-background-processor"
version = "0.2.0"
version = "0.2.3"
authors = ["Valentine Wallace <vwallace@protonmail.com>"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning"
Expand Down
2 changes: 1 addition & 1 deletion lightning-custom-message/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning-custom-message"
version = "0.2.0"
version = "0.2.3"
authors = ["Jeffrey Czyz"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning"
Expand Down
2 changes: 1 addition & 1 deletion lightning-dns-resolver/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning-dns-resolver"
version = "0.3.0"
version = "0.3.1"
authors = ["Matt Corallo"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning/"
Expand Down
2 changes: 1 addition & 1 deletion lightning-invoice/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
[package]
name = "lightning-invoice"
description = "Data structures to parse and serialize BOLT11 lightning invoices"
version = "0.34.0"
version = "0.34.1"
authors = ["Sebastian Geisler <sgeisler@wh2.tu-dresden.de>"]
documentation = "https://docs.rs/lightning-invoice/"
license = "MIT OR Apache-2.0"
Expand Down
53 changes: 48 additions & 5 deletions lightning-invoice/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1498,17 +1498,22 @@ impl Bolt11Invoice {
self.signed_invoice.features()
}

/// Recover the payee's public key (only to be used if none was included in the invoice)
/// Get the invoice's payee public key.
///
/// This uses the explicitly included payee public key, if present, otherwise it recovers the
/// payee public key from the signature. Prefer [`Self::get_payee_pub_key`] for clarity.
pub fn recover_payee_pub_key(&self) -> PublicKey {
self.signed_invoice.recover_payee_pub_key().expect("was checked by constructor").0
self.get_payee_pub_key()
}

/// Recover the payee's public key if one was included in the invoice, otherwise return the
/// recovered public key from the signature
/// Get the invoice's payee public key, preferring an explicitly included payee public key and
/// falling back to recovering the key from the signature.
pub fn get_payee_pub_key(&self) -> PublicKey {
match self.payee_pub_key() {
Some(pk) => *pk,
None => self.recover_payee_pub_key(),
None => {
self.signed_invoice.recover_payee_pub_key().expect("was checked by constructor").0
},
}
}

Expand DownExpand Up@@ -2044,6 +2049,44 @@ mod test {
assert!(new_signed.check_signature());
}

#[test]
fn recover_payee_pub_key_uses_included_payee_pub_key() {
use crate::*;
use bitcoin::secp256k1::ecdsa::{RecoverableSignature, RecoveryId};
use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};
use core::time::Duration;

let secp_ctx = Secp256k1::new();
let private_key = SecretKey::from_slice(&[42; 32]).unwrap();
let public_key = PublicKey::from_secret_key(&secp_ctx, &private_key);

let invoice = InvoiceBuilder::new(Currency::Bitcoin)
.description("Test".to_string())
.payment_hash(sha256::Hash::from_slice(&[0; 32][..]).unwrap())
.payment_secret(PaymentSecret([21; 32]))
.payee_pub_key(public_key)
.min_final_cltv_expiry_delta(144)
.duration_since_epoch(Duration::from_secs(1234567))
.build_signed(|hash| secp_ctx.sign_ecdsa_recoverable(hash, &private_key))
.unwrap();

let signed_raw = invoice.into_signed_raw();
let (raw_invoice, hash, signature) = signed_raw.into_parts();
let (_orig_rid, sig_bytes) = signature.0.serialize_compact();
let bad_rid = RecoveryId::from_i32(2).unwrap();
let bad_sig = RecoverableSignature::from_compact(&sig_bytes, bad_rid).unwrap();
let bad_signed_raw = SignedRawBolt11Invoice {
raw_invoice,
hash,
signature: Bolt11InvoiceSignature(bad_sig),
};
let bad_invoice = Bolt11Invoice::from_signed(bad_signed_raw).unwrap();

assert_eq!(bad_invoice.payee_pub_key(), Some(&public_key));
assert_eq!(bad_invoice.recover_payee_pub_key(), public_key);
assert_eq!(bad_invoice.get_payee_pub_key(), public_key);
}

#[test]
fn test_check_feature_bits() {
use crate::TaggedField::*;
Expand Down
2 changes: 1 addition & 1 deletion lightning-liquidity/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning-liquidity"
version = "0.2.0"
version = "0.2.3"
authors = ["John Cantrell <johncantrell97@gmail.com>", "Elias Rohrer <dev@tnull.de>"]
homepage = "https://lightningdevkit.org/"
license = "MIT OR Apache-2.0"
Expand Down
2 changes: 1 addition & 1 deletion lightning-persister/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning-persister"
version = "0.2.0"
version = "0.2.3"
authors = ["Valentine Wallace", "Matt Corallo"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning"
Expand Down
2 changes: 1 addition & 1 deletion lightning-rapid-gossip-sync/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning-rapid-gossip-sync"
version = "0.2.0"
version = "0.2.3"
authors = ["Arik Sosman <git@arik.io>"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning"
Expand Down
12 changes: 12 additions & 0 deletions lightning-rapid-gossip-sync/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -153,6 +153,10 @@ where
/// Sync gossip data from a file.
/// Returns the last sync timestamp to be used the next time rapid sync data is queried.
///
/// You should consider the gossip data source as semi-trusted. It is generally the case that it
/// can DoS the client either by omitting data which leads to pathfinding failure or by bloating
/// the graph such that it leads to eventual OOM on the client.
///
/// `network_graph`: The network graph to apply the updates to
///
/// `sync_path`: Path to the file where the gossip update data is located
Expand All@@ -172,6 +176,10 @@ where
/// Update network graph from binary data.
/// Returns the last sync timestamp to be used the next time rapid sync data is queried.
///
/// You should consider the gossip data source as semi-trusted. It is generally the case that it
/// can DoS the client either by omitting data which leads to pathfinding failure or by bloating
/// the graph such that it leads to eventual OOM on the client.
///
/// `update_data`: `&[u8]` binary stream that comprises the update data
#[cfg(feature = "std")]
pub fn update_network_graph(&self, update_data: &[u8]) -> Result<u32, GraphSyncError> {
Expand All@@ -182,6 +190,10 @@ where
/// Update network graph from binary data.
/// Returns the last sync timestamp to be used the next time rapid sync data is queried.
///
/// You should consider the gossip data source as semi-trusted. It is generally the case that it
/// can DoS the client either by omitting data which leads to pathfinding failure or by bloating
/// the graph such that it leads to eventual OOM on the client.
///
/// `update_data`: `&[u8]` binary stream that comprises the update data
/// `current_time_unix`: `Option<u64>` optional current timestamp to verify data age
pub fn update_network_graph_no_std(
Expand Down
34 changes: 30 additions & 4 deletions lightning-rapid-gossip-sync/src/processing.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,7 +9,9 @@ use lightning::ln::msgs::{
DecodeError, ErrorAction, LightningError, SocketAddress, UnsignedChannelUpdate,
UnsignedNodeAnnouncement,
};
use lightning::routing::gossip::{NetworkGraph, NodeAlias, NodeId};
use lightning::routing::gossip::{
NetworkGraph, NodeAlias, NodeId, CHAN_COUNT_ESTIMATE, NODE_COUNT_ESTIMATE,
};
use lightning::util::logger::Logger;
use lightning::util::ser::{BigSize, FixedLengthReader, Readable};
use lightning::{log_debug, log_given_level, log_gossip, log_trace, log_warn};
Expand DownExpand Up@@ -115,17 +117,27 @@ where
}
};

const MAX_NODE_COUNT: u32 = (NODE_COUNT_ESTIMATE as u32) * 10;
const MAX_CHANNEL_COUNT: u64 = (CHAN_COUNT_ESTIMATE as u64) * 10;

let node_id_count: u32 = Readable::read(read_cursor)?;
if node_id_count > MAX_NODE_COUNT {
return Err(LightningError {
err: "RGS data contained nonsense number of nodes to update".to_owned(),
action: ErrorAction::IgnoreError,
}
.into());
}
let mut node_ids: Vec<NodeId> = Vec::with_capacity(core::cmp::min(
node_id_count,
MAX_INITIAL_NODE_ID_VECTOR_CAPACITY,
) as usize);

let network_graph = &self.network_graph;
let mut node_modifications: Vec<UnsignedNodeAnnouncement> = Vec::new();

let read_only_network_graph = network_graph.read_only();

if parse_node_details {
let read_only_network_graph = network_graph.read_only();
for _ in 0..node_id_count {
let mut pubkey_bytes = [0u8; 33];
read_cursor.read_exact(&mut pubkey_bytes)?;
Expand DownExpand Up@@ -237,9 +249,12 @@ where
}
}

let original_graph_channel_count = read_only_network_graph.channels().len() as u32;
core::mem::drop(read_only_network_graph);

let mut previous_scid: u64 = 0;
let announcement_count: u32 = Readable::read(read_cursor)?;
for _ in 0..announcement_count {
for i in 0..announcement_count {
let features = Readable::read(read_cursor)?;

// handle SCID
Expand DownExpand Up@@ -284,6 +299,10 @@ where
}
}

if (original_graph_channel_count as u64) + (i as u64) > MAX_CHANNEL_COUNT {
continue;
}

let announcement_result = network_graph.add_channel_from_partial_announcement(
short_channel_id,
funding_sats,
Expand DownExpand Up@@ -329,6 +348,13 @@ where
previous_scid = 0;

let update_count: u32 = Readable::read(read_cursor)?;
if update_count as u64 > MAX_CHANNEL_COUNT {
return Err(LightningError {
err: "RGS data contained nonsense number of channels to update".to_owned(),
action: ErrorAction::IgnoreError,
}
.into());
}
log_debug!(self.logger, "Processing RGS update from {} with {} nodes, {} channel announcements and {} channel updates.",
latest_seen_timestamp, node_id_count, announcement_count, update_count);
if update_count == 0 {
Expand Down
2 changes: 1 addition & 1 deletion lightning-types/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning-types"
version = "0.3.1"
version = "0.3.2"
authors = ["Matt Corallo"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning/"
Expand Down
2 changes: 1 addition & 1 deletion lightning/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning"
version = "0.2.2"
version = "0.2.3"
authors = ["Matt Corallo"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning/"
Expand Down
9 changes: 6 additions & 3 deletions lightning/src/ln/channel.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1959,9 +1959,12 @@ where

let tx_abort = should_ack.then(|| {
let logger = WithChannelContext::from(logger, &self.context(), None);
let reason =
types::string::UntrustedString(String::from_utf8_lossy(&msg.data).to_string());
log_info!(logger, "Counterparty failed interactive transaction negotiation: {reason}");
let reason = String::from_utf8_lossy(&msg.data);
log_info!(
logger,
"Counterparty failed interactive transaction negotiation: {}",
log_msg!(reason)
);
msgs::TxAbort {
channel_id: msg.channel_id,
data: "Acknowledged tx_abort".to_string().into_bytes(),
Expand Down
4 changes: 2 additions & 2 deletions lightning/src/ln/invoice_utils.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1288,7 +1288,7 @@ mod test {
assert!(!invoice.features().unwrap().supports_basic_mpp());

let payment_params = PaymentParameters::from_node_id(
invoice.recover_payee_pub_key(),
invoice.get_payee_pub_key(),
invoice.min_final_cltv_expiry_delta() as u32,
)
.with_bolt11_features(invoice.features().unwrap().clone())
Expand DownExpand Up@@ -1350,7 +1350,7 @@ mod test {
payment_secret,
payment_amt,
payment_preimage_opt,
invoice.recover_payee_pub_key(),
invoice.get_payee_pub_key(),
);
do_claim_payment_along_route(ClaimAlongRouteArgs::new(
&nodes[0],
Expand Down
13 changes: 6 additions & 7 deletions lightning/src/ln/peer_handler.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -45,7 +45,6 @@ use crate::onion_message::packet::OnionMessageContents;
use crate::routing::gossip::{NodeAlias, NodeId};
use crate::sign::{NodeSigner, Recipient};
use crate::types::features::{InitFeatures, NodeFeatures};
use crate::types::string::PrintableString;
use crate::util::atomic_counter::AtomicCounter;
use crate::util::logger::{Level, Logger, WithContext};
use crate::util::ser::{VecWriter, Writeable, Writer};
Expand DownExpand Up@@ -2415,7 +2414,7 @@ where
logger,
"Got Err message from {}: {}",
their_node_id,
PrintableString(&msg.data)
log_msg!(msg.data)
);
self.message_handler.chan_handler.handle_error(their_node_id, &msg);
if msg.channel_id.is_zero() {
Expand All@@ -2427,7 +2426,7 @@ where
logger,
"Got warning message from {}: {}",
their_node_id,
PrintableString(&msg.data)
log_msg!(msg.data)
);
},

Expand DownExpand Up@@ -3213,7 +3212,7 @@ where
msgs::ErrorAction::DisconnectPeer { msg } => {
if let Some(msg) = msg.as_ref() {
log_trace!(logger, "Handling DisconnectPeer HandleError event in peer_handler for node {} with message {}",
node_id, msg.data);
node_id, log_msg!(msg.data));
} else {
log_trace!(logger, "Handling DisconnectPeer HandleError event in peer_handler for node {}",
node_id);
Expand All@@ -3228,7 +3227,7 @@ where
},
msgs::ErrorAction::DisconnectPeerWithWarning { msg } => {
log_trace!(logger, "Handling DisconnectPeer HandleError event in peer_handler for node {} with message {}",
node_id, msg.data);
node_id, log_msg!(msg.data));
// We do not have the peers write lock, so we just store that we're
// about to disconnect the peer and do it after we finish
// processing most messages.
Expand All@@ -3254,7 +3253,7 @@ where
msgs::ErrorAction::SendErrorMessage { ref msg } => {
log_trace!(logger, "Handling SendErrorMessage HandleError event in peer_handler for node {} with message {}",
node_id,
msg.data);
log_msg!(msg.data));
self.enqueue_message(
&mut *get_peer_for_forwarding!(&node_id)?,
msg,
Expand All@@ -3266,7 +3265,7 @@ where
} => {
log_given_level!(logger, *log_level, "Handling SendWarningMessage HandleError event in peer_handler for node {} with message {}",
node_id,
msg.data);
log_msg!(msg.data));
self.enqueue_message(
&mut *get_peer_for_forwarding!(&node_id)?,
msg,
Expand Down
3 changes: 2 additions & 1 deletion lightning/src/onion_message/dns_resolution.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -520,7 +520,8 @@ impl OMNameResolver {
.filter_map(|data| String::from_utf8(data).ok())
.filter(|data_string| data_string.len() > URI_PREFIX.len())
.filter(|data_string| {
data_string[..URI_PREFIX.len()].eq_ignore_ascii_case(URI_PREFIX)
let pfx = &data_string.as_bytes()[..URI_PREFIX.len()];
pfx.eq_ignore_ascii_case(URI_PREFIX.as_bytes())
});
// Check that there is exactly one TXT record that begins with
// bitcoin: as required by BIP 353 (and is valid UTF-8).
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
21 changes: 20 additions & 1 deletion CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -71,7 +71,19 @@

## Security
0.2.3 fixes several underestimates of the anchor reserves required to ensure we
can reliably close channels and a sanitization issue.
can reliably close channels, several denial-of-service vulnerabilities and a
sanitization issue.
* `Bolt11Invoice::recover_payee_pub_key` no longer panics if called on an
invoice which set an explicit public key, rather than relying on public key
recovery. Note that this method is called from
`PaymentParameters::from_bolt11_invoice` (#4717).
* Maliciously-crafted unpayable invoices which have overflowing feerates will
no longer cause an `unwrap` failure panic (#4716).
* `possiblyrandom` did not properly generate random data except when it was
explicitly configured to. By default this means LDK is vulnerable to various
HashDoS attacks (#4719).
* `OMNameResolver` will no longer panic when looking up payment instructions
which include unicode characters at the start of a TXT record (#4718).
* When using the `anchor_channel_reserves` module to calculate reserves
required to pay for fees when closing anchor channels, zero-fee-commitment
channels were not considered. This could allow a counterparty to open many
Expand All@@ -80,6 +92,13 @@ can reliably close channels and a sanitization issue.
the wallet by ignoring the `TxIn` cost to spend them (#4670).
* `PrintableString` did not properly sanitize unicode format characters,
allowing an attacker to corrupt the rendering of logs or UI (#4593, #4605).
* RGS data is now limited in how large of a graph it is able to cause a client
to store in memory. Note that RGS data is still considered a DoS vector in
general and you should only use semi-trusted RGS data (#4713).
* Counterparty-provided strings in failure messages are no longer logged in
full, reducing the ability of such a counterparty to spam our logs (#4714).
* Reading a corrupted `ChannelManager` or `ProbabilisticScorer` can no longer
cause us to allocate large amounts of memory (#4712).

Thanks to Project Loupe for reporting most of the issues fixed in this release.

Expand Down
2 changes: 1 addition & 1 deletion lightning-background-processor/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning-background-processor"
version = "0.2.0"
version = "0.2.3"
authors = ["Valentine Wallace <vwallace@protonmail.com>"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning"
Expand Down
2 changes: 1 addition & 1 deletion lightning-custom-message/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning-custom-message"
version = "0.2.0"
version = "0.2.3"
authors = ["Jeffrey Czyz"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning"
Expand Down
2 changes: 1 addition & 1 deletion lightning-dns-resolver/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning-dns-resolver"
version = "0.3.0"
version = "0.3.1"
authors = ["Matt Corallo"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning/"
Expand Down
2 changes: 1 addition & 1 deletion lightning-invoice/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
[package]
name = "lightning-invoice"
description = "Data structures to parse and serialize BOLT11 lightning invoices"
version = "0.34.0"
version = "0.34.1"
authors = ["Sebastian Geisler <sgeisler@wh2.tu-dresden.de>"]
documentation = "https://docs.rs/lightning-invoice/"
license = "MIT OR Apache-2.0"
Expand Down
53 changes: 48 additions & 5 deletions lightning-invoice/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1498,17 +1498,22 @@ impl Bolt11Invoice {
self.signed_invoice.features()
}

/// Recover the payee's public key (only to be used if none was included in the invoice)
/// Get the invoice's payee public key.
///
/// This uses the explicitly included payee public key, if present, otherwise it recovers the
/// payee public key from the signature. Prefer [`Self::get_payee_pub_key`] for clarity.
pub fn recover_payee_pub_key(&self) -> PublicKey {
self.signed_invoice.recover_payee_pub_key().expect("was checked by constructor").0
self.get_payee_pub_key()
}

/// Recover the payee's public key if one was included in the invoice, otherwise return the
/// recovered public key from the signature
/// Get the invoice's payee public key, preferring an explicitly included payee public key and
/// falling back to recovering the key from the signature.
pub fn get_payee_pub_key(&self) -> PublicKey {
match self.payee_pub_key() {
Some(pk) => *pk,
None => self.recover_payee_pub_key(),
None => {
self.signed_invoice.recover_payee_pub_key().expect("was checked by constructor").0
},
}
}

Expand DownExpand Up@@ -2044,6 +2049,44 @@ mod test {
assert!(new_signed.check_signature());
}

#[test]
fn recover_payee_pub_key_uses_included_payee_pub_key() {
use crate::*;
use bitcoin::secp256k1::ecdsa::{RecoverableSignature, RecoveryId};
use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};
use core::time::Duration;

let secp_ctx = Secp256k1::new();
let private_key = SecretKey::from_slice(&[42; 32]).unwrap();
let public_key = PublicKey::from_secret_key(&secp_ctx, &private_key);

let invoice = InvoiceBuilder::new(Currency::Bitcoin)
.description("Test".to_string())
.payment_hash(sha256::Hash::from_slice(&[0; 32][..]).unwrap())
.payment_secret(PaymentSecret([21; 32]))
.payee_pub_key(public_key)
.min_final_cltv_expiry_delta(144)
.duration_since_epoch(Duration::from_secs(1234567))
.build_signed(|hash| secp_ctx.sign_ecdsa_recoverable(hash, &private_key))
.unwrap();

let signed_raw = invoice.into_signed_raw();
let (raw_invoice, hash, signature) = signed_raw.into_parts();
let (_orig_rid, sig_bytes) = signature.0.serialize_compact();
let bad_rid = RecoveryId::from_i32(2).unwrap();
let bad_sig = RecoverableSignature::from_compact(&sig_bytes, bad_rid).unwrap();
let bad_signed_raw = SignedRawBolt11Invoice {
raw_invoice,
hash,
signature: Bolt11InvoiceSignature(bad_sig),
};
let bad_invoice = Bolt11Invoice::from_signed(bad_signed_raw).unwrap();

assert_eq!(bad_invoice.payee_pub_key(), Some(&public_key));
assert_eq!(bad_invoice.recover_payee_pub_key(), public_key);
assert_eq!(bad_invoice.get_payee_pub_key(), public_key);
}

#[test]
fn test_check_feature_bits() {
use crate::TaggedField::*;
Expand Down
2 changes: 1 addition & 1 deletion lightning-liquidity/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning-liquidity"
version = "0.2.0"
version = "0.2.3"
authors = ["John Cantrell <johncantrell97@gmail.com>", "Elias Rohrer <dev@tnull.de>"]
homepage = "https://lightningdevkit.org/"
license = "MIT OR Apache-2.0"
Expand Down
2 changes: 1 addition & 1 deletion lightning-persister/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning-persister"
version = "0.2.0"
version = "0.2.3"
authors = ["Valentine Wallace", "Matt Corallo"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning"
Expand Down
2 changes: 1 addition & 1 deletion lightning-rapid-gossip-sync/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning-rapid-gossip-sync"
version = "0.2.0"
version = "0.2.3"
authors = ["Arik Sosman <git@arik.io>"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning"
Expand Down
12 changes: 12 additions & 0 deletions lightning-rapid-gossip-sync/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -153,6 +153,10 @@ where
/// Sync gossip data from a file.
/// Returns the last sync timestamp to be used the next time rapid sync data is queried.
///
/// You should consider the gossip data source as semi-trusted. It is generally the case that it
/// can DoS the client either by omitting data which leads to pathfinding failure or by bloating
/// the graph such that it leads to eventual OOM on the client.
///
/// `network_graph`: The network graph to apply the updates to
///
/// `sync_path`: Path to the file where the gossip update data is located
Expand All@@ -172,6 +176,10 @@ where
/// Update network graph from binary data.
/// Returns the last sync timestamp to be used the next time rapid sync data is queried.
///
/// You should consider the gossip data source as semi-trusted. It is generally the case that it
/// can DoS the client either by omitting data which leads to pathfinding failure or by bloating
/// the graph such that it leads to eventual OOM on the client.
///
/// `update_data`: `&[u8]` binary stream that comprises the update data
#[cfg(feature = "std")]
pub fn update_network_graph(&self, update_data: &[u8]) -> Result<u32, GraphSyncError> {
Expand All@@ -182,6 +190,10 @@ where
/// Update network graph from binary data.
/// Returns the last sync timestamp to be used the next time rapid sync data is queried.
///
/// You should consider the gossip data source as semi-trusted. It is generally the case that it
/// can DoS the client either by omitting data which leads to pathfinding failure or by bloating
/// the graph such that it leads to eventual OOM on the client.
///
/// `update_data`: `&[u8]` binary stream that comprises the update data
/// `current_time_unix`: `Option<u64>` optional current timestamp to verify data age
pub fn update_network_graph_no_std(
Expand Down
34 changes: 30 additions & 4 deletions lightning-rapid-gossip-sync/src/processing.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,7 +9,9 @@ use lightning::ln::msgs::{
DecodeError, ErrorAction, LightningError, SocketAddress, UnsignedChannelUpdate,
UnsignedNodeAnnouncement,
};
use lightning::routing::gossip::{NetworkGraph, NodeAlias, NodeId};
use lightning::routing::gossip::{
NetworkGraph, NodeAlias, NodeId, CHAN_COUNT_ESTIMATE, NODE_COUNT_ESTIMATE,
};
use lightning::util::logger::Logger;
use lightning::util::ser::{BigSize, FixedLengthReader, Readable};
use lightning::{log_debug, log_given_level, log_gossip, log_trace, log_warn};
Expand DownExpand Up@@ -115,17 +117,27 @@ where
}
};

const MAX_NODE_COUNT: u32 = (NODE_COUNT_ESTIMATE as u32) * 10;
const MAX_CHANNEL_COUNT: u64 = (CHAN_COUNT_ESTIMATE as u64) * 10;

let node_id_count: u32 = Readable::read(read_cursor)?;
if node_id_count > MAX_NODE_COUNT {
return Err(LightningError {
err: "RGS data contained nonsense number of nodes to update".to_owned(),
action: ErrorAction::IgnoreError,
}
.into());
}
let mut node_ids: Vec<NodeId> = Vec::with_capacity(core::cmp::min(
node_id_count,
MAX_INITIAL_NODE_ID_VECTOR_CAPACITY,
) as usize);

let network_graph = &self.network_graph;
let mut node_modifications: Vec<UnsignedNodeAnnouncement> = Vec::new();

let read_only_network_graph = network_graph.read_only();

if parse_node_details {
let read_only_network_graph = network_graph.read_only();
for _ in 0..node_id_count {
let mut pubkey_bytes = [0u8; 33];
read_cursor.read_exact(&mut pubkey_bytes)?;
Expand DownExpand Up@@ -237,9 +249,12 @@ where
}
}

let original_graph_channel_count = read_only_network_graph.channels().len() as u32;
core::mem::drop(read_only_network_graph);

let mut previous_scid: u64 = 0;
let announcement_count: u32 = Readable::read(read_cursor)?;
for _ in 0..announcement_count {
for i in 0..announcement_count {
let features = Readable::read(read_cursor)?;

// handle SCID
Expand DownExpand Up@@ -284,6 +299,10 @@ where
}
}

if (original_graph_channel_count as u64) + (i as u64) > MAX_CHANNEL_COUNT {
continue;
}

let announcement_result = network_graph.add_channel_from_partial_announcement(
short_channel_id,
funding_sats,
Expand DownExpand Up@@ -329,6 +348,13 @@ where
previous_scid = 0;

let update_count: u32 = Readable::read(read_cursor)?;
if update_count as u64 > MAX_CHANNEL_COUNT {
return Err(LightningError {
err: "RGS data contained nonsense number of channels to update".to_owned(),
action: ErrorAction::IgnoreError,
}
.into());
}
log_debug!(self.logger, "Processing RGS update from {} with {} nodes, {} channel announcements and {} channel updates.",
latest_seen_timestamp, node_id_count, announcement_count, update_count);
if update_count == 0 {
Expand Down
2 changes: 1 addition & 1 deletion lightning-types/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning-types"
version = "0.3.1"
version = "0.3.2"
authors = ["Matt Corallo"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning/"
Expand Down
2 changes: 1 addition & 1 deletion lightning/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning"
version = "0.2.2"
version = "0.2.3"
authors = ["Matt Corallo"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning/"
Expand Down
9 changes: 6 additions & 3 deletions lightning/src/ln/channel.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1959,9 +1959,12 @@ where

let tx_abort = should_ack.then(|| {
let logger = WithChannelContext::from(logger, &self.context(), None);
let reason =
types::string::UntrustedString(String::from_utf8_lossy(&msg.data).to_string());
log_info!(logger, "Counterparty failed interactive transaction negotiation: {reason}");
let reason = String::from_utf8_lossy(&msg.data);
log_info!(
logger,
"Counterparty failed interactive transaction negotiation: {}",
log_msg!(reason)
);
msgs::TxAbort {
channel_id: msg.channel_id,
data: "Acknowledged tx_abort".to_string().into_bytes(),
Expand Down
4 changes: 2 additions & 2 deletions lightning/src/ln/invoice_utils.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1288,7 +1288,7 @@ mod test {
assert!(!invoice.features().unwrap().supports_basic_mpp());

let payment_params = PaymentParameters::from_node_id(
invoice.recover_payee_pub_key(),
invoice.get_payee_pub_key(),
invoice.min_final_cltv_expiry_delta() as u32,
)
.with_bolt11_features(invoice.features().unwrap().clone())
Expand DownExpand Up@@ -1350,7 +1350,7 @@ mod test {
payment_secret,
payment_amt,
payment_preimage_opt,
invoice.recover_payee_pub_key(),
invoice.get_payee_pub_key(),
);
do_claim_payment_along_route(ClaimAlongRouteArgs::new(
&nodes[0],
Expand Down
13 changes: 6 additions & 7 deletions lightning/src/ln/peer_handler.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -45,7 +45,6 @@ use crate::onion_message::packet::OnionMessageContents;
use crate::routing::gossip::{NodeAlias, NodeId};
use crate::sign::{NodeSigner, Recipient};
use crate::types::features::{InitFeatures, NodeFeatures};
use crate::types::string::PrintableString;
use crate::util::atomic_counter::AtomicCounter;
use crate::util::logger::{Level, Logger, WithContext};
use crate::util::ser::{VecWriter, Writeable, Writer};
Expand DownExpand Up@@ -2415,7 +2414,7 @@ where
logger,
"Got Err message from {}: {}",
their_node_id,
PrintableString(&msg.data)
log_msg!(msg.data)
);
self.message_handler.chan_handler.handle_error(their_node_id, &msg);
if msg.channel_id.is_zero() {
Expand All@@ -2427,7 +2426,7 @@ where
logger,
"Got warning message from {}: {}",
their_node_id,
PrintableString(&msg.data)
log_msg!(msg.data)
);
},

Expand DownExpand Up@@ -3213,7 +3212,7 @@ where
msgs::ErrorAction::DisconnectPeer { msg } => {
if let Some(msg) = msg.as_ref() {
log_trace!(logger, "Handling DisconnectPeer HandleError event in peer_handler for node {} with message {}",
node_id, msg.data);
node_id, log_msg!(msg.data));
} else {
log_trace!(logger, "Handling DisconnectPeer HandleError event in peer_handler for node {}",
node_id);
Expand All@@ -3228,7 +3227,7 @@ where
},
msgs::ErrorAction::DisconnectPeerWithWarning { msg } => {
log_trace!(logger, "Handling DisconnectPeer HandleError event in peer_handler for node {} with message {}",
node_id, msg.data);
node_id, log_msg!(msg.data));
// We do not have the peers write lock, so we just store that we're
// about to disconnect the peer and do it after we finish
// processing most messages.
Expand All@@ -3254,7 +3253,7 @@ where
msgs::ErrorAction::SendErrorMessage { ref msg } => {
log_trace!(logger, "Handling SendErrorMessage HandleError event in peer_handler for node {} with message {}",
node_id,
msg.data);
log_msg!(msg.data));
self.enqueue_message(
&mut *get_peer_for_forwarding!(&node_id)?,
msg,
Expand All@@ -3266,7 +3265,7 @@ where
} => {
log_given_level!(logger, *log_level, "Handling SendWarningMessage HandleError event in peer_handler for node {} with message {}",
node_id,
msg.data);
log_msg!(msg.data));
self.enqueue_message(
&mut *get_peer_for_forwarding!(&node_id)?,
msg,
Expand Down
3 changes: 2 additions & 1 deletion lightning/src/onion_message/dns_resolution.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -520,7 +520,8 @@ impl OMNameResolver {
.filter_map(|data| String::from_utf8(data).ok())
.filter(|data_string| data_string.len() > URI_PREFIX.len())
.filter(|data_string| {
data_string[..URI_PREFIX.len()].eq_ignore_ascii_case(URI_PREFIX)
let pfx = &data_string.as_bytes()[..URI_PREFIX.len()];
pfx.eq_ignore_ascii_case(URI_PREFIX.as_bytes())
});
// Check that there is exactly one TXT record that begins with
// bitcoin: as required by BIP 353 (and is valid UTF-8).
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
21 changes: 20 additions & 1 deletion CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -71,7 +71,19 @@

## Security
0.2.3 fixes several underestimates of the anchor reserves required to ensure we
can reliably close channels and a sanitization issue.
can reliably close channels, several denial-of-service vulnerabilities and a
sanitization issue.
* `Bolt11Invoice::recover_payee_pub_key` no longer panics if called on an
invoice which set an explicit public key, rather than relying on public key
recovery. Note that this method is called from
`PaymentParameters::from_bolt11_invoice` (#4717).
* Maliciously-crafted unpayable invoices which have overflowing feerates will
no longer cause an `unwrap` failure panic (#4716).
* `possiblyrandom` did not properly generate random data except when it was
explicitly configured to. By default this means LDK is vulnerable to various
HashDoS attacks (#4719).
* `OMNameResolver` will no longer panic when looking up payment instructions
which include unicode characters at the start of a TXT record (#4718).
* When using the `anchor_channel_reserves` module to calculate reserves
required to pay for fees when closing anchor channels, zero-fee-commitment
channels were not considered. This could allow a counterparty to open many
Expand All@@ -80,6 +92,13 @@ can reliably close channels and a sanitization issue.
the wallet by ignoring the `TxIn` cost to spend them (#4670).
* `PrintableString` did not properly sanitize unicode format characters,
allowing an attacker to corrupt the rendering of logs or UI (#4593, #4605).
* RGS data is now limited in how large of a graph it is able to cause a client
to store in memory. Note that RGS data is still considered a DoS vector in
general and you should only use semi-trusted RGS data (#4713).
* Counterparty-provided strings in failure messages are no longer logged in
full, reducing the ability of such a counterparty to spam our logs (#4714).
* Reading a corrupted `ChannelManager` or `ProbabilisticScorer` can no longer
cause us to allocate large amounts of memory (#4712).

Thanks to Project Loupe for reporting most of the issues fixed in this release.

Expand Down
2 changes: 1 addition & 1 deletion lightning-background-processor/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning-background-processor"
version = "0.2.0"
version = "0.2.3"
authors = ["Valentine Wallace <vwallace@protonmail.com>"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning"
Expand Down
2 changes: 1 addition & 1 deletion lightning-custom-message/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning-custom-message"
version = "0.2.0"
version = "0.2.3"
authors = ["Jeffrey Czyz"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning"
Expand Down
2 changes: 1 addition & 1 deletion lightning-dns-resolver/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning-dns-resolver"
version = "0.3.0"
version = "0.3.1"
authors = ["Matt Corallo"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning/"
Expand Down
2 changes: 1 addition & 1 deletion lightning-invoice/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
[package]
name = "lightning-invoice"
description = "Data structures to parse and serialize BOLT11 lightning invoices"
version = "0.34.0"
version = "0.34.1"
authors = ["Sebastian Geisler <sgeisler@wh2.tu-dresden.de>"]
documentation = "https://docs.rs/lightning-invoice/"
license = "MIT OR Apache-2.0"
Expand Down
53 changes: 48 additions & 5 deletions lightning-invoice/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1498,17 +1498,22 @@ impl Bolt11Invoice {
self.signed_invoice.features()
}

/// Recover the payee's public key (only to be used if none was included in the invoice)
/// Get the invoice's payee public key.
///
/// This uses the explicitly included payee public key, if present, otherwise it recovers the
/// payee public key from the signature. Prefer [`Self::get_payee_pub_key`] for clarity.
pub fn recover_payee_pub_key(&self) -> PublicKey {
self.signed_invoice.recover_payee_pub_key().expect("was checked by constructor").0
self.get_payee_pub_key()
}

/// Recover the payee's public key if one was included in the invoice, otherwise return the
/// recovered public key from the signature
/// Get the invoice's payee public key, preferring an explicitly included payee public key and
/// falling back to recovering the key from the signature.
pub fn get_payee_pub_key(&self) -> PublicKey {
match self.payee_pub_key() {
Some(pk) => *pk,
None => self.recover_payee_pub_key(),
None => {
self.signed_invoice.recover_payee_pub_key().expect("was checked by constructor").0
},
}
}

Expand DownExpand Up@@ -2044,6 +2049,44 @@ mod test {
assert!(new_signed.check_signature());
}

#[test]
fn recover_payee_pub_key_uses_included_payee_pub_key() {
use crate::*;
use bitcoin::secp256k1::ecdsa::{RecoverableSignature, RecoveryId};
use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};
use core::time::Duration;

let secp_ctx = Secp256k1::new();
let private_key = SecretKey::from_slice(&[42; 32]).unwrap();
let public_key = PublicKey::from_secret_key(&secp_ctx, &private_key);

let invoice = InvoiceBuilder::new(Currency::Bitcoin)
.description("Test".to_string())
.payment_hash(sha256::Hash::from_slice(&[0; 32][..]).unwrap())
.payment_secret(PaymentSecret([21; 32]))
.payee_pub_key(public_key)
.min_final_cltv_expiry_delta(144)
.duration_since_epoch(Duration::from_secs(1234567))
.build_signed(|hash| secp_ctx.sign_ecdsa_recoverable(hash, &private_key))
.unwrap();

let signed_raw = invoice.into_signed_raw();
let (raw_invoice, hash, signature) = signed_raw.into_parts();
let (_orig_rid, sig_bytes) = signature.0.serialize_compact();
let bad_rid = RecoveryId::from_i32(2).unwrap();
let bad_sig = RecoverableSignature::from_compact(&sig_bytes, bad_rid).unwrap();
let bad_signed_raw = SignedRawBolt11Invoice {
raw_invoice,
hash,
signature: Bolt11InvoiceSignature(bad_sig),
};
let bad_invoice = Bolt11Invoice::from_signed(bad_signed_raw).unwrap();

assert_eq!(bad_invoice.payee_pub_key(), Some(&public_key));
assert_eq!(bad_invoice.recover_payee_pub_key(), public_key);
assert_eq!(bad_invoice.get_payee_pub_key(), public_key);
}

#[test]
fn test_check_feature_bits() {
use crate::TaggedField::*;
Expand Down
2 changes: 1 addition & 1 deletion lightning-liquidity/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning-liquidity"
version = "0.2.0"
version = "0.2.3"
authors = ["John Cantrell <johncantrell97@gmail.com>", "Elias Rohrer <dev@tnull.de>"]
homepage = "https://lightningdevkit.org/"
license = "MIT OR Apache-2.0"
Expand Down
2 changes: 1 addition & 1 deletion lightning-persister/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning-persister"
version = "0.2.0"
version = "0.2.3"
authors = ["Valentine Wallace", "Matt Corallo"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning"
Expand Down
2 changes: 1 addition & 1 deletion lightning-rapid-gossip-sync/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning-rapid-gossip-sync"
version = "0.2.0"
version = "0.2.3"
authors = ["Arik Sosman <git@arik.io>"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning"
Expand Down
12 changes: 12 additions & 0 deletions lightning-rapid-gossip-sync/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -153,6 +153,10 @@ where
/// Sync gossip data from a file.
/// Returns the last sync timestamp to be used the next time rapid sync data is queried.
///
/// You should consider the gossip data source as semi-trusted. It is generally the case that it
/// can DoS the client either by omitting data which leads to pathfinding failure or by bloating
/// the graph such that it leads to eventual OOM on the client.
///
/// `network_graph`: The network graph to apply the updates to
///
/// `sync_path`: Path to the file where the gossip update data is located
Expand All@@ -172,6 +176,10 @@ where
/// Update network graph from binary data.
/// Returns the last sync timestamp to be used the next time rapid sync data is queried.
///
/// You should consider the gossip data source as semi-trusted. It is generally the case that it
/// can DoS the client either by omitting data which leads to pathfinding failure or by bloating
/// the graph such that it leads to eventual OOM on the client.
///
/// `update_data`: `&[u8]` binary stream that comprises the update data
#[cfg(feature = "std")]
pub fn update_network_graph(&self, update_data: &[u8]) -> Result<u32, GraphSyncError> {
Expand All@@ -182,6 +190,10 @@ where
/// Update network graph from binary data.
/// Returns the last sync timestamp to be used the next time rapid sync data is queried.
///
/// You should consider the gossip data source as semi-trusted. It is generally the case that it
/// can DoS the client either by omitting data which leads to pathfinding failure or by bloating
/// the graph such that it leads to eventual OOM on the client.
///
/// `update_data`: `&[u8]` binary stream that comprises the update data
/// `current_time_unix`: `Option<u64>` optional current timestamp to verify data age
pub fn update_network_graph_no_std(
Expand Down
34 changes: 30 additions & 4 deletions lightning-rapid-gossip-sync/src/processing.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,7 +9,9 @@ use lightning::ln::msgs::{
DecodeError, ErrorAction, LightningError, SocketAddress, UnsignedChannelUpdate,
UnsignedNodeAnnouncement,
};
use lightning::routing::gossip::{NetworkGraph, NodeAlias, NodeId};
use lightning::routing::gossip::{
NetworkGraph, NodeAlias, NodeId, CHAN_COUNT_ESTIMATE, NODE_COUNT_ESTIMATE,
};
use lightning::util::logger::Logger;
use lightning::util::ser::{BigSize, FixedLengthReader, Readable};
use lightning::{log_debug, log_given_level, log_gossip, log_trace, log_warn};
Expand DownExpand Up@@ -115,17 +117,27 @@ where
}
};

const MAX_NODE_COUNT: u32 = (NODE_COUNT_ESTIMATE as u32) * 10;
const MAX_CHANNEL_COUNT: u64 = (CHAN_COUNT_ESTIMATE as u64) * 10;

let node_id_count: u32 = Readable::read(read_cursor)?;
if node_id_count > MAX_NODE_COUNT {
return Err(LightningError {
err: "RGS data contained nonsense number of nodes to update".to_owned(),
action: ErrorAction::IgnoreError,
}
.into());
}
let mut node_ids: Vec<NodeId> = Vec::with_capacity(core::cmp::min(
node_id_count,
MAX_INITIAL_NODE_ID_VECTOR_CAPACITY,
) as usize);

let network_graph = &self.network_graph;
let mut node_modifications: Vec<UnsignedNodeAnnouncement> = Vec::new();

let read_only_network_graph = network_graph.read_only();

if parse_node_details {
let read_only_network_graph = network_graph.read_only();
for _ in 0..node_id_count {
let mut pubkey_bytes = [0u8; 33];
read_cursor.read_exact(&mut pubkey_bytes)?;
Expand DownExpand Up@@ -237,9 +249,12 @@ where
}
}

let original_graph_channel_count = read_only_network_graph.channels().len() as u32;
core::mem::drop(read_only_network_graph);

let mut previous_scid: u64 = 0;
let announcement_count: u32 = Readable::read(read_cursor)?;
for _ in 0..announcement_count {
for i in 0..announcement_count {
let features = Readable::read(read_cursor)?;

// handle SCID
Expand DownExpand Up@@ -284,6 +299,10 @@ where
}
}

if (original_graph_channel_count as u64) + (i as u64) > MAX_CHANNEL_COUNT {
continue;
}

let announcement_result = network_graph.add_channel_from_partial_announcement(
short_channel_id,
funding_sats,
Expand DownExpand Up@@ -329,6 +348,13 @@ where
previous_scid = 0;

let update_count: u32 = Readable::read(read_cursor)?;
if update_count as u64 > MAX_CHANNEL_COUNT {
return Err(LightningError {
err: "RGS data contained nonsense number of channels to update".to_owned(),
action: ErrorAction::IgnoreError,
}
.into());
}
log_debug!(self.logger, "Processing RGS update from {} with {} nodes, {} channel announcements and {} channel updates.",
latest_seen_timestamp, node_id_count, announcement_count, update_count);
if update_count == 0 {
Expand Down
2 changes: 1 addition & 1 deletion lightning-types/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning-types"
version = "0.3.1"
version = "0.3.2"
authors = ["Matt Corallo"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning/"
Expand Down
2 changes: 1 addition & 1 deletion lightning/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning"
version = "0.2.2"
version = "0.2.3"
authors = ["Matt Corallo"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning/"
Expand Down
9 changes: 6 additions & 3 deletions lightning/src/ln/channel.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1959,9 +1959,12 @@ where

let tx_abort = should_ack.then(|| {
let logger = WithChannelContext::from(logger, &self.context(), None);
let reason =
types::string::UntrustedString(String::from_utf8_lossy(&msg.data).to_string());
log_info!(logger, "Counterparty failed interactive transaction negotiation: {reason}");
let reason = String::from_utf8_lossy(&msg.data);
log_info!(
logger,
"Counterparty failed interactive transaction negotiation: {}",
log_msg!(reason)
);
msgs::TxAbort {
channel_id: msg.channel_id,
data: "Acknowledged tx_abort".to_string().into_bytes(),
Expand Down
4 changes: 2 additions & 2 deletions lightning/src/ln/invoice_utils.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1288,7 +1288,7 @@ mod test {
assert!(!invoice.features().unwrap().supports_basic_mpp());

let payment_params = PaymentParameters::from_node_id(
invoice.recover_payee_pub_key(),
invoice.get_payee_pub_key(),
invoice.min_final_cltv_expiry_delta() as u32,
)
.with_bolt11_features(invoice.features().unwrap().clone())
Expand DownExpand Up@@ -1350,7 +1350,7 @@ mod test {
payment_secret,
payment_amt,
payment_preimage_opt,
invoice.recover_payee_pub_key(),
invoice.get_payee_pub_key(),
);
do_claim_payment_along_route(ClaimAlongRouteArgs::new(
&nodes[0],
Expand Down
13 changes: 6 additions & 7 deletions lightning/src/ln/peer_handler.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -45,7 +45,6 @@ use crate::onion_message::packet::OnionMessageContents;
use crate::routing::gossip::{NodeAlias, NodeId};
use crate::sign::{NodeSigner, Recipient};
use crate::types::features::{InitFeatures, NodeFeatures};
use crate::types::string::PrintableString;
use crate::util::atomic_counter::AtomicCounter;
use crate::util::logger::{Level, Logger, WithContext};
use crate::util::ser::{VecWriter, Writeable, Writer};
Expand DownExpand Up@@ -2415,7 +2414,7 @@ where
logger,
"Got Err message from {}: {}",
their_node_id,
PrintableString(&msg.data)
log_msg!(msg.data)
);
self.message_handler.chan_handler.handle_error(their_node_id, &msg);
if msg.channel_id.is_zero() {
Expand All@@ -2427,7 +2426,7 @@ where
logger,
"Got warning message from {}: {}",
their_node_id,
PrintableString(&msg.data)
log_msg!(msg.data)
);
},

Expand DownExpand Up@@ -3213,7 +3212,7 @@ where
msgs::ErrorAction::DisconnectPeer { msg } => {
if let Some(msg) = msg.as_ref() {
log_trace!(logger, "Handling DisconnectPeer HandleError event in peer_handler for node {} with message {}",
node_id, msg.data);
node_id, log_msg!(msg.data));
} else {
log_trace!(logger, "Handling DisconnectPeer HandleError event in peer_handler for node {}",
node_id);
Expand All@@ -3228,7 +3227,7 @@ where
},
msgs::ErrorAction::DisconnectPeerWithWarning { msg } => {
log_trace!(logger, "Handling DisconnectPeer HandleError event in peer_handler for node {} with message {}",
node_id, msg.data);
node_id, log_msg!(msg.data));
// We do not have the peers write lock, so we just store that we're
// about to disconnect the peer and do it after we finish
// processing most messages.
Expand All@@ -3254,7 +3253,7 @@ where
msgs::ErrorAction::SendErrorMessage { ref msg } => {
log_trace!(logger, "Handling SendErrorMessage HandleError event in peer_handler for node {} with message {}",
node_id,
msg.data);
log_msg!(msg.data));
self.enqueue_message(
&mut *get_peer_for_forwarding!(&node_id)?,
msg,
Expand All@@ -3266,7 +3265,7 @@ where
} => {
log_given_level!(logger, *log_level, "Handling SendWarningMessage HandleError event in peer_handler for node {} with message {}",
node_id,
msg.data);
log_msg!(msg.data));
self.enqueue_message(
&mut *get_peer_for_forwarding!(&node_id)?,
msg,
Expand Down
3 changes: 2 additions & 1 deletion lightning/src/onion_message/dns_resolution.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -520,7 +520,8 @@ impl OMNameResolver {
.filter_map(|data| String::from_utf8(data).ok())
.filter(|data_string| data_string.len() > URI_PREFIX.len())
.filter(|data_string| {
data_string[..URI_PREFIX.len()].eq_ignore_ascii_case(URI_PREFIX)
let pfx = &data_string.as_bytes()[..URI_PREFIX.len()];
pfx.eq_ignore_ascii_case(URI_PREFIX.as_bytes())
});
// Check that there is exactly one TXT record that begins with
// bitcoin: as required by BIP 353 (and is valid UTF-8).
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
21 changes: 20 additions & 1 deletion CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -71,7 +71,19 @@

## Security
0.2.3 fixes several underestimates of the anchor reserves required to ensure we
can reliably close channels and a sanitization issue.
can reliably close channels, several denial-of-service vulnerabilities and a
sanitization issue.
* `Bolt11Invoice::recover_payee_pub_key` no longer panics if called on an
invoice which set an explicit public key, rather than relying on public key
recovery. Note that this method is called from
`PaymentParameters::from_bolt11_invoice` (#4717).
* Maliciously-crafted unpayable invoices which have overflowing feerates will
no longer cause an `unwrap` failure panic (#4716).
* `possiblyrandom` did not properly generate random data except when it was
explicitly configured to. By default this means LDK is vulnerable to various
HashDoS attacks (#4719).
* `OMNameResolver` will no longer panic when looking up payment instructions
which include unicode characters at the start of a TXT record (#4718).
* When using the `anchor_channel_reserves` module to calculate reserves
required to pay for fees when closing anchor channels, zero-fee-commitment
channels were not considered. This could allow a counterparty to open many
Expand All@@ -80,6 +92,13 @@ can reliably close channels and a sanitization issue.
the wallet by ignoring the `TxIn` cost to spend them (#4670).
* `PrintableString` did not properly sanitize unicode format characters,
allowing an attacker to corrupt the rendering of logs or UI (#4593, #4605).
* RGS data is now limited in how large of a graph it is able to cause a client
to store in memory. Note that RGS data is still considered a DoS vector in
general and you should only use semi-trusted RGS data (#4713).
* Counterparty-provided strings in failure messages are no longer logged in
full, reducing the ability of such a counterparty to spam our logs (#4714).
* Reading a corrupted `ChannelManager` or `ProbabilisticScorer` can no longer
cause us to allocate large amounts of memory (#4712).

Thanks to Project Loupe for reporting most of the issues fixed in this release.

Expand Down
2 changes: 1 addition & 1 deletion lightning-background-processor/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning-background-processor"
version = "0.2.0"
version = "0.2.3"
authors = ["Valentine Wallace <vwallace@protonmail.com>"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning"
Expand Down
2 changes: 1 addition & 1 deletion lightning-custom-message/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning-custom-message"
version = "0.2.0"
version = "0.2.3"
authors = ["Jeffrey Czyz"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning"
Expand Down
2 changes: 1 addition & 1 deletion lightning-dns-resolver/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning-dns-resolver"
version = "0.3.0"
version = "0.3.1"
authors = ["Matt Corallo"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning/"
Expand Down
2 changes: 1 addition & 1 deletion lightning-invoice/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
[package]
name = "lightning-invoice"
description = "Data structures to parse and serialize BOLT11 lightning invoices"
version = "0.34.0"
version = "0.34.1"
authors = ["Sebastian Geisler <sgeisler@wh2.tu-dresden.de>"]
documentation = "https://docs.rs/lightning-invoice/"
license = "MIT OR Apache-2.0"
Expand Down
53 changes: 48 additions & 5 deletions lightning-invoice/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1498,17 +1498,22 @@ impl Bolt11Invoice {
self.signed_invoice.features()
}

/// Recover the payee's public key (only to be used if none was included in the invoice)
/// Get the invoice's payee public key.
///
/// This uses the explicitly included payee public key, if present, otherwise it recovers the
/// payee public key from the signature. Prefer [`Self::get_payee_pub_key`] for clarity.
pub fn recover_payee_pub_key(&self) -> PublicKey {
self.signed_invoice.recover_payee_pub_key().expect("was checked by constructor").0
self.get_payee_pub_key()
}

/// Recover the payee's public key if one was included in the invoice, otherwise return the
/// recovered public key from the signature
/// Get the invoice's payee public key, preferring an explicitly included payee public key and
/// falling back to recovering the key from the signature.
pub fn get_payee_pub_key(&self) -> PublicKey {
match self.payee_pub_key() {
Some(pk) => *pk,
None => self.recover_payee_pub_key(),
None => {
self.signed_invoice.recover_payee_pub_key().expect("was checked by constructor").0
},
}
}

Expand DownExpand Up@@ -2044,6 +2049,44 @@ mod test {
assert!(new_signed.check_signature());
}

#[test]
fn recover_payee_pub_key_uses_included_payee_pub_key() {
use crate::*;
use bitcoin::secp256k1::ecdsa::{RecoverableSignature, RecoveryId};
use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};
use core::time::Duration;

let secp_ctx = Secp256k1::new();
let private_key = SecretKey::from_slice(&[42; 32]).unwrap();
let public_key = PublicKey::from_secret_key(&secp_ctx, &private_key);

let invoice = InvoiceBuilder::new(Currency::Bitcoin)
.description("Test".to_string())
.payment_hash(sha256::Hash::from_slice(&[0; 32][..]).unwrap())
.payment_secret(PaymentSecret([21; 32]))
.payee_pub_key(public_key)
.min_final_cltv_expiry_delta(144)
.duration_since_epoch(Duration::from_secs(1234567))
.build_signed(|hash| secp_ctx.sign_ecdsa_recoverable(hash, &private_key))
.unwrap();

let signed_raw = invoice.into_signed_raw();
let (raw_invoice, hash, signature) = signed_raw.into_parts();
let (_orig_rid, sig_bytes) = signature.0.serialize_compact();
let bad_rid = RecoveryId::from_i32(2).unwrap();
let bad_sig = RecoverableSignature::from_compact(&sig_bytes, bad_rid).unwrap();
let bad_signed_raw = SignedRawBolt11Invoice {
raw_invoice,
hash,
signature: Bolt11InvoiceSignature(bad_sig),
};
let bad_invoice = Bolt11Invoice::from_signed(bad_signed_raw).unwrap();

assert_eq!(bad_invoice.payee_pub_key(), Some(&public_key));
assert_eq!(bad_invoice.recover_payee_pub_key(), public_key);
assert_eq!(bad_invoice.get_payee_pub_key(), public_key);
}

#[test]
fn test_check_feature_bits() {
use crate::TaggedField::*;
Expand Down
2 changes: 1 addition & 1 deletion lightning-liquidity/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning-liquidity"
version = "0.2.0"
version = "0.2.3"
authors = ["John Cantrell <johncantrell97@gmail.com>", "Elias Rohrer <dev@tnull.de>"]
homepage = "https://lightningdevkit.org/"
license = "MIT OR Apache-2.0"
Expand Down
2 changes: 1 addition & 1 deletion lightning-persister/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning-persister"
version = "0.2.0"
version = "0.2.3"
authors = ["Valentine Wallace", "Matt Corallo"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning"
Expand Down
2 changes: 1 addition & 1 deletion lightning-rapid-gossip-sync/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning-rapid-gossip-sync"
version = "0.2.0"
version = "0.2.3"
authors = ["Arik Sosman <git@arik.io>"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning"
Expand Down
12 changes: 12 additions & 0 deletions lightning-rapid-gossip-sync/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -153,6 +153,10 @@ where
/// Sync gossip data from a file.
/// Returns the last sync timestamp to be used the next time rapid sync data is queried.
///
/// You should consider the gossip data source as semi-trusted. It is generally the case that it
/// can DoS the client either by omitting data which leads to pathfinding failure or by bloating
/// the graph such that it leads to eventual OOM on the client.
///
/// `network_graph`: The network graph to apply the updates to
///
/// `sync_path`: Path to the file where the gossip update data is located
Expand All@@ -172,6 +176,10 @@ where
/// Update network graph from binary data.
/// Returns the last sync timestamp to be used the next time rapid sync data is queried.
///
/// You should consider the gossip data source as semi-trusted. It is generally the case that it
/// can DoS the client either by omitting data which leads to pathfinding failure or by bloating
/// the graph such that it leads to eventual OOM on the client.
///
/// `update_data`: `&[u8]` binary stream that comprises the update data
#[cfg(feature = "std")]
pub fn update_network_graph(&self, update_data: &[u8]) -> Result<u32, GraphSyncError> {
Expand All@@ -182,6 +190,10 @@ where
/// Update network graph from binary data.
/// Returns the last sync timestamp to be used the next time rapid sync data is queried.
///
/// You should consider the gossip data source as semi-trusted. It is generally the case that it
/// can DoS the client either by omitting data which leads to pathfinding failure or by bloating
/// the graph such that it leads to eventual OOM on the client.
///
/// `update_data`: `&[u8]` binary stream that comprises the update data
/// `current_time_unix`: `Option<u64>` optional current timestamp to verify data age
pub fn update_network_graph_no_std(
Expand Down
34 changes: 30 additions & 4 deletions lightning-rapid-gossip-sync/src/processing.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,7 +9,9 @@ use lightning::ln::msgs::{
DecodeError, ErrorAction, LightningError, SocketAddress, UnsignedChannelUpdate,
UnsignedNodeAnnouncement,
};
use lightning::routing::gossip::{NetworkGraph, NodeAlias, NodeId};
use lightning::routing::gossip::{
NetworkGraph, NodeAlias, NodeId, CHAN_COUNT_ESTIMATE, NODE_COUNT_ESTIMATE,
};
use lightning::util::logger::Logger;
use lightning::util::ser::{BigSize, FixedLengthReader, Readable};
use lightning::{log_debug, log_given_level, log_gossip, log_trace, log_warn};
Expand DownExpand Up@@ -115,17 +117,27 @@ where
}
};

const MAX_NODE_COUNT: u32 = (NODE_COUNT_ESTIMATE as u32) * 10;
const MAX_CHANNEL_COUNT: u64 = (CHAN_COUNT_ESTIMATE as u64) * 10;

let node_id_count: u32 = Readable::read(read_cursor)?;
if node_id_count > MAX_NODE_COUNT {
return Err(LightningError {
err: "RGS data contained nonsense number of nodes to update".to_owned(),
action: ErrorAction::IgnoreError,
}
.into());
}
let mut node_ids: Vec<NodeId> = Vec::with_capacity(core::cmp::min(
node_id_count,
MAX_INITIAL_NODE_ID_VECTOR_CAPACITY,
) as usize);

let network_graph = &self.network_graph;
let mut node_modifications: Vec<UnsignedNodeAnnouncement> = Vec::new();

let read_only_network_graph = network_graph.read_only();

if parse_node_details {
let read_only_network_graph = network_graph.read_only();
for _ in 0..node_id_count {
let mut pubkey_bytes = [0u8; 33];
read_cursor.read_exact(&mut pubkey_bytes)?;
Expand DownExpand Up@@ -237,9 +249,12 @@ where
}
}

let original_graph_channel_count = read_only_network_graph.channels().len() as u32;
core::mem::drop(read_only_network_graph);

let mut previous_scid: u64 = 0;
let announcement_count: u32 = Readable::read(read_cursor)?;
for _ in 0..announcement_count {
for i in 0..announcement_count {
let features = Readable::read(read_cursor)?;

// handle SCID
Expand DownExpand Up@@ -284,6 +299,10 @@ where
}
}

if (original_graph_channel_count as u64) + (i as u64) > MAX_CHANNEL_COUNT {
continue;
}

let announcement_result = network_graph.add_channel_from_partial_announcement(
short_channel_id,
funding_sats,
Expand DownExpand Up@@ -329,6 +348,13 @@ where
previous_scid = 0;

let update_count: u32 = Readable::read(read_cursor)?;
if update_count as u64 > MAX_CHANNEL_COUNT {
return Err(LightningError {
err: "RGS data contained nonsense number of channels to update".to_owned(),
action: ErrorAction::IgnoreError,
}
.into());
}
log_debug!(self.logger, "Processing RGS update from {} with {} nodes, {} channel announcements and {} channel updates.",
latest_seen_timestamp, node_id_count, announcement_count, update_count);
if update_count == 0 {
Expand Down
2 changes: 1 addition & 1 deletion lightning-types/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning-types"
version = "0.3.1"
version = "0.3.2"
authors = ["Matt Corallo"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning/"
Expand Down
2 changes: 1 addition & 1 deletion lightning/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning"
version = "0.2.2"
version = "0.2.3"
authors = ["Matt Corallo"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning/"
Expand Down
9 changes: 6 additions & 3 deletions lightning/src/ln/channel.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1959,9 +1959,12 @@ where

let tx_abort = should_ack.then(|| {
let logger = WithChannelContext::from(logger, &self.context(), None);
let reason =
types::string::UntrustedString(String::from_utf8_lossy(&msg.data).to_string());
log_info!(logger, "Counterparty failed interactive transaction negotiation: {reason}");
let reason = String::from_utf8_lossy(&msg.data);
log_info!(
logger,
"Counterparty failed interactive transaction negotiation: {}",
log_msg!(reason)
);
msgs::TxAbort {
channel_id: msg.channel_id,
data: "Acknowledged tx_abort".to_string().into_bytes(),
Expand Down
4 changes: 2 additions & 2 deletions lightning/src/ln/invoice_utils.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1288,7 +1288,7 @@ mod test {
assert!(!invoice.features().unwrap().supports_basic_mpp());

let payment_params = PaymentParameters::from_node_id(
invoice.recover_payee_pub_key(),
invoice.get_payee_pub_key(),
invoice.min_final_cltv_expiry_delta() as u32,
)
.with_bolt11_features(invoice.features().unwrap().clone())
Expand DownExpand Up@@ -1350,7 +1350,7 @@ mod test {
payment_secret,
payment_amt,
payment_preimage_opt,
invoice.recover_payee_pub_key(),
invoice.get_payee_pub_key(),
);
do_claim_payment_along_route(ClaimAlongRouteArgs::new(
&nodes[0],
Expand Down
13 changes: 6 additions & 7 deletions lightning/src/ln/peer_handler.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -45,7 +45,6 @@ use crate::onion_message::packet::OnionMessageContents;
use crate::routing::gossip::{NodeAlias, NodeId};
use crate::sign::{NodeSigner, Recipient};
use crate::types::features::{InitFeatures, NodeFeatures};
use crate::types::string::PrintableString;
use crate::util::atomic_counter::AtomicCounter;
use crate::util::logger::{Level, Logger, WithContext};
use crate::util::ser::{VecWriter, Writeable, Writer};
Expand DownExpand Up@@ -2415,7 +2414,7 @@ where
logger,
"Got Err message from {}: {}",
their_node_id,
PrintableString(&msg.data)
log_msg!(msg.data)
);
self.message_handler.chan_handler.handle_error(their_node_id, &msg);
if msg.channel_id.is_zero() {
Expand All@@ -2427,7 +2426,7 @@ where
logger,
"Got warning message from {}: {}",
their_node_id,
PrintableString(&msg.data)
log_msg!(msg.data)
);
},

Expand DownExpand Up@@ -3213,7 +3212,7 @@ where
msgs::ErrorAction::DisconnectPeer { msg } => {
if let Some(msg) = msg.as_ref() {
log_trace!(logger, "Handling DisconnectPeer HandleError event in peer_handler for node {} with message {}",
node_id, msg.data);
node_id, log_msg!(msg.data));
} else {
log_trace!(logger, "Handling DisconnectPeer HandleError event in peer_handler for node {}",
node_id);
Expand All@@ -3228,7 +3227,7 @@ where
},
msgs::ErrorAction::DisconnectPeerWithWarning { msg } => {
log_trace!(logger, "Handling DisconnectPeer HandleError event in peer_handler for node {} with message {}",
node_id, msg.data);
node_id, log_msg!(msg.data));
// We do not have the peers write lock, so we just store that we're
// about to disconnect the peer and do it after we finish
// processing most messages.
Expand All@@ -3254,7 +3253,7 @@ where
msgs::ErrorAction::SendErrorMessage { ref msg } => {
log_trace!(logger, "Handling SendErrorMessage HandleError event in peer_handler for node {} with message {}",
node_id,
msg.data);
log_msg!(msg.data));
self.enqueue_message(
&mut *get_peer_for_forwarding!(&node_id)?,
msg,
Expand All@@ -3266,7 +3265,7 @@ where
} => {
log_given_level!(logger, *log_level, "Handling SendWarningMessage HandleError event in peer_handler for node {} with message {}",
node_id,
msg.data);
log_msg!(msg.data));
self.enqueue_message(
&mut *get_peer_for_forwarding!(&node_id)?,
msg,
Expand Down
3 changes: 2 additions & 1 deletion lightning/src/onion_message/dns_resolution.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -520,7 +520,8 @@ impl OMNameResolver {
.filter_map(|data| String::from_utf8(data).ok())
.filter(|data_string| data_string.len() > URI_PREFIX.len())
.filter(|data_string| {
data_string[..URI_PREFIX.len()].eq_ignore_ascii_case(URI_PREFIX)
let pfx = &data_string.as_bytes()[..URI_PREFIX.len()];
pfx.eq_ignore_ascii_case(URI_PREFIX.as_bytes())
});
// Check that there is exactly one TXT record that begins with
// bitcoin: as required by BIP 353 (and is valid UTF-8).
Expand Down
Loading