Skip to content

Commit 18f5a33

Browse files
genforAIcursoragentedi-oai
authored
Require BOOTP chaddr match for host→VM DHCP responses (#192)
* Require BOOTP chaddr match for host→VM DHCP responses Mirror #191 request-path identity checks: admit/forward DHCP BootReplies only when chaddr matches the VM MAC, so foreign client replies are not written into the guest fd. Co-authored-by: Cursor <cursoragent@cursor.com> * $ cargo fmt --------- Co-authored-by: genforAI <genforAI@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Nikolay Edigaryev <edi@openai.com>
1 parent 0c4327d commit 18f5a33

1 file changed

Lines changed: 73 additions & 3 deletions

File tree

lib/proxy/host.rs

Lines changed: 73 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
use crate::dhcp_snooper::message_matches_bootp_client;
12
use crate::proxy::flows::{FlowDirection, FlowMatch};
23
use crate::proxy::udp_packet_helper::UdpPacketHelper;
34
use crate::proxy::{Direction, PolicyDecision, Proxy};
45
use anyhow::{Context, Result};
6+
use dhcproto::Decodable;
7+
use dhcproto::v4::Opcode;
58
use smoltcp::phy::ChecksumCapabilities;
69
use smoltcp::wire::{EthernetFrame, EthernetProtocol, Ipv4Packet, Ipv4Repr, UdpPacket};
710

@@ -141,8 +144,75 @@ impl Proxy<'_> {
141144
return false;
142145
}
143146

144-
UdpPacket::new_checked(ipv4_pkt.payload())
145-
.map(|udp_pkt| udp_pkt.is_dhcp_response())
146-
.unwrap_or(false)
147+
let Ok(udp_pkt) = UdpPacket::new_checked(ipv4_pkt.payload()) else {
148+
return false;
149+
};
150+
151+
// Require the standard DHCP server and client ports
152+
if !udp_pkt.is_dhcp_response() {
153+
return false;
154+
}
155+
156+
// Require the BOOTP client hardware address to match this VM
157+
// (symmetric with is_allowed_dhcp_request / #191 on the VM→host path)
158+
let mut decoder = dhcproto::v4::Decoder::new(udp_pkt.payload());
159+
let Ok(message) = dhcproto::v4::Message::decode(&mut decoder) else {
160+
return false;
161+
};
162+
163+
message_matches_bootp_client(&message, Opcode::BootReply, self.vm_mac_address.0)
164+
}
165+
}
166+
167+
#[cfg(test)]
168+
mod tests {
169+
use crate::dhcp_snooper::message_matches_bootp_client;
170+
use dhcproto::Decodable;
171+
use dhcproto::v4::{DhcpOption, Message, MessageType, Opcode};
172+
use dhcproto::{Encodable, Encoder};
173+
use smoltcp::wire::Ipv4Address;
174+
175+
const VM_MAC: [u8; 6] = [0x02, 0x00, 0x00, 0x00, 0x00, 0x01];
176+
const OTHER_MAC: [u8; 6] = [0x02, 0x00, 0x00, 0x00, 0x00, 0x02];
177+
178+
#[test]
179+
fn dhcp_boot_reply_chaddr_must_match_vm() {
180+
let own = encode_boot_reply(VM_MAC);
181+
let foreign = encode_boot_reply(OTHER_MAC);
182+
183+
let mut dec = dhcproto::v4::Decoder::new(&own);
184+
let own_msg = Message::decode(&mut dec).unwrap();
185+
let mut dec = dhcproto::v4::Decoder::new(&foreign);
186+
let foreign_msg = Message::decode(&mut dec).unwrap();
187+
188+
assert!(message_matches_bootp_client(
189+
&own_msg,
190+
Opcode::BootReply,
191+
VM_MAC
192+
));
193+
assert!(!message_matches_bootp_client(
194+
&foreign_msg,
195+
Opcode::BootReply,
196+
VM_MAC
197+
));
198+
}
199+
200+
fn encode_boot_reply(chaddr: [u8; 6]) -> Vec<u8> {
201+
let mut message = Message::new(
202+
Ipv4Address::UNSPECIFIED,
203+
Ipv4Address::new(192, 168, 64, 2),
204+
Ipv4Address::UNSPECIFIED,
205+
Ipv4Address::UNSPECIFIED,
206+
&chaddr,
207+
);
208+
message.set_opcode(Opcode::BootReply);
209+
message
210+
.opts_mut()
211+
.insert(DhcpOption::MessageType(MessageType::Ack));
212+
message.opts_mut().insert(DhcpOption::AddressLeaseTime(600));
213+
214+
let mut encoded = Vec::new();
215+
message.encode(&mut Encoder::new(&mut encoded)).unwrap();
216+
encoded
147217
}
148218
}

0 commit comments

Comments
 (0)