Skip to content

Commit 511c531

Browse files
authored
Merge pull request #15 from TheBlueMatt/master
Add fuzz targets, bump bitcoin/secp256k1 deps
2 parents bceb952 + 2188192 commit 511c531

16 files changed

+969
-122
lines changed

.travis.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,15 @@
11
language: rust
22
rust:
33
- stable
4+
- beta
5+
- 1.22.0
46
cache: cargo
7+
8+
before_install:
9+
- sudo apt-get -qq update
10+
- sudo apt-get install -y binutils-dev libunwind8-dev
11+
12+
script:
13+
- cargo build --verbose
14+
- cargo test --verbose
15+
- if [ "$(rustup show | grep default | grep stable)" != "" ]; then cd fuzz && cargo test --verbose && ./travis-fuzz.sh; fi

Cargo.toml

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,10 @@ Still super-early code-dump quality and is missing large chunks. See README in g
1212
[features]
1313
# Supports tracking channels with a non-bitcoin chain hashes. Currently enables all kinds of fun DoS attacks.
1414
non_bitcoin_chain_hash_routing = []
15+
fuzztarget = ["secp256k1/fuzztarget", "bitcoin/fuzztarget"]
1516

1617
[dependencies]
17-
bitcoin = "0.11"
18+
bitcoin = "0.12"
1819
rust-crypto = "0.2"
1920
rand = "0.4"
20-
secp256k1 = "0.8"
21-
22-
[patch.crates-io]
23-
bitcoin = { git = "https://github.com/rust-bitcoin/rust-bitcoin", branch = "master" }
21+
secp256k1 = "0.9"

fuzz/Cargo.toml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
[package]
2+
name = "lightning-fuzz"
3+
version = "0.0.1"
4+
authors = ["Automatically generated"]
5+
publish = false
6+
7+
[package.metadata]
8+
cargo-fuzz = true
9+
10+
[features]
11+
afl_fuzz = ["afl"]
12+
honggfuzz_fuzz = ["honggfuzz"]
13+
14+
[dependencies]
15+
lightning = { path = "..", features = ["fuzztarget"] }
16+
bitcoin = { version = "0.12", features = ["fuzztarget"] }
17+
secp256k1 = { version = "0.9", features = ["fuzztarget"] }
18+
honggfuzz = { version = "0.5", optional = true }
19+
afl = { version = "0.3", optional = true }
20+
21+
# Prevent this from interfering with workspaces
22+
[workspace]
23+
members = ["."]
24+
25+
[[bin]]
26+
name = "peer_crypt_target"
27+
path = "fuzz_targets/peer_crypt_target.rs"
28+
29+
[[bin]]
30+
name = "channel_target"
31+
path = "fuzz_targets/channel_target.rs"
32+
33+
[[bin]]
34+
name = "full_stack_target"
35+
path = "fuzz_targets/full_stack_target.rs"

fuzz/fuzz_targets/channel_target.rs

Lines changed: 299 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,299 @@
1+
extern crate bitcoin;
2+
extern crate lightning;
3+
extern crate secp256k1;
4+
5+
use bitcoin::blockdata::block::BlockHeader;
6+
use bitcoin::blockdata::transaction::Transaction;
7+
use bitcoin::util::hash::Sha256dHash;
8+
use bitcoin::network::serialize::{serialize, BitcoinHash};
9+
10+
use lightning::ln::channel::Channel;
11+
use lightning::ln::channelmanager::PendingForwardHTLCInfo;
12+
use lightning::ln::msgs;
13+
use lightning::ln::msgs::MsgDecodable;
14+
use lightning::chain::chaininterface::{FeeEstimator, ConfirmationTarget};
15+
16+
use secp256k1::key::PublicKey;
17+
use secp256k1::Secp256k1;
18+
19+
use std::sync::atomic::{AtomicUsize,Ordering};
20+
21+
#[inline]
22+
pub fn slice_to_be16(v: &[u8]) -> u16 {
23+
((v[0] as u16) << 8*1) |
24+
((v[1] as u16) << 8*0)
25+
}
26+
27+
#[inline]
28+
pub fn slice_to_be32(v: &[u8]) -> u32 {
29+
((v[0] as u32) << 8*3) |
30+
((v[1] as u32) << 8*2) |
31+
((v[2] as u32) << 8*1) |
32+
((v[3] as u32) << 8*0)
33+
}
34+
35+
#[inline]
36+
pub fn slice_to_be64(v: &[u8]) -> u64 {
37+
((v[0] as u64) << 8*7) |
38+
((v[1] as u64) << 8*6) |
39+
((v[2] as u64) << 8*5) |
40+
((v[3] as u64) << 8*4) |
41+
((v[4] as u64) << 8*3) |
42+
((v[5] as u64) << 8*2) |
43+
((v[6] as u64) << 8*1) |
44+
((v[7] as u64) << 8*0)
45+
}
46+
47+
#[inline]
48+
fn slice_to_be24(v: &[u8]) -> u64 {
49+
//TODO: We should probably be returning a Result for channel creation, not panic!()ing on
50+
//>2**24 values...
51+
((v[0] as u64) << 8*2) |
52+
((v[1] as u64) << 8*1) |
53+
((v[2] as u64) << 8*0)
54+
}
55+
56+
struct InputData<'a> {
57+
data: &'a [u8],
58+
read_pos: AtomicUsize,
59+
}
60+
impl<'a> InputData<'a> {
61+
fn get_slice(&self, len: usize) -> Option<&'a [u8]> {
62+
let old_pos = self.read_pos.fetch_add(len, Ordering::AcqRel);
63+
if self.data.len() < old_pos + len {
64+
return None;
65+
}
66+
Some(&self.data[old_pos..old_pos + len])
67+
}
68+
fn get_slice_nonadvancing(&self, len: usize) -> Option<&'a [u8]> {
69+
let old_pos = self.read_pos.load(Ordering::Acquire);
70+
if self.data.len() < old_pos + len {
71+
return None;
72+
}
73+
Some(&self.data[old_pos..old_pos + len])
74+
}
75+
}
76+
77+
struct FuzzEstimator<'a> {
78+
input: &'a InputData<'a>,
79+
}
80+
impl<'a> FeeEstimator for FuzzEstimator<'a> {
81+
fn get_est_sat_per_vbyte(&self, _: ConfirmationTarget) -> u64 {
82+
//TODO: We should actually be testing at least much more than 64k...
83+
match self.input.get_slice(2) {
84+
Some(slice) => slice_to_be16(slice) as u64,
85+
None => 0
86+
}
87+
}
88+
}
89+
90+
#[inline]
91+
pub fn do_test(data: &[u8]) {
92+
let input = InputData {
93+
data,
94+
read_pos: AtomicUsize::new(0),
95+
};
96+
let fee_est = FuzzEstimator {
97+
input: &input,
98+
};
99+
100+
macro_rules! get_slice {
101+
($len: expr) => {
102+
match input.get_slice($len as usize) {
103+
Some(slice) => slice,
104+
None => return,
105+
}
106+
}
107+
}
108+
109+
macro_rules! decode_msg {
110+
($MsgType: path, $len: expr) => {
111+
match <($MsgType)>::decode(get_slice!($len)) {
112+
Ok(msg) => msg,
113+
Err(e) => match e {
114+
msgs::DecodeError::UnknownRealmByte => return,
115+
msgs::DecodeError::BadPublicKey => return,
116+
msgs::DecodeError::BadSignature => return,
117+
msgs::DecodeError::WrongLength => panic!("We picked the length..."),
118+
}
119+
}
120+
}
121+
}
122+
123+
macro_rules! decode_msg_with_len16 {
124+
($MsgType: path, $begin_len: expr, $factor: expr) => {
125+
{
126+
let extra_len = slice_to_be16(&match input.get_slice_nonadvancing($begin_len as usize + 2) {
127+
Some(slice) => slice,
128+
None => return,
129+
}[$begin_len..$begin_len + 2]);
130+
match <($MsgType)>::decode(get_slice!($begin_len as usize + 2 + (extra_len as usize)*$factor)) {
131+
Ok(msg) => msg,
132+
Err(e) => match e {
133+
msgs::DecodeError::UnknownRealmByte => return,
134+
msgs::DecodeError::BadPublicKey => return,
135+
msgs::DecodeError::BadSignature => return,
136+
msgs::DecodeError::WrongLength => panic!("We picked the length..."),
137+
}
138+
}
139+
}
140+
}
141+
}
142+
143+
let secp_ctx = Secp256k1::new();
144+
macro_rules! get_pubkey {
145+
() => {
146+
match PublicKey::from_slice(&secp_ctx, get_slice!(33)) {
147+
Ok(key) => key,
148+
Err(_) => return,
149+
}
150+
}
151+
}
152+
153+
macro_rules! return_err {
154+
($expr: expr) => {
155+
match $expr {
156+
Ok(_) => {},
157+
Err(_) => return,
158+
}
159+
}
160+
}
161+
162+
let their_pubkey = get_pubkey!();
163+
164+
let tx = Transaction { version: 0, lock_time: 0, input: Vec::new(), output: Vec::new(), witness: Vec::new() };
165+
let funding_output = (Sha256dHash::from_data(&serialize(&tx).unwrap()[..]), 0);
166+
167+
let mut channel = if get_slice!(1)[0] != 0 {
168+
let mut chan = Channel::new_outbound(&fee_est, their_pubkey, slice_to_be24(get_slice!(3)), get_slice!(1)[0] == 0, slice_to_be64(get_slice!(8)));
169+
chan.get_open_channel(Sha256dHash::from(get_slice!(32)), &fee_est).unwrap();
170+
let accept_chan = if get_slice!(1)[0] == 0 {
171+
decode_msg_with_len16!(msgs::AcceptChannel, 270, 1)
172+
} else {
173+
decode_msg!(msgs::AcceptChannel, 270)
174+
};
175+
return_err!(chan.accept_channel(&accept_chan));
176+
chan.get_outbound_funding_created(funding_output.0.clone(), funding_output.1).unwrap();
177+
let funding_signed = decode_msg!(msgs::FundingSigned, 32+64);
178+
return_err!(chan.funding_signed(&funding_signed));
179+
chan
180+
} else {
181+
let open_chan = if get_slice!(1)[0] == 0 {
182+
decode_msg_with_len16!(msgs::OpenChannel, 2*32+6*8+4+2*2+6*33+1, 1)
183+
} else {
184+
decode_msg!(msgs::OpenChannel, 2*32+6*8+4+2*2+6*33+1)
185+
};
186+
let mut chan = match Channel::new_from_req(&fee_est, their_pubkey, &open_chan, slice_to_be64(get_slice!(8)), get_slice!(1)[0] == 0) {
187+
Ok(chan) => chan,
188+
Err(_) => return,
189+
};
190+
chan.get_accept_channel().unwrap();
191+
let mut funding_created = decode_msg!(msgs::FundingCreated, 32+32+2+64);
192+
funding_created.funding_txid = funding_output.0.clone();
193+
funding_created.funding_output_index = funding_output.1;
194+
return_err!(chan.funding_created(&funding_created));
195+
chan
196+
};
197+
198+
let mut header = BlockHeader { version: 0x20000000, prev_blockhash: Default::default(), merkle_root: Default::default(), time: 42, bits: 42, nonce: 42 };
199+
channel.block_connected(&header, 1, &[&tx; 1], &[42; 1]);
200+
for i in 2..100 {
201+
header = BlockHeader { version: 0x20000000, prev_blockhash: header.bitcoin_hash(), merkle_root: Default::default(), time: 42, bits: 42, nonce: 42 };
202+
channel.block_connected(&header, i, &[&tx; 0], &[0; 0]);
203+
}
204+
205+
let funding_locked = decode_msg!(msgs::FundingLocked, 32+33);
206+
return_err!(channel.funding_locked(&funding_locked));
207+
208+
loop {
209+
match get_slice!(1)[0] {
210+
0 => {
211+
return_err!(channel.send_htlc(slice_to_be64(get_slice!(8)), [42; 32], slice_to_be32(get_slice!(4)), msgs::OnionPacket {
212+
version: get_slice!(1)[0],
213+
public_key: get_pubkey!(),
214+
hop_data: [0; 20*65],
215+
hmac: [0; 32],
216+
}));
217+
},
218+
1 => {
219+
return_err!(channel.send_commitment());
220+
},
221+
2 => {
222+
let update_add_htlc = decode_msg!(msgs::UpdateAddHTLC, 32+8+8+32+4+4+33+20*65+32);
223+
return_err!(channel.update_add_htlc(&update_add_htlc, PendingForwardHTLCInfo::dummy()));
224+
},
225+
3 => {
226+
let update_fulfill_htlc = decode_msg!(msgs::UpdateFulfillHTLC, 32 + 8 + 32);
227+
return_err!(channel.update_fulfill_htlc(&update_fulfill_htlc));
228+
},
229+
4 => {
230+
let update_fail_htlc = decode_msg_with_len16!(msgs::UpdateFailHTLC, 32 + 8, 1);
231+
return_err!(channel.update_fail_htlc(&update_fail_htlc));
232+
},
233+
5 => {
234+
let update_fail_malformed_htlc = decode_msg!(msgs::UpdateFailMalformedHTLC, 32+8+32+2);
235+
return_err!(channel.update_fail_malformed_htlc(&update_fail_malformed_htlc));
236+
},
237+
6 => {
238+
let commitment_signed = decode_msg_with_len16!(msgs::CommitmentSigned, 32+64, 64);
239+
return_err!(channel.commitment_signed(&commitment_signed));
240+
},
241+
7 => {
242+
let revoke_and_ack = decode_msg!(msgs::RevokeAndACK, 32+32+33);
243+
return_err!(channel.revoke_and_ack(&revoke_and_ack));
244+
},
245+
8 => {
246+
let update_fee = decode_msg!(msgs::UpdateFee, 32+4);
247+
return_err!(channel.update_fee(&fee_est, &update_fee));
248+
},
249+
_ => return,
250+
}
251+
}
252+
}
253+
254+
#[cfg(feature = "afl")]
255+
extern crate afl;
256+
#[cfg(feature = "afl")]
257+
fn main() {
258+
afl::read_stdio_bytes(|data| {
259+
do_test(&data);
260+
});
261+
}
262+
263+
#[cfg(feature = "honggfuzz")]
264+
#[macro_use] extern crate honggfuzz;
265+
#[cfg(feature = "honggfuzz")]
266+
fn main() {
267+
loop {
268+
fuzz!(|data| {
269+
do_test(data);
270+
});
271+
}
272+
}
273+
274+
#[cfg(test)]
275+
mod tests {
276+
fn extend_vec_from_hex(hex: &str, out: &mut Vec<u8>) {
277+
let mut b = 0;
278+
for (idx, c) in hex.as_bytes().iter().enumerate() {
279+
b <<= 4;
280+
match *c {
281+
b'A'...b'F' => b |= c - b'A' + 10,
282+
b'a'...b'f' => b |= c - b'a' + 10,
283+
b'0'...b'9' => b |= c - b'0',
284+
_ => panic!("Bad hex"),
285+
}
286+
if (idx & 1) == 1 {
287+
out.push(b);
288+
b = 0;
289+
}
290+
}
291+
}
292+
293+
#[test]
294+
fn duplicate_crash() {
295+
let mut a = Vec::new();
296+
extend_vec_from_hex("00", &mut a);
297+
super::do_test(&a);
298+
}
299+
}

0 commit comments

Comments
 (0)