Compare commits

..

9 Commits

Author SHA1 Message Date
flamingcow
8bd0813161 Simplify API with recursive mutex 2019-05-09 23:22:45 -07:00
flamingcow
10bdc8e775 Add WriteMany() 2019-05-09 23:04:04 -07:00
flamingcow
ad0281f9d2 Fix afl 2019-05-09 21:48:51 -07:00
flamingcow
1b81228814 Fix asan, add tsan and ubsan 2019-05-09 21:37:20 -07:00
flamingcow
2d29c38914 Shared library, hardening 2019-05-09 21:30:10 -07:00
flamingcow
31dd603873 Lock around writes 2019-05-09 20:24:59 -07:00
flamingcow
19c390aa54 Don't copy params 2019-05-09 19:49:05 -07:00
flamingcow
05382c76a5 Only allow one stdin record, avoid the copy 2019-05-09 19:30:36 -07:00
flamingcow
67f0fc48f9 Only Consume() after a full request is processed 2019-05-09 19:19:26 -07:00
11 changed files with 73 additions and 36 deletions

1
.gitignore vendored
View File

@@ -1,5 +1,6 @@
*.o *.o
*.a *.a
*.so
example_simple example_simple
connection_afl connection_afl
findings findings

View File

@@ -1,38 +1,42 @@
FIRE_CXX ?= clang++ FIRE_CXX ?= clang++
FIRE_CXXFLAGS ?= -O3 -std=gnu++2a -Wall -Werror FIRE_CXXFLAGS ?= -O3 -std=gnu++2a -Wall -Werror -Wextra -fPIE -fPIC -fstack-protector-strong -fsanitize=safe-stack -fsanitize=safe-stack
FIRE_LDFLAGS ?= -fuse-ld=gold -flto -Wl,-z,relro -Wl,-z,now
FIRE_LDLIBS ?= -lgflags -lglog -lpthread FIRE_LDLIBS ?= -lgflags -lglog -lpthread
all: firecgi.a firecgi.o example_simple all: firecgi.a firecgi.o firecgi.so example_simple
objects = server.o connection.o request.o parse.o objects = server.o connection.o request.o parse.o
firebuf/firebuf.o: firebuf/firebuf.o:
$(MAKE) --directory=firebuf $(MAKE) --directory=firebuf firebuf.o
firecgi.a: $(objects) firecgi.a: $(objects)
ar rcs $@ $^ ar rcs $@ $^
firecgi.o: $(objects) firebuf/firebuf.o firecgi.o: $(objects) firebuf/firebuf.o
ld --relocatable --output=$@ $+ gold -z relro -z now -r --output=$@ $+
firecgi.so: $(objects) firebuf/firebuf.o
$(FIRE_CXX) $(FIRE_CXXFLAGS) $(FIRE_LDFLAGS) -shared -o $@ $+ $(FIRE_LDFLIBS)
example_simple: example_simple.o firecgi.o example_simple: example_simple.o firecgi.o
$(FIRE_CXX) $(FIRE_CXXFLAGS) -o $@ $+ $(FIRE_LDLIBS) $(FIRE_CXX) $(FIRE_CXXFLAGS) $(FIRE_LDFLAGS) -pie -o $@ $+ $(FIRE_LDLIBS)
%.o: %.cc *.h Makefile %.o: %.cc *.h Makefile
$(FIRE_CXX) $(FIRE_CXXFLAGS) -c -o $@ $< $(FIRE_CXX) $(FIRE_CXXFLAGS) -c -o $@ $<
clean: clean:
$(MAKE) --directory=firebuf clean $(MAKE) --directory=firebuf clean
rm --force example_simple connection_afl *.o *.a rm --force example_simple connection_afl *.so *.o *.a
afl: afl:
$(MAKE) clean $(MAKE) clean
FIRE_CXX=afl-g++ $(MAKE) afl_int FIRE_CXX=afl-g++ FIRE_CXXFLAGS="-O3 -std=gnu++2a -fPIC -fPIE" $(MAKE) afl_int
afl_int: connection_afl afl_int: connection_afl
connection_afl: connection_afl.o firecgi.o connection_afl: connection_afl.o firecgi.o
$(FIRE_CXX) $(FIRE_CXXFLAGS) -o $@ $+ $(FIRE_LDLIBS) $(FIRE_CXX) $(FIRE_CXXFLAGS) $(FIRE_LDFLAGS) -pie -o $@ $+ $(FIRE_LDLIBS)
test: test_connection test: test_connection
@@ -43,4 +47,12 @@ test_connection: connection_afl
asan: asan:
$(MAKE) clean $(MAKE) clean
FIRE_CXXFLAGS="-O1 -g -fsanitize=address -fno-omit-frame-pointer -std=gnu++2a -Wall -Werror" $(MAKE) all FIRE_CXXFLAGS="-O1 -g -fsanitize=address -fno-omit-frame-pointer -std=gnu++2a -fPIE -fPIC" $(MAKE) all
tsan:
$(MAKE) clean
FIRE_CXXFLAGS="-O1 -g -fsanitize=thread -std=gnu++2a -fPIE -fPIC" $(MAKE) all
ubsan:
$(MAKE) clean
FIRE_CXXFLAGS="-O1 -g -fsanitize=undefined -std=gnu++2a -fPIE -fPIC" $(MAKE) all

View File

@@ -9,11 +9,11 @@
namespace firecgi { namespace firecgi {
Connection::Connection(int sock, const sockaddr_in6& client_addr, const std::function<void(Request*)>& callback, const std::unordered_set<std::string_view>& headers) Connection::Connection(int sock, const sockaddr_in6& client_addr, const std::function<void(Request*)>& callback, const std::unordered_set<std::string_view>& headers, int max_request_len)
: sock_(sock), : sock_(sock),
callback_(callback), callback_(callback),
headers_(headers), headers_(headers),
buf_(sock, max_record_len), buf_(sock, max_request_len),
request_(this) { request_(this) {
char client_addr_str[INET6_ADDRSTRLEN]; char client_addr_str[INET6_ADDRSTRLEN];
PCHECK(inet_ntop(AF_INET6, &client_addr.sin6_addr, client_addr_str, sizeof(client_addr_str))); PCHECK(inet_ntop(AF_INET6, &client_addr.sin6_addr, client_addr_str, sizeof(client_addr_str)));
@@ -122,9 +122,12 @@ int Connection::Read() {
// Magic signal for completed request (mirrors the HTTP/1.1 protocol) // Magic signal for completed request (mirrors the HTTP/1.1 protocol)
requests_++; requests_++;
callback_(&request_); callback_(&request_);
buf_.Consume(); // discard data and invalidate pointers
} else { } else {
std::string_view in(buf_.Read(header->ContentLength()), header->ContentLength()); if (!request_.GetBody().empty()) {
request_.AddIn(in); LOG(ERROR) << "received multiple stdin records. have you set \"fastcgi_request_buffering on\"?";
}
request_.SetBody({buf_.Read(header->ContentLength()), header->ContentLength()});
} }
} }
break; break;
@@ -141,7 +144,6 @@ int Connection::Read() {
buf_.Commit(); // we've acted on the bytes read so far buf_.Commit(); // we've acted on the bytes read so far
} }
buf_.Consume();
return -1; return -1;
} }

View File

@@ -13,7 +13,7 @@ namespace firecgi {
class Connection { class Connection {
public: public:
Connection(int sock, const sockaddr_in6& client_addr, const std::function<void(Request*)>& callback, const std::unordered_set<std::string_view>& headers); Connection(int sock, const sockaddr_in6& client_addr, const std::function<void(Request*)>& callback, const std::unordered_set<std::string_view>& headers, int max_request_len);
~Connection(); ~Connection();
[[nodiscard]] int Read(); [[nodiscard]] int Read();

View File

@@ -7,7 +7,7 @@ int main(int argc, char* argv[]) {
gflags::ParseCommandLineFlags(&argc, &argv, true); gflags::ParseCommandLineFlags(&argc, &argv, true);
{ {
firecgi::Connection conn(STDIN_FILENO, {}, [](std::unique_ptr<firecgi::Request> req) { req->End(); }, {}); firecgi::Connection conn(STDIN_FILENO, {}, [](firecgi::Request* req) { req->End(); }, {}, 16*1024);
static_cast<void>(conn.Read()); static_cast<void>(conn.Read());
} }

Submodule firebuf updated: 49766e29f4...2102147a2f

View File

@@ -44,8 +44,4 @@ struct ParamHeader {
uint8_t value_length; uint8_t value_length;
}; };
constexpr auto max_content_len = 65535;
constexpr auto max_padding_len = 255;
constexpr auto max_record_len = sizeof(Header) + max_content_len + max_padding_len;
} // namespace firecgi } // namespace firecgi

View File

@@ -18,12 +18,12 @@ template<class T> void AppendVec(const T& obj, std::vector<iovec>* vec) {
Request::Request(Connection* conn) Request::Request(Connection* conn)
: conn_(conn), : conn_(conn),
out_buf_(max_record_len) {} out_buf_(64*1024) {}
void Request::NewRequest(uint16_t request_id) { void Request::NewRequest(uint16_t request_id) {
request_id_ = request_id; request_id_ = request_id;
params_.clear(); params_.clear();
in_.clear(); body_ = {};
out_buf_.Reset(); out_buf_.Reset();
body_written_ = false; body_written_ = false;
} }
@@ -36,20 +36,26 @@ void Request::AddParam(const std::string_view& key, const std::string_view& valu
params_.try_emplace(std::string(key), std::string(value)); params_.try_emplace(std::string(key), std::string(value));
} }
void Request::AddIn(const std::string_view& in) { void Request::SetBody(const std::string_view& body) {
in_.append(in); body_ = body;
} }
const std::string& Request::GetParam(const std::string& key) { const std::string_view& Request::GetParam(const std::string& key) {
auto iter = params_.find(key); auto iter = params_.find(key);
if (iter == params_.end()) { if (iter == params_.end()) {
static const std::string none; static const std::string_view none;
return none; return none;
} }
return iter->second; return iter->second;
} }
const std::string_view& Request::GetBody() {
return body_;
}
void Request::WriteHeader(const std::string_view& name, const std::string_view& value) { void Request::WriteHeader(const std::string_view& name, const std::string_view& value) {
std::lock_guard<std::recursive_mutex> l(output_mu_);
CHECK(!body_written_); CHECK(!body_written_);
CHECK(out_buf_.Write(name)); CHECK(out_buf_.Write(name));
CHECK(out_buf_.Write(": ")); CHECK(out_buf_.Write(": "));
@@ -58,6 +64,7 @@ void Request::WriteHeader(const std::string_view& name, const std::string_view&
} }
void Request::WriteBody(const std::string_view& body) { void Request::WriteBody(const std::string_view& body) {
std::lock_guard<std::recursive_mutex> l(output_mu_);
if (!body_written_) { if (!body_written_) {
CHECK(out_buf_.Write("\n")); CHECK(out_buf_.Write("\n"));
body_written_ = true; body_written_ = true;
@@ -67,6 +74,8 @@ void Request::WriteBody(const std::string_view& body) {
} }
bool Request::Flush() { bool Request::Flush() {
std::lock_guard<std::recursive_mutex> l(output_mu_);
std::vector<iovec> vecs; std::vector<iovec> vecs;
auto header = OutputHeader(); auto header = OutputHeader();
@@ -78,11 +87,13 @@ bool Request::Flush() {
return false; return false;
} }
out_buf_.Commit(); out_buf_.Commit();
out_buf_.Consume();
return true; return true;
} }
bool Request::End() { bool Request::End() {
// Fully empty response not allowed std::lock_guard<std::recursive_mutex> l(output_mu_);
WriteBody(""); WriteBody("");
std::vector<iovec> vecs; std::vector<iovec> vecs;

View File

@@ -1,5 +1,6 @@
#pragma once #pragma once
#include <mutex>
#include <unordered_map> #include <unordered_map>
#include "firebuf/buffer.h" #include "firebuf/buffer.h"
@@ -19,15 +20,19 @@ class Request {
uint16_t RequestId(); uint16_t RequestId();
void AddParam(const std::string_view& key, const std::string_view& value); void AddParam(const std::string_view& key, const std::string_view& value);
void AddIn(const std::string_view& in); void SetBody(const std::string_view& in);
const std::string& GetParam(const std::string& key); const std::string_view& GetParam(const std::string& key);
const std::string_view& GetBody();
void WriteHeader(const std::string_view& name, const std::string_view& value); void WriteHeader(const std::string_view& name, const std::string_view& value);
void WriteBody(const std::string_view& body); void WriteBody(const std::string_view& body);
[[nodiscard]] bool Flush(); [[nodiscard]] bool Flush();
bool End(); bool End();
template<typename...Args>
void WriteBody(const std::string_view& first, Args... more);
private: private:
Header OutputHeader(); Header OutputHeader();
iovec OutputVec(); iovec OutputVec();
@@ -35,11 +40,19 @@ class Request {
Connection *conn_; Connection *conn_;
uint16_t request_id_ = 0; uint16_t request_id_ = 0;
std::unordered_map<std::string, std::string> params_; std::unordered_map<std::string_view, std::string_view> params_;
std::string in_; std::string_view body_;
firebuf::Buffer out_buf_; firebuf::Buffer out_buf_;
bool body_written_; bool body_written_;
std::recursive_mutex output_mu_;
}; };
template<typename...Args>
void Request::WriteBody(const std::string_view& first, Args... more) {
std::lock_guard<std::recursive_mutex> l(output_mu_);
WriteBody(first);
WriteBody(more...);
}
} // namespace firecgi } // namespace firecgi

View File

@@ -11,11 +11,12 @@
namespace firecgi { namespace firecgi {
Server::Server(int port, const std::function<void(Request*)>& callback, int threads, const std::unordered_set<std::string_view>& headers) Server::Server(int port, const std::function<void(Request*)>& callback, int threads, const std::unordered_set<std::string_view>& headers, int max_request_len)
: port_(port), : port_(port),
callback_(callback), callback_(callback),
threads_(threads), threads_(threads),
headers_(headers) { headers_(headers),
max_request_len_(max_request_len) {
LOG(INFO) << "listening on [::1]:" << port_; LOG(INFO) << "listening on [::1]:" << port_;
signal(SIGPIPE, SIG_IGN); signal(SIGPIPE, SIG_IGN);
@@ -81,7 +82,7 @@ void Server::NewConn(int listen_sock, int epoll_fd) {
PCHECK(setsockopt(client_sock, SOL_TCP, TCP_NODELAY, &flags, sizeof(flags)) == 0); PCHECK(setsockopt(client_sock, SOL_TCP, TCP_NODELAY, &flags, sizeof(flags)) == 0);
{ {
auto *conn = new Connection(client_sock, client_addr, callback_, headers_); auto *conn = new Connection(client_sock, client_addr, callback_, headers_, max_request_len_);
struct epoll_event ev{ struct epoll_event ev{
.events = EPOLLIN, .events = EPOLLIN,
.data = { .data = {

View File

@@ -10,7 +10,7 @@ namespace firecgi {
class Server { class Server {
public: public:
Server(int port, const std::function<void(Request*)>& callback, int threads=1, const std::unordered_set<std::string_view>& headers={}); Server(int port, const std::function<void(Request*)>& callback, int threads=1, const std::unordered_set<std::string_view>& headers={}, int max_request_len=(16*1024));
void Serve(); void Serve();
private: private:
@@ -22,6 +22,7 @@ class Server {
const std::function<void(Request*)> callback_; const std::function<void(Request*)> callback_;
const int threads_; const int threads_;
const std::unordered_set<std::string_view> headers_; const std::unordered_set<std::string_view> headers_;
const int max_request_len_;
}; };
} // firecgi } // firecgi