CVE-2023-38505 (Rust, published 2023-07-27T19:15Z)
Description: value: DietPi-Dashboard is a web dashboard for the operating system DietPi. The dashboard only allows for one TLS handshake to be in process at a given moment. Once a TCP connection is established in HTTPS mode, it will assume that it should be waiting for a handshake, and will stay this way indefinitely until a handshake starts or some error occurs. In version 0.6.1, this can be exploited by simply not starting the handshake, preventing any other TLS handshakes from getting through. An attacker can lock the dashboard in a state where it is waiting for a TLS handshake from the attacker, who won't provide it. This prevents any legitimate traffic from getting to the dashboard, and can last indefinitely. Version 0.6.2 has a patch for this issue. As a workaround, do not use HTTPS mode on the open internet where anyone can connect. Instead, put a reverse proxy in front of the dashboard, and have it handle any HTTPS connections.
Severity: nan (CVSS v3 base score: 7.5)
Weakness: CWE-667 \u2014 Improper Locking
The product does not properly acquire or release a lock on a resource, leading to unexpected resource state changes and behaviors.
Repository: https://github.com/ravenclaw900/DietPi-Dashboard
Files changed:
Cargo.lock
Cargo.toml
src/main.rs
Commit message: fix(backend): replace TLS implementation with `flexible-hyper-server-tls`

The old TLS implementation was vulnerable to a DOS attack


Vulnerable code:
version = "1.0.1"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
 "syn",
 "rustls-pemfile",
 "tokio-rustls",
 "miniz_oxide",
version = "0.3.23"
checksum = "2bfc52cbddcfd745bf1740338492bb0bd83d76c67b445f91c5fb29fae29ecaa1"
version = "0.3.23"
checksum = "d2acedae88d38235936c3922476b10fced7b2b68136f5e3c03c2d5be348a1115"
version = "0.3.23"
checksum = "93a66fc6d035a26a3ae255a6d2bca35eda63ae4c5512bef54449113f7a1228e5"
version = "0.3.23"
checksum = "0db9cce532b0eae2ccf2766ab246f114b56b9cf6d445e00c2549fbc100ca045d"
 "syn",
version = "0.3.23"
checksum = "ca0bae1fe9752cf7fd9b0064c674ae63f97b37bc714d745cbde0afb7ec4e6765"
version = "0.3.23"
checksum = "842fc63b931f4056a24d59de13fb1272134ce261816e063e634ad0c15cdc5306"
version = "0.3.23"
checksum = "f0828a5471e340229c11c77ca80017937ce3c58cb788a17e5f1c2d5c485a9577"
version = "1.7.1"
checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c"
version = "0.14.20"
checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac"
 "base64",
version = "0.2.127"
checksum = "505e71a4706fa491e9b1b55f51b95d4037d0821ee40131190475f692b35b009b"
version = "0.8.4"
checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf"
 "log",
 "syn",
version = "1.0.32"
checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43"
 "unicode-xid",
 "syn",
version = "1.0.10"
checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05"
version = "0.20.6"
checksum = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033"
 "webpki",
version = "1.0.1"
checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55"
 "base64",
 "syn",
version = "0.4.4"
checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0"
version = "1.0.30"
checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"
version = "1.0.30"
checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
 "syn",
version = "1.19.2"
checksum = "c51a52ed6686dd62c320f9b89299e9dfb46f730c7a48e635c19f21d116cb1439"
 "memchr",
 "once_cell",
 "winapi",
version = "1.7.0"
checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7"
 "syn",
version = "0.23.4"
checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59"
 "webpki",
 "syn",
 "base64",
 "syn",
 "syn",
[[package]]
name = "webpki"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd"
dependencies = [
 "ring",
 "untrusted",
]

version = "0.36.1"
checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2"
version = "0.36.1"
checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
version = "0.36.1"
checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
version = "0.36.1"
checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
version = "0.36.1"
checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1"
version = "0.36.1"
checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
tokio-rustls = "0.23.4"
rustls-pemfile = "1.0.1"
use futures::FutureExt;
use std::task::Poll;
enum TlsOrTcpConnection {
    Plain(tokio::net::TcpStream, std::net::SocketAddr),
    Tls(
        Box<tokio_rustls::server::TlsStream<tokio::net::TcpStream>>,
        std::net::SocketAddr,
    ),
}

struct HyperTlsOrTcpAcceptor {
    listener: tokio::net::TcpListener,
    acceptor: Option<tokio_rustls::TlsAcceptor>,
    accept_future: Option<tokio_rustls::Accept<tokio::net::TcpStream>>,
    remote_addr: Option<std::net::SocketAddr>,
}

impl HyperTlsOrTcpAcceptor {
    const fn new(
        listener: tokio::net::TcpListener,
        acceptor: Option<tokio_rustls::TlsAcceptor>,
    ) -> Self {
        Self {
            listener,
            acceptor,
            accept_future: None,
            remote_addr: None,
        }
    }
}

impl TlsOrTcpConnection {
    const fn remote_addr(&self) -> std::net::SocketAddr {
        match self {
            Self::Plain(_, remote_addr) | Self::Tls(_, remote_addr) => *remote_addr,
        }
    }
}

impl tokio::io::AsyncRead for TlsOrTcpConnection {
    fn poll_read(
        self: std::pin::Pin<&mut Self>,
        cx: &mut std::task::Context<'_>,
        buf: &mut tokio::io::ReadBuf<'_>,
    ) -> Poll<std::io::Result<()>> {
        match self.get_mut() {
            Self::Plain(tcp, _) => std::pin::Pin::new(tcp).poll_read(cx, buf),
            Self::Tls(tls, _) => std::pin::Pin::new(tls).poll_read(cx, buf),
        }
    }
}

impl tokio::io::AsyncWrite for TlsOrTcpConnection {
    fn poll_write(
        self: std::pin::Pin<&mut Self>,
        cx: &mut std::task::Context<'_>,
        buf: &[u8],
    ) -> Poll<Result<usize, std::io::Error>> {
        match self.get_mut() {
            Self::Plain(tcp, _) => std::pin::Pin::new(tcp).poll_write(cx, buf),
            Self::Tls(tls, _) => std::pin::Pin::new(tls).poll_write(cx, buf),
        }
    }

    fn poll_flush(
        self: std::pin::Pin<&mut Self>,
        cx: &mut std::task::Context<'_>,
    ) -> Poll<Result<(), std::io::Error>> {
        match self.get_mut() {
            Self::Plain(tcp, _) => std::pin::Pin::new(tcp).poll_flush(cx),
            Self::Tls(tls, _) => std::pin::Pin::new(tls).poll_flush(cx),
        }
    }

    fn poll_shutdown(
        self: std::pin::Pin<&mut Self>,
        cx: &mut std::task::Context<'_>,
    ) -> Poll<Result<(), std::io::Error>> {
        match self.get_mut() {
            Self::Plain(tcp, _) => std::pin::Pin::new(tcp).poll_shutdown(cx),
            Self::Tls(tls, _) => std::pin::Pin::new(tls).poll_shutdown(cx),
        }
    }
}

impl hyper::server::accept::Accept for &mut HyperTlsOrTcpAcceptor {
    type Conn = TlsOrTcpConnection;
    type Error = anyhow::Error;

    fn poll_accept(
        mut self: std::pin::Pin<&mut Self>,
        cx: &mut std::task::Context<'_>,
    ) -> Poll<Option<Result<Self::Conn, Self::Error>>> {
        if self.accept_future.is_none() {
            match self.listener.poll_accept(cx) {
                Poll::Ready(stream) => match stream {
                    Ok(stream) => {
                        if let Some(acceptor) = &self.acceptor {
                            self.accept_future = Some(acceptor.accept(stream.0));
                            self.remote_addr = Some(stream.1);
                        } else {
                            return Poll::Ready(Some(Ok(TlsOrTcpConnection::Plain(
                                stream.0, stream.1,
                            ))));
                        }
                    }
                    Err(err) => {
                        return Poll::Ready(Some(Err(err).context("Couldn't make TCP connection")));
                    }
                },
                Poll::Pending => return Poll::Pending,
            }
        }
        if let Some(accept_future) = &mut self.accept_future {
            match accept_future.poll_unpin(cx) {
                Poll::Pending => return Poll::Pending,
                Poll::Ready(tls) => {
                    self.accept_future = None;
                    let tls = match tls {
                        Ok(tls) => tls,
                        Err(err) => {
                            return Poll::Ready(Some(
                                Err(err).context("Couldn't encrypt TCP connection"),
                            ));
                        }
                    };
                    let remote_addr = self.remote_addr.take().unwrap_or_else(|| {
                        std::net::SocketAddr::from((std::net::Ipv4Addr::UNSPECIFIED, 0))
                    });
                    return Poll::Ready(Some(Ok(TlsOrTcpConnection::Tls(
                        Box::new(tls),
                        remote_addr,
                    ))));
                }
            }
        }
        Poll::Pending
    }
}

    let make_svc = make_service_fn(|conn: &TlsOrTcpConnection| {
    let mut acceptor = if CONFIG.tls {
        let tls_cfg = {
            let certs = rustls_pemfile::certs(&mut std::io::BufReader::new(
                std::fs::File::open(&CONFIG.cert).context("Couldn't open cert file")?,
            ))
            .context("Couldn't read certs")?
            .into_iter()
            .map(tokio_rustls::rustls::Certificate)
            .collect();

            let key = match rustls_pemfile::read_one(&mut std::io::BufReader::new(
                std::fs::File::open(&CONFIG.key).context("Couldn't open cert file")?,
            ))
            .context("Couldn't read key")?
            .context("No private key")?
            {
                rustls_pemfile::Item::PKCS8Key(vec) | rustls_pemfile::Item::RSAKey(vec) => {
                    tokio_rustls::rustls::PrivateKey(vec)
                }
                _ => anyhow::bail!("No PKCS8 or RSA formatted private key"),
            };

            let mut cfg = tokio_rustls::rustls::ServerConfig::builder()
                .with_safe_defaults()
                .with_no_client_auth()
                .with_single_cert(certs, key)
                .context("Couldn't build TLS config")?;
            cfg.alpn_protocols = vec![b"http/1.1".to_vec()];
            std::sync::Arc::new(cfg)
        };

        HyperTlsOrTcpAcceptor::new(tcp, Some(tokio_rustls::TlsAcceptor::from(tls_cfg)))
        HyperTlsOrTcpAcceptor::new(tcp, None)
        let _res = hyper::server::Server::builder(&mut acceptor)
            .serve(make_svc)
            .await
            .context("Server error")
            .or_else(|e| {
                tracing::warn!("{:?}", e);
                anyhow::Ok(())
            });

Fixed code:
[[package]]
name = "addr2line"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7a2e47a1fbe209ee101dd6d61285226744c6c8d3c21c8dc878ba6cb9f467f3a"
dependencies = [
 "gimli",
]

version = "1.1.0"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"

[[package]]
name = "backtrace"
version = "0.3.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4717cfcbfaa661a0fd48f8453951837ae7e8f81e481fbb136e3202d72805a744"
dependencies = [
 "addr2line",
 "cc",
 "cfg-if",
 "libc",
 "miniz_oxide 0.4.4",
 "object",
 "rustc-demangle",
]
[[package]]
name = "base64"
version = "0.21.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d"

 "syn 1.0.92",
 "flexible-hyper-server-tls",
 "miniz_oxide 0.5.3",
]

[[package]]
name = "flexible-hyper-server-tls"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87651e5b8ed9cb4a20c04296bfae1a4d10cf2546f0e057328ccb742c55fbc91e"
dependencies = [
 "futures-util",
 "hyper",
 "rustls-pemfile",
 "thiserror",
 "tokio",
 "tokio-rustls",
version = "0.3.28"
checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2"
version = "0.3.28"
checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"
version = "0.3.28"
checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964"
version = "0.3.28"
checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
 "syn 2.0.27",
version = "0.3.28"
checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e"
version = "0.3.28"
checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65"
version = "0.3.28"
checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533"
[[package]]
name = "gimli"
version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e4075386626662786ddb0ec9081e7c7eeb1ba31951f447ca780ef9f5d568189"

version = "1.8.0"
checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
version = "0.14.27"
checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468"
 "base64 0.13.0",
version = "0.2.147"
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
[[package]]
name = "miniz_oxide"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b"
dependencies = [
 "adler",
 "autocfg",
]

version = "0.8.8"
checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
[[package]]
name = "object"
version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a5b3dd1c072ee7963717671d1ca129f1048fda25edea6b752bfc71ac8854170"

 "syn 1.0.92",
version = "1.0.66"
checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
 "unicode-ident",
 "syn 1.0.92",
version = "1.0.32"
checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965"
[[package]]
name = "rustc-demangle"
version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"

version = "0.21.5"
checksum = "79ea77c539259495ce8ca47f53e66ae0330a8819f67e23ac96ca02f50e7b7d36"
 "rustls-webpki",
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2"
dependencies = [
 "base64 0.21.2",
]

[[package]]
name = "rustls-webpki"
version = "0.101.2"
checksum = "513722fd73ad80a71f72b61009ea1b584bcfa1483ca93949c8f290298837fa59"
 "ring",
 "untrusted",
 "syn 1.0.92",
version = "0.4.9"
checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662"
[[package]]
name = "syn"
version = "2.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b60f673f44a8255b9c8c657daf66a596d435f2da81a555b06dc644d080ba45e0"
dependencies = [
 "proc-macro2",
 "quote",
 "unicode-ident",
]

version = "1.0.44"
checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90"
version = "1.0.44"
checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96"
 "syn 2.0.27",
version = "1.29.1"
checksum = "532826ff75199d5833b9d2c5fe410f29235e25704ee5f0ef599fb51c21f4a4da"
 "autocfg",
 "backtrace",
 "windows-sys",
version = "2.1.0"
checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
 "syn 2.0.27",
version = "0.24.1"
checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081"
 "syn 1.0.92",
 "base64 0.13.0",
[[package]]
name = "unicode-ident"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"

 "syn 1.0.92",
 "syn 1.0.92",
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
 "windows-targets",
]

[[package]]
name = "windows-targets"
version = "0.48.1"
checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f"
 "windows_aarch64_gnullvm",
 "windows_x86_64_gnullvm",
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"

version = "0.48.0"
checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
version = "0.48.0"
checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
version = "0.48.0"
checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"

[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.0"
checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
version = "0.48.0"
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
flexible-hyper-server-tls = "0.1.1"
    let make_svc = make_service_fn(|conn: &flexible_hyper_server_tls::HttpOrHttpsConnection| {
    let acceptor = if CONFIG.tls {
        let tls_acceptor = flexible_hyper_server_tls::tlsconfig::get_tlsacceptor_from_files(
            &CONFIG.cert,
            &CONFIG.key,
            flexible_hyper_server_tls::tlsconfig::HttpProtocol::Http1,
        )
        .context("Couldn't get TLS config")?;

        flexible_hyper_server_tls::HyperHttpOrHttpsAcceptor::new_https(
            tcp,
            tls_acceptor,
            std::time::Duration::from_secs(10),
        )
        flexible_hyper_server_tls::HyperHttpOrHttpsAcceptor::new_http(tcp)
    let mut server = hyper::server::Server::builder(acceptor).serve(make_svc);

        let _res = (&mut server).await.context("Server error").or_else(|e| {
            tracing::warn!("{:?}", e);
            anyhow::Ok(())
        });