Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions docs/01_nodeos/03_plugins/http_plugin/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,18 @@ Config Options for eosio::http_plugin:
--http-keep-alive arg (=1) If set to false, do not keep HTTP
connections alive, even if client
requests.
--http-allow-control-plane-cors Allow Access-Control-Allow-Origin to be
configured while an unauthenticated
control-plane API (producer_rw,
snapshot) is bound to a non-loopback
address. Without this flag that
combination is refused.
```

`access-control-allow-origin=*` cannot be combined with
`access-control-allow-credentials=true`; `http_plugin` refuses that pair at
startup.

## Dependencies

None
44 changes: 43 additions & 1 deletion docs/01_nodeos/03_plugins/producer_api_plugin/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,49 @@ nodeos ... --plugin eosio::producer_api_plugin

## Options

None
These can be specified from both the command-line or the `config.ini` file:

```console
Config Options for eosio::producer_api_plugin:
--http-expose-nonloopback-producer-api
Allow producer_rw and snapshot HTTP
APIs to bind to non-loopback addresses.
These endpoints have no authentication
and can pause/resume production, change
runtime options, manage snapshots, and
schedule protocol features. Default is
false: non-loopback exposure is refused
at startup. Loopback and UNIX socket
bindings do not require this option.
```

Related `http_plugin` option:

```console
--http-allow-control-plane-cors
Allow Access-Control-Allow-Origin while
producer_rw/snapshot are bound to a
non-loopback address. Without this flag
that combination is refused.
```

## Security

`producer_api_plugin` RPCs are **unauthenticated**. Destructive calls (`pause`,
`resume`, `update_runtime_options`, snapshot schedule, whitelist/greylist,
protocol feature schedule) share the `http_plugin` listener.

Safe defaults:

* Bind HTTP to `127.0.0.1` or a UNIX socket (the `http-server-address` default
is loopback). Local `cleos` / operator tooling keeps working with no extra flags.
* Prefer `--http-category-address` so `producer_rw` and `snapshot` listen on
loopback or a UNIX socket while public chain APIs use a different address.
* Non-loopback exposure requires `--http-expose-nonloopback-producer-api`.
* Combining a configured `access-control-allow-origin` with non-loopback
producer/snapshot APIs requires `--http-allow-control-plane-cors`.
* `access-control-allow-origin=*` cannot be combined with
`access-control-allow-credentials=true` (refused at startup).

## Dependencies

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ This procedure creates a database containing the chain state, with full history
1. Enable the `producer_api_plugin` on a node with full state-history.

[[caution | Caution when using `producer_api_plugin`]]
| Either use a firewall to block access to `http-server-address`, or change it to `localhost:8888` to disable remote access.
| Producer/snapshot RPCs are unauthenticated. Keep `http-server-address` on loopback (`localhost:8888`, the default) or a UNIX socket. Non-loopback exposure is refused unless you pass `--http-expose-nonloopback-producer-api`. Do not combine a public CORS origin with those APIs unless you also pass `--http-allow-control-plane-cors`.

2. Create a portable snapshot:
```sh
Expand Down
3 changes: 3 additions & 0 deletions libraries/libfc/include/fc/container/container_detail.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include <fc/variant.hpp>
#include <fc/io/raw_fwd.hpp>
#include <fc/io/raw_unpack_bounds.hpp>

namespace fc {

Expand Down Expand Up @@ -34,6 +35,7 @@ namespace fc {
inline void unpack_flat_set( Stream& s, Set<T, U...>& value ) {
unsigned_int size; unpack( s, size );
FC_ASSERT( size.value <= MAX_NUM_ARRAY_ELEMENTS );
assert_claimed_container_fits<Stream, T>( s, size.value );
value.clear();
value.reserve( size.value );
for( uint32_t i = 0; i < size.value; ++i ) {
Expand Down Expand Up @@ -68,6 +70,7 @@ namespace fc {
inline void unpack_flat_map( Stream& s, Map<K, V, U...>& value ) {
unsigned_int size; unpack( s, size );
FC_ASSERT( size.value <= MAX_NUM_ARRAY_ELEMENTS );
assert_claimed_container_fits<Stream, std::pair<K, V>>( s, size.value );
value.clear();
value.reserve( size.value );
for( uint32_t i = 0; i < size.value; ++i ) {
Expand Down
3 changes: 3 additions & 0 deletions libraries/libfc/include/fc/container/flat.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include <fc/container/flat_fwd.hpp>
#include <fc/container/container_detail.hpp>
#include <fc/io/raw_unpack_bounds.hpp>
#include <boost/container/flat_map.hpp>
#include <boost/container/flat_set.hpp>
#include <fc/crypto/hex.hpp>
Expand All @@ -28,6 +29,7 @@ namespace fc {
unsigned_int size;
unpack( s, size );
FC_ASSERT( size.value <= MAX_NUM_ARRAY_ELEMENTS );
detail::assert_claimed_container_fits<Stream, T>( s, size.value );
value.clear();
value.resize( size.value );
if( !std::is_fundamental<T>::value ) {
Expand All @@ -52,6 +54,7 @@ namespace fc {
unsigned_int size;
unpack( s, size );
FC_ASSERT( size.value <= MAX_SIZE_OF_BYTE_ARRAYS );
detail::assert_claimed_container_fits<Stream, char>( s, size.value );
value.clear();
value.resize( size.value );
if( value.size() )
Expand Down
8 changes: 8 additions & 0 deletions libraries/libfc/include/fc/io/raw.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include <fc/safe.hpp>
#include <fc/static_variant.hpp>
#include <fc/io/raw_fwd.hpp>
#include <fc/io/raw_unpack_bounds.hpp>
#include <fc/crypto/hex.hpp>
#include <fc/bitutil.hpp>

Expand Down Expand Up @@ -298,6 +299,7 @@ namespace fc {
template<typename Stream> inline void unpack( Stream& s, std::vector<char>& value ) {
unsigned_int size; fc::raw::unpack( s, size );
FC_ASSERT( size.value <= MAX_SIZE_OF_BYTE_ARRAYS );
detail::assert_claimed_container_fits<Stream, char>( s, size.value );
value.resize(size.value);
if( value.size() )
s.read( value.data(), value.size() );
Expand Down Expand Up @@ -449,6 +451,7 @@ namespace fc {
inline void unpack( Stream& s, std::unordered_set<T>& value ) {
unsigned_int size; fc::raw::unpack( s, size );
FC_ASSERT( size.value <= MAX_NUM_ARRAY_ELEMENTS );
detail::assert_claimed_container_fits<Stream, T>( s, size.value );
value.clear();
value.reserve(size.value);
for( uint32_t i = 0; i < size.value; ++i )
Expand Down Expand Up @@ -498,6 +501,7 @@ namespace fc {
{
unsigned_int size; fc::raw::unpack( s, size );
FC_ASSERT( size.value <= MAX_NUM_ARRAY_ELEMENTS );
detail::assert_claimed_container_fits<Stream, std::pair<K,V>>( s, size.value );
value.clear();
value.reserve(size.value);
for( uint32_t i = 0; i < size.value; ++i )
Expand Down Expand Up @@ -545,6 +549,7 @@ namespace fc {
inline void unpack( Stream& s, std::deque<T>& value ) {
unsigned_int size; fc::raw::unpack( s, size );
FC_ASSERT( size.value <= MAX_NUM_ARRAY_ELEMENTS );
detail::assert_claimed_container_fits<Stream, T>( s, size.value );
value.resize(size.value);
for( auto& i : value ) {
fc::raw::unpack( s, i );
Expand All @@ -565,6 +570,7 @@ namespace fc {
unsigned_int size;
fc::raw::unpack( s, size );
FC_ASSERT( size.value <= MAX_NUM_ARRAY_ELEMENTS );
detail::assert_claimed_container_fits<Stream, T>( s, size.value );
value.resize( size.value );
for( auto& i : value ) {
fc::raw::unpack( s, i );
Expand Down Expand Up @@ -596,6 +602,7 @@ namespace fc {
constexpr size_t word_size = sizeof(fc::dynamic_bitset::block_type) * CHAR_BIT;
size_t num_blocks = (size + word_size - 1) / word_size;
FC_ASSERT( num_blocks <= MAX_NUM_ARRAY_ELEMENTS );
detail::assert_claimed_container_fits<Stream, fc::dynamic_bitset::block_type>( s, num_blocks );
std::vector<fc::dynamic_bitset::block_type> blocks(num_blocks);
for( size_t i = 0; i < num_blocks; ++i ) {
fc::raw::unpack( s, blocks[i] );
Expand All @@ -617,6 +624,7 @@ namespace fc {
inline void unpack( Stream& s, std::vector<T>& value ) {
unsigned_int size; fc::raw::unpack( s, size );
FC_ASSERT( size.value <= MAX_NUM_ARRAY_ELEMENTS );
detail::assert_claimed_container_fits<Stream, T>( s, size.value );
value.resize(size.value);
for( auto& i : value ) {
fc::raw::unpack( s, i );
Expand Down
78 changes: 78 additions & 0 deletions libraries/libfc/include/fc/io/raw_unpack_bounds.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#pragma once

#include <fc/io/datastream.hpp>
#include <fc/exception/exception.hpp>
#include <fc/io/raw_fwd.hpp>

#include <cstdint>
#include <type_traits>
#include <utility>

namespace fc { namespace raw { namespace detail {

/**
* True when Stream::remaining() reports a trustworthy byte count of unread
* payload (in-memory datastreams and bounded_datastream). Skips:
* - datastream<size_t> (size-calculation stream; remaining() is always 0)
* - streams whose remaining() is bool (streambuf in_avail() wrapper)
* - streams with no remaining() (cfile, etc.)
*
* This is a defensive bound only: valid payloads still unpack unchanged.
*/
template<typename Stream>
inline constexpr bool stream_has_trusted_remaining() {
using S = std::remove_cvref_t<Stream>;
if constexpr (std::is_same_v<S, datastream<size_t>>) {
return false;
} else if constexpr (requires(const S& s) { s.remaining(); }) {
using rem_t = std::remove_cvref_t<decltype(std::declval<const S&>().remaining())>;
return std::is_integral_v<rem_t> && !std::is_same_v<rem_t, bool>;
} else {
return false;
}
}

template<typename T>
inline uint64_t default_instance_packed_size() {
if constexpr (!std::is_default_constructible_v<T>) {
return 0;
} else {
datastream<size_t> ps;
// Default-initialize (T dummy;), not T dummy{}. Copy-list-initialization
// cannot invoke explicit default constructors such as
// chainbase::shared_cow_vector().
T dummy;
fc::raw::pack(ps, dummy);
return static_cast<uint64_t>(ps.tellp());
}
}

/**
* Fail before resize/reserve when a claimed element count cannot fit in the
* remaining stream even if every element serializes at its minimum size
* (default-constructed T). Prevents allocation amplification from a short
* frame that advertises MAX_NUM_ARRAY_ELEMENTS.
*
* Types whose default instance packs to 0 bytes (empty structs) skip the
* check so valid zero-payload vectors are not rejected.
*/
template<typename Stream, typename T>
inline void assert_claimed_container_fits(Stream& s, uint64_t count) {
if (count == 0)
return;
if constexpr (stream_has_trusted_remaining<Stream>()) {
const uint64_t rem = static_cast<uint64_t>(s.remaining());
const uint64_t min_elem = default_instance_packed_size<T>();
if (min_elem == 0)
return;
if (count > rem / min_elem) {
// Same exception as a short-read so existing unpack tests and
// call sites that catch out_of_range_exception keep working.
FC_THROW_EXCEPTION(out_of_range_exception,
"claimed container size ${c} exceeds remaining stream (${r} bytes, min ${m} per element)",
("c", count)("r", rem)("m", min_elem));
}
}
}

}}} // namespace fc::raw::detail
4 changes: 4 additions & 0 deletions libraries/libfc/include/fc/network/message_buffer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,8 @@ namespace fc {
inline bool get( unsigned char& c ) { return mb.read(&c, 1); }
inline bool get( char& c ) { return mb.read(&c, 1); }

inline size_t remaining() const { return mb.bytes_to_read(); }

private:
message_buffer<buffer_len>& mb;
};
Expand Down Expand Up @@ -344,6 +346,8 @@ namespace fc {
inline bool get( unsigned char& c ) { return mb.peek( &c, 1, index ); }
inline bool get( char& c ) { return mb.peek( &c, 1, index ); }

inline size_t remaining() const { return mb.bytes_to_read_from_index(index); }

private:
const message_buffer<buffer_len>& mb;
typename message_buffer<buffer_len>::index_t index{0,0};
Expand Down
1 change: 1 addition & 0 deletions libraries/libfc/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ add_executable( test_fc
io/test_json.cpp
io/test_random_access_file.cpp
io/test_raw.cpp
io/test_unpack_bounds.cpp
io/test_tracked_storage.cpp
io/test_bounded_datastream.cpp
network/test_message_buffer.cpp
Expand Down
Loading
Loading