Skip to content

Commit 428746a

Browse files
authored
Merge pull request #4715 from tnull/tnull/2026-05-lspsdatetime-pre-epoch
Reject pre-epoch `LSPSDateTime` at parse time
2 parents 61e3cab + 837763a commit 428746a

1 file changed

Lines changed: 29 additions & 3 deletions

File tree

  • lightning-liquidity/src/lsps0

lightning-liquidity/src/lsps0/ser.rs

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ impl Readable for LSPSRequestId {
234234
}
235235

236236
/// An object representing datetimes as described in bLIP-50 / LSPS0.
237-
#[derive(Clone, Debug, Copy, PartialEq, Eq, Hash, Deserialize, Serialize)]
237+
#[derive(Clone, Debug, Copy, PartialEq, Eq, Hash, Serialize)]
238238
#[serde(transparent)]
239239
pub struct LSPSDateTime(pub chrono::DateTime<chrono::Utc>);
240240

@@ -275,8 +275,23 @@ impl LSPSDateTime {
275275
impl FromStr for LSPSDateTime {
276276
type Err = ();
277277
fn from_str(s: &str) -> Result<Self, Self::Err> {
278-
let datetime = chrono::DateTime::parse_from_rfc3339(s).map_err(|_| ())?;
279-
Ok(Self(datetime.into()))
278+
let datetime: chrono::DateTime<chrono::Utc> =
279+
chrono::DateTime::parse_from_rfc3339(s).map_err(|_| ())?.into();
280+
// Reject pre-epoch datetimes here so peer-controlled `valid_until` /
281+
// `expires_at` fields can never produce an `LSPSDateTime` with a negative
282+
// UNIX timestamp, which would otherwise panic the `i64 -> u64` cast in
283+
// `is_past`.
284+
if datetime.timestamp() < 0 {
285+
return Err(());
286+
}
287+
Ok(Self(datetime))
288+
}
289+
}
290+
291+
impl<'de> Deserialize<'de> for LSPSDateTime {
292+
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
293+
let s = String::deserialize(deserializer)?;
294+
Self::from_str(&s).map_err(|()| de::Error::custom("invalid LSPSDateTime"))
280295
}
281296
}
282297

@@ -996,4 +1011,15 @@ mod tests {
9961011
assert_eq!(later.duration_since(&earlier), Duration::from_secs(60));
9971012
assert_eq!(earlier.duration_since(&later), Duration::ZERO);
9981013
}
1014+
1015+
#[test]
1016+
fn is_past_handles_pre_epoch_datetime() {
1017+
// A peer-controlled RFC3339 datetime before 1970 must be rejected at parse
1018+
// time, so it can never reach `is_past` (or any other consumer) and panic.
1019+
assert!(LSPSDateTime::from_str("1900-01-01T00:00:00Z").is_err());
1020+
1021+
// JSON deserialization (the path peer messages take) must reject it too.
1022+
let json = "\"1900-01-01T00:00:00Z\"";
1023+
assert!(serde_json::from_str::<LSPSDateTime>(json).is_err());
1024+
}
9991025
}

0 commit comments

Comments
 (0)