Skip to content

Commit 3df6546

Browse files
authored
Create a gRPC interface for the untrusted loader (#2705)
This PR does three things: * adds a really trivial echo grpc interface * implements the gRPC server in the untrusted loader (ripping out the hacky stdio code) * and finally, adds a really trivial gRPC client to talk to the loader. Thus, the poor little string will first travel from the client to loader via gRPC, where it's unpacked and stuffed into CBOR and sent via serial to the UEFI app, which will then echo the string back via the same channels. But hey, we've got something working end-to-end!
1 parent edbf426 commit 3df6546

File tree

12 files changed

+315
-42
lines changed

12 files changed

+315
-42
lines changed

Cargo.lock

Lines changed: 25 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ members = [
1212
"experimental/trusted_shuffler/common",
1313
"experimental/trusted_shuffler/server",
1414
"experimental/trusted_shuffler/trusted_shuffler",
15+
"experimental/uefi/client",
1516
"experimental/uefi/loader",
1617
"oak_functions/abi",
1718
"oak_functions/client/rust",

experimental/uefi/app/src/main.rs

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ mod serial;
3939
// there's exactly one function with the `#[entry]` attribute in the
4040
// dependency graph.
4141
#[entry]
42-
fn _start(_handle: Handle, mut system_table: SystemTable<Boot>) -> Status {
42+
fn _start(handle: Handle, mut system_table: SystemTable<Boot>) -> Status {
4343
uefi_services::init(&mut system_table).unwrap();
4444

4545
// As we're not relying on the normal Rust test harness, we need to call
@@ -49,7 +49,7 @@ fn _start(_handle: Handle, mut system_table: SystemTable<Boot>) -> Status {
4949
test_main();
5050
Status::SUCCESS
5151
} else {
52-
main(_handle, &mut system_table)
52+
main(handle, &mut system_table)
5353
};
5454

5555
// After we're done running our code, we also tell the UEFI runtime to shut
@@ -59,20 +59,32 @@ fn _start(_handle: Handle, mut system_table: SystemTable<Boot>) -> Status {
5959
.reset(ResetType::Shutdown, status, None);
6060
}
6161

62+
// Run the echo on the first serial port in the system (the UEFI console will
63+
// use the first serial port in the system)
64+
const ECHO_SERIAL_PORT_INDEX: usize = 1;
65+
6266
fn main(handle: Handle, system_table: &mut SystemTable<Boot>) -> Status {
6367
use core::fmt::Write;
6468

6569
writeln!(system_table.stdout(), "Hello World!").unwrap();
6670

67-
serial_echo(handle, system_table.boot_services()).unwrap();
71+
serial_echo(handle, system_table.boot_services(), ECHO_SERIAL_PORT_INDEX).unwrap();
6872
}
6973

70-
// Run the echo on the first serial port in the system (the UEFI console will
71-
// use the first serial port in the system)
72-
const ECHO_SERIAL_PORT_INDEX: usize = 1;
73-
74-
fn serial_echo(handle: Handle, bt: &BootServices) -> Result<!, uefi::Error<()>> {
75-
let mut serial = serial::Serial::get(handle, bt, ECHO_SERIAL_PORT_INDEX)?;
74+
// Opens the index-th serial port on the system and echoes back all frames sent over the serial
75+
// port.
76+
//
77+
// Panics if the index-th serial port does not exist.
78+
//
79+
// Arguments:
80+
// * `handle` - UEFI handle of the agent (eg of the UEFI app)
81+
// * `boot_services` - reference to the UEFI Boot Services struct (obtained from the system
82+
// table)
83+
// * `index` - index of the serial port, zero-based.
84+
//
85+
// Normally does not return, unless an error is raised.
86+
fn serial_echo(handle: Handle, bt: &BootServices, index: usize) -> Result<!, uefi::Error<()>> {
87+
let mut serial = serial::Serial::get(handle, bt, index)?;
7688
loop {
7789
let msg: alloc::string::String = de::from_reader(&mut serial).map_err(|err| match err {
7890
de::Error::Io(err) => err,
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[package]
2+
name = "uefi-client"
3+
version = "0.1.0"
4+
authors = ["Andri Saar <[email protected]>"]
5+
edition = "2021"
6+
license = "Apache-2.0"
7+
8+
[dependencies]
9+
clap = { version = "*", features = ["derive"] }
10+
prost = "*"
11+
tokio = { version = "*", features = ["macros", "rt-multi-thread"] }
12+
tonic = "*"
13+
14+
[build-dependencies]
15+
oak_utils = { path = "../../../oak_utils" }

experimental/uefi/client/build.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//
2+
// Copyright 2022 The Project Oak Authors
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
//
16+
17+
use oak_utils::{generate_grpc_code, CodegenOptions};
18+
19+
fn main() -> Result<(), Box<dyn std::error::Error>> {
20+
generate_grpc_code(
21+
"../../../",
22+
&["experimental/uefi/proto/echo.proto"],
23+
CodegenOptions {
24+
build_client: true,
25+
build_server: false,
26+
extern_paths: vec![],
27+
},
28+
)?;
29+
Ok(())
30+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
//
2+
// Copyright 2022 The Project Oak Authors
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
//
16+
17+
use std::io::{stdin, BufRead};
18+
19+
use clap::Parser;
20+
use tonic::Request;
21+
22+
pub mod proto {
23+
tonic::include_proto!("oak.experimental.uefi");
24+
}
25+
26+
use proto::{echo_client::EchoClient, EchoRequest};
27+
28+
#[derive(Parser, Debug)]
29+
struct Args {
30+
/// address of the server
31+
#[clap(long, default_value = "http://127.0.0.1:8000")]
32+
server: String,
33+
}
34+
35+
#[tokio::main]
36+
async fn main() -> Result<(), Box<dyn std::error::Error>> {
37+
let cli = Args::parse();
38+
39+
let mut client = EchoClient::connect(cli.server).await?;
40+
41+
for line in stdin().lock().lines() {
42+
let request = Request::new(EchoRequest {
43+
message: line.unwrap(),
44+
});
45+
let response = client.echo(request).await?;
46+
println!("Response: {:?}", response);
47+
}
48+
49+
println!("Hello, world!");
50+
Ok(())
51+
}

experimental/uefi/loader/Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ license = "Apache-2.0"
77

88
[dependencies]
99
anyhow = "*"
10+
bmrng = "*"
1011
clap = { version = "*", features = ["derive"] }
1112
command-fds = { version = "*", features = ["tokio"] }
1213
futures = "*"
1314
log = "*"
1415
env_logger = "*"
16+
prost = "*"
1517
serde = "*"
1618
tokio = { version = "*", features = [
1719
"io-std",
@@ -21,7 +23,12 @@ tokio = { version = "*", features = [
2123
"net",
2224
"process",
2325
"signal",
26+
"sync",
2427
] }
2528
tokio-serde-cbor = "*"
2629
# fixed to 0.6 because of tokio-serde-cbor
2730
tokio-util = { version = "~0.6", features = ["codec"] }
31+
tonic = "*"
32+
33+
[build-dependencies]
34+
oak_utils = { path = "../../../oak_utils" }

experimental/uefi/loader/build.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//
2+
// Copyright 2022 The Project Oak Authors
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
//
16+
17+
use oak_utils::{generate_grpc_code, CodegenOptions};
18+
19+
fn main() -> Result<(), Box<dyn std::error::Error>> {
20+
generate_grpc_code(
21+
"../../../",
22+
&["experimental/uefi/proto/echo.proto"],
23+
CodegenOptions {
24+
build_client: false,
25+
build_server: true,
26+
extern_paths: vec![],
27+
},
28+
)?;
29+
Ok(())
30+
}

experimental/uefi/loader/src/main.rs

Lines changed: 36 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,12 @@ use clap::Parser;
2020
use futures::{stream::StreamExt, SinkExt};
2121
use log::info;
2222
use qemu::{Qemu, QemuParams};
23-
use tokio::{
24-
io::{self, AsyncReadExt},
25-
net::UnixStream,
26-
signal,
27-
};
23+
use tokio::{io::AsyncReadExt, net::UnixStream, signal};
2824
use tokio_serde_cbor::Codec;
2925
use tokio_util::codec::Decoder;
26+
3027
mod qemu;
28+
mod server;
3129

3230
#[derive(Parser, Debug)]
3331
struct Args {
@@ -60,7 +58,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
6058
let (console_qemu, console) = UnixStream::pair()?;
6159
let (comms_qemu, mut comms) = UnixStream::pair()?;
6260

63-
let qemu = Qemu::start(QemuParams {
61+
let mut qemu = Qemu::start(QemuParams {
6462
binary: cli.qemu.as_path(),
6563
firmware: cli.ovmf.as_path(),
6664
app: cli.uefi_app.as_path(),
@@ -91,39 +89,45 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
9189
}
9290
info!("Leading junk on comms: {:?}", std::str::from_utf8(&junk));
9391

94-
// Set up the CBOR codec to handle the comms.
95-
let codec: Codec<std::string::String, std::string::String> = Codec::new();
96-
let (mut sink, mut stream) = codec.framed(comms).split();
92+
// Use a bmrng channel for serial communication. The good thing about spawning a separate
93+
// task for the serial communication is that it serializes (no pun intended) the communication,
94+
// as right now we don't have any mechanisms to track multiple requests in flight.
95+
let (tx, mut rx) = bmrng::unbounded_channel::<String, anyhow::Result<String>>();
9796

98-
// Bit hacky, but it's only temporary and turns out console I/O is complex.
9997
tokio::spawn(async move {
100-
loop {
101-
if let Some(result) = stream.next().await {
102-
match result {
103-
Ok(msg) => info!("rx: {:?}", msg),
104-
Err(e) => {
105-
info!("recv error: {:?}", e);
98+
// Set up the CBOR codec to handle the comms.
99+
let codec: Codec<String, String> = Codec::new();
100+
let mut channel = codec.framed(comms);
101+
while let Ok((input, responder)) = rx.recv().await {
102+
responder
103+
.respond({
104+
if let Err(err) = channel.send(input).await {
105+
Err(anyhow::Error::from(err))
106+
} else {
107+
// TODO(#2726): Sometimes next() gives us a None. Figure out what's going on
108+
// in there.
109+
let mut response;
110+
loop {
111+
response = channel.next().await;
112+
if response.is_some() {
113+
break;
114+
}
115+
}
116+
response.unwrap().map_err(anyhow::Error::from)
106117
}
107-
};
108-
}
109-
}
110-
});
111-
tokio::spawn(async move {
112-
let mut buf = [0; 1024];
113-
loop {
114-
let result = io::stdin().read(&mut buf).await.unwrap();
115-
if result == 0 {
116-
break;
117-
} else {
118-
let msg = std::str::from_utf8(&buf[..result]).unwrap().to_string();
119-
info!("tx: {:?}", &msg);
120-
sink.send(msg).await.unwrap();
121-
}
118+
})
119+
.unwrap();
122120
}
123121
});
124122

125-
signal::ctrl_c().await?;
123+
let server_future = server::server("127.0.0.1:8000".parse()?, tx);
126124

125+
// Wait until something dies or we get a signal to terminate.
126+
tokio::select! {
127+
_ = signal::ctrl_c() => {},
128+
_ = server_future => {},
129+
_ = qemu.wait() => {},
130+
}
127131
// Clean up.
128132
qemu.kill().await?;
129133
Ok(())

experimental/uefi/loader/src/qemu.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,12 +112,16 @@ impl Qemu {
112112
})
113113
}
114114

115+
pub async fn wait(&mut self) -> Result<std::process::ExitStatus> {
116+
self.instance.wait().await.map_err(anyhow::Error::from)
117+
}
118+
115119
pub async fn kill(mut self) -> Result<std::process::ExitStatus> {
116120
info!("Cleaning up and shutting down.");
117121
self.console.shutdown().await?;
118122
self.comms.shutdown().await?;
119123
self.qmp.shutdown().await?;
120124
self.instance.start_kill()?;
121-
self.instance.wait().await.map_err(anyhow::Error::from)
125+
self.wait().await
122126
}
123127
}

0 commit comments

Comments
 (0)