mirror of
https://github.com/genodelabs/genode.git
synced 2025-04-19 00:27:21 +00:00
vfs/xoroshiro: add PRNG plugin using Xoroshiro128+
This plugin provides a PRNG based on the Xoroshiro128+ algorithm that reseeds itself after a specific amount of state was consumed. For more detailed information please take a look at its README in 'repos/os/src/lib/vfs/xoroshiro/README'. Fixes #5407
This commit is contained in:
parent
c053c46fae
commit
8b5b5cda76
repos/os
lib/mk
recipes/src/vfs_xoroshiro
run
src/lib/vfs/xoroshiro
12
repos/os/lib/mk/vfs_xoroshiro.mk
Normal file
12
repos/os/lib/mk/vfs_xoroshiro.mk
Normal file
@ -0,0 +1,12 @@
|
||||
VFS_DIR := $(REP_DIR)/src/lib/vfs/xoroshiro
|
||||
|
||||
SRC_CC := vfs.cc
|
||||
|
||||
# for base/internal/xoroshiro.h
|
||||
REP_INC_DIR += src/include/base/internal
|
||||
|
||||
LD_OPT += --version-script=$(VFS_DIR)/symbol.map
|
||||
|
||||
SHARED_LIB := yes
|
||||
|
||||
vpath %.cc $(VFS_DIR)
|
17
repos/os/recipes/src/vfs_xoroshiro/content.mk
Normal file
17
repos/os/recipes/src/vfs_xoroshiro/content.mk
Normal file
@ -0,0 +1,17 @@
|
||||
MIRROR_FROM_REP_DIR := src/lib/vfs/xoroshiro lib/mk/vfs_xoroshiro.mk
|
||||
|
||||
content: $(MIRROR_FROM_REP_DIR) LICENSE
|
||||
|
||||
$(MIRROR_FROM_REP_DIR):
|
||||
$(mirror_from_rep_dir)
|
||||
|
||||
MIRROR_FROM_BASE_DIR := src/include/base/internal/xoroshiro.h
|
||||
|
||||
content: $(MIRROR_FROM_BASE_DIR)
|
||||
|
||||
$(MIRROR_FROM_BASE_DIR):
|
||||
mkdir -p $(dir $@)
|
||||
cp -r $(addprefix $(GENODE_DIR)/repos/base/,$@) $(dir $@)
|
||||
|
||||
LICENSE:
|
||||
cp $(GENODE_DIR)/LICENSE $@
|
1
repos/os/recipes/src/vfs_xoroshiro/hash
Normal file
1
repos/os/recipes/src/vfs_xoroshiro/hash
Normal file
@ -0,0 +1 @@
|
||||
2024-12-16 af40f4293fa0bc3a44ff27b6a174d37a0ecce3eb
|
3
repos/os/recipes/src/vfs_xoroshiro/used_apis
Normal file
3
repos/os/recipes/src/vfs_xoroshiro/used_apis
Normal file
@ -0,0 +1,3 @@
|
||||
base
|
||||
os
|
||||
vfs
|
90
repos/os/run/vfs_xoroshiro.run
Normal file
90
repos/os/run/vfs_xoroshiro.run
Normal file
@ -0,0 +1,90 @@
|
||||
build { lib/vfs_xoroshiro lib/vfs_jitterentropy }
|
||||
|
||||
create_boot_directory
|
||||
|
||||
import_from_depot \
|
||||
[depot_user]/src/[base_src] \
|
||||
[depot_user]/src/coreutils \
|
||||
[depot_user]/src/init \
|
||||
[depot_user]/src/fs_rom \
|
||||
[depot_user]/src/libc \
|
||||
[depot_user]/src/vfs \
|
||||
[depot_user]/src/posix
|
||||
|
||||
install_config {
|
||||
<config verbose="yes">
|
||||
<parent-provides>
|
||||
<service name="ROM"/>
|
||||
<service name="LOG"/>
|
||||
<service name="RM"/>
|
||||
<service name="CPU"/>
|
||||
<service name="PD"/>
|
||||
<service name="IRQ"/>
|
||||
<service name="IO_MEM"/>
|
||||
<service name="IO_PORT"/>
|
||||
</parent-provides>
|
||||
|
||||
<default-route>
|
||||
<any-service> <parent/> </any-service>
|
||||
</default-route>
|
||||
<default caps="100" ram="1M"/>
|
||||
|
||||
<start name="timer">
|
||||
<provides><service name="Timer"/></provides>
|
||||
</start>
|
||||
|
||||
<start name="vfs" ram="4M">
|
||||
<provides><service name="File_system"/></provides>
|
||||
<config>
|
||||
<vfs>
|
||||
<ram/>
|
||||
<tar name="coreutils.tar" />
|
||||
</vfs>
|
||||
<default-policy root="/" writeable="yes"/>
|
||||
</config>
|
||||
</start>
|
||||
|
||||
<start name="vfs_rom" ram="10M">
|
||||
<binary name="fs_rom"/>
|
||||
<provides> <service name="ROM"/> </provides>
|
||||
<config/>
|
||||
<route>
|
||||
<service name="File_system"> <child name="vfs"/> </service>
|
||||
<any-service> <parent/> </any-service>
|
||||
</route>
|
||||
</start>
|
||||
|
||||
<start name="/bin/dd" caps="500" ram="16M">
|
||||
<config>
|
||||
<libc stdin="/dev/null" stdout="/dev/log" stderr="/dev/log"
|
||||
rtc="/dev/null"/>
|
||||
<vfs>
|
||||
<dir name="dev">
|
||||
<log/>
|
||||
<null/>
|
||||
<jitterentropy name="entropy"/>
|
||||
<xoroshiro name="random" seed_path="/dev/entropy"/>
|
||||
</dir>
|
||||
</vfs>
|
||||
<arg value="dd"/>
|
||||
<arg value="if=/dev/random"/>
|
||||
<arg value="of=/dev/null"/>
|
||||
<arg value="bs=1M"/>
|
||||
<arg value="count=128"/>
|
||||
</config>
|
||||
<route>
|
||||
<service name="File_system"> <child name="vfs"/> </service>
|
||||
<service name="ROM" label_suffix=".lib.so"> <parent/> </service>
|
||||
<service name="ROM" label_last="/bin/dd"> <child name="vfs_rom"/> </service>
|
||||
<any-service> <parent/> <any-child/> </any-service>
|
||||
</route>
|
||||
</start>
|
||||
|
||||
</config>
|
||||
}
|
||||
|
||||
build_boot_image [build_artifacts]
|
||||
|
||||
append qemu_args " -nographic "
|
||||
|
||||
run_genode_until "child .* exited with exit value 0.*\n" 20
|
20
repos/os/src/lib/vfs/xoroshiro/README
Normal file
20
repos/os/src/lib/vfs/xoroshiro/README
Normal file
@ -0,0 +1,20 @@
|
||||
The VFS xoroshiro plugin provides a PRNG based on the Xoroshiro12+
|
||||
algorithm. It is seeded initially and reseeded regularly after a fixed
|
||||
amount of bytes, around 1 MiB, were produced by reading a seed file.
|
||||
|
||||
The plugin supports the following configuration options:
|
||||
|
||||
* :name: sets the file-name under which the plugins exposes itself
|
||||
to the VFS, the default is 'xoroshiro'.
|
||||
|
||||
* :seed_path: specifies the file in the VFS that is read to reseed
|
||||
the PRNG.
|
||||
|
||||
The following examplary config snippets illustrates its usage:
|
||||
|
||||
! <vfs>
|
||||
! <dir name="dev">
|
||||
! <jitterentropy name="entropy"/>
|
||||
! <xoroshiro name="random" seed_path="/dev/entropy"/>
|
||||
! </dir>
|
||||
! </vfs>
|
9
repos/os/src/lib/vfs/xoroshiro/symbol.map
Normal file
9
repos/os/src/lib/vfs/xoroshiro/symbol.map
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
global:
|
||||
|
||||
vfs_file_system_factory;
|
||||
|
||||
local:
|
||||
|
||||
*;
|
||||
};
|
274
repos/os/src/lib/vfs/xoroshiro/vfs.cc
Normal file
274
repos/os/src/lib/vfs/xoroshiro/vfs.cc
Normal file
@ -0,0 +1,274 @@
|
||||
/*
|
||||
* \brief PRNG VFS plugin employing Xoroshiro128+
|
||||
* \author Josef Soentgen
|
||||
* \author Martin Stein
|
||||
* \date 2024-12-09
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2024 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU Affero General Public License version 3.
|
||||
*/
|
||||
|
||||
/* Genode includes */
|
||||
#include <os/vfs.h>
|
||||
#include <vfs/single_file_system.h>
|
||||
#include <xoroshiro.h> /* base/internal/xoroshiro.h */
|
||||
|
||||
using namespace Genode;
|
||||
|
||||
|
||||
struct Entropy_source : Interface
|
||||
{
|
||||
struct Collect_ok { };
|
||||
struct Collect_error { };
|
||||
using Collect_result = Attempt<Collect_ok, Collect_error>;
|
||||
|
||||
virtual Collect_result collect(Byte_range_ptr const &dst) = 0;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* A wrapper for the Xoroshiro128+ PRNG that reseeds the PRNG around every
|
||||
* 1024 * 1024 + random(0..4095) bytes of generated output.
|
||||
*/
|
||||
struct Xoroshiro_128_plus_reseeding
|
||||
{
|
||||
public:
|
||||
|
||||
enum class Query_error : uint32_t { RESEED_FAILED };
|
||||
struct Query_ok { size_t produced_bytes; };
|
||||
|
||||
using Query_result = Attempt<Query_ok, Query_error>;
|
||||
|
||||
private:
|
||||
|
||||
static constexpr unsigned CONSUME_THRESHOLD = 1024u * 1024u;
|
||||
|
||||
struct Xoroshiro_bytewise
|
||||
{
|
||||
Xoroshiro_128_plus _xoroshiro;
|
||||
|
||||
union {
|
||||
uint64_t value;
|
||||
char bytes[sizeof(value)];
|
||||
};
|
||||
|
||||
uint8_t _index = 0;
|
||||
|
||||
Xoroshiro_bytewise(uint64_t seed) : _xoroshiro { seed } { }
|
||||
|
||||
void produce(Byte_range_ptr const &range)
|
||||
{
|
||||
auto get_byte = [&] {
|
||||
if (!_index)
|
||||
value = _xoroshiro.value();
|
||||
|
||||
return bytes[_index++ % sizeof(value)];
|
||||
};
|
||||
|
||||
char *dst = range.start;
|
||||
for (uint64_t i = 0; i < range.num_bytes; i++) {
|
||||
*dst = get_byte();
|
||||
dst++;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Entropy_source &_entropy_src;
|
||||
uint64_t _seed { 0 };
|
||||
size_t _consumed { 0 };
|
||||
size_t _consumed_limit { CONSUME_THRESHOLD };
|
||||
Constructible<Xoroshiro_bytewise> _xoroshiro { };
|
||||
|
||||
bool _reseed()
|
||||
{
|
||||
return _entropy_src.collect(Byte_range_ptr { (char*)&_seed,
|
||||
sizeof(_seed) }).convert<bool>(
|
||||
[&] (Entropy_source::Collect_ok) {
|
||||
_consumed_limit = CONSUME_THRESHOLD + (_seed & 0xfffu);
|
||||
_xoroshiro.construct(_seed);
|
||||
return true;
|
||||
},
|
||||
[&] (Entropy_source::Collect_error) {
|
||||
_xoroshiro.destruct();
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
Xoroshiro_128_plus_reseeding(Entropy_source &entropy_src)
|
||||
: _entropy_src { entropy_src } { }
|
||||
|
||||
Query_result query(Byte_range_ptr const &range)
|
||||
{
|
||||
/*
|
||||
* Cap consumed bytes to trigger reseeding at around
|
||||
* twice the limit.
|
||||
*/
|
||||
Byte_range_ptr const buffer(range.start, min(range.num_bytes,
|
||||
_consumed_limit));
|
||||
|
||||
/*
|
||||
* Reseed initially and the next time around when the limit
|
||||
* was hit.
|
||||
*/
|
||||
if (_consumed == 0)
|
||||
if (!_reseed())
|
||||
return Query_error::RESEED_FAILED;
|
||||
|
||||
_consumed += buffer.num_bytes;
|
||||
if (_consumed >= _consumed_limit)
|
||||
_consumed = 0;
|
||||
|
||||
_xoroshiro->produce(buffer);
|
||||
|
||||
return Query_ok { .produced_bytes = buffer.num_bytes };
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
namespace Vfs {
|
||||
struct Xoroshiro_file_system;
|
||||
} /* namespace Vfs */
|
||||
|
||||
|
||||
struct Vfs::Xoroshiro_file_system : Single_file_system
|
||||
{
|
||||
Allocator &_alloc;
|
||||
|
||||
using File_path = String<256>;
|
||||
static File_path _get_seed_file_path(Xml_node const config)
|
||||
{
|
||||
if (!config.has_attribute("seed_path"))
|
||||
error("seed_path is unset");
|
||||
|
||||
/* open Readonly_file with empty path fails when reading */
|
||||
return config.attribute_value("seed_path", File_path(""));
|
||||
}
|
||||
|
||||
struct File_entropy_source : Entropy_source
|
||||
{
|
||||
Readonly_file const _seed_file;
|
||||
|
||||
File_entropy_source(Directory &root_dir,
|
||||
File_path const &file_path)
|
||||
: _seed_file { root_dir, file_path } { }
|
||||
|
||||
Collect_result collect(Byte_range_ptr const &dst) override
|
||||
{
|
||||
if (_seed_file.read(Readonly_file::At { 0 }, dst) >= dst.num_bytes)
|
||||
return Collect_ok();
|
||||
|
||||
return Collect_error();
|
||||
}
|
||||
};
|
||||
|
||||
Directory _root_dir;
|
||||
File_path const _seed_file_path;
|
||||
|
||||
struct Xoroshiro_vfs_handle : Single_vfs_handle
|
||||
{
|
||||
File_entropy_source _entropy_src;
|
||||
Xoroshiro_128_plus_reseeding _xoroshiro;
|
||||
|
||||
Xoroshiro_vfs_handle(Directory_service &ds,
|
||||
File_io_service &fs,
|
||||
Allocator &alloc,
|
||||
Directory &root_dir,
|
||||
File_path const &seed_file)
|
||||
:
|
||||
Single_vfs_handle { ds, fs, alloc, 0 },
|
||||
_entropy_src { root_dir, seed_file },
|
||||
_xoroshiro { _entropy_src }
|
||||
{ }
|
||||
|
||||
Read_result read(Byte_range_ptr const &dst, size_t &out_count) override
|
||||
{
|
||||
using Query_ok = Xoroshiro_128_plus_reseeding::Query_ok;
|
||||
using Query_error = Xoroshiro_128_plus_reseeding::Query_error;
|
||||
|
||||
return _xoroshiro.query(dst).convert<Read_result>(
|
||||
[&] (Query_ok ok) {
|
||||
out_count = ok.produced_bytes;
|
||||
return READ_OK;
|
||||
},
|
||||
[&] (Query_error e) {
|
||||
if (e == Query_error::RESEED_FAILED)
|
||||
error("xoroshiro reseeding failed");
|
||||
return READ_ERR_IO;
|
||||
});
|
||||
}
|
||||
|
||||
Write_result write(Const_byte_range_ptr const &, size_t &) override {
|
||||
return WRITE_ERR_IO; }
|
||||
|
||||
bool read_ready() const override { return true; }
|
||||
bool write_ready() const override { return false; }
|
||||
};
|
||||
|
||||
Xoroshiro_file_system(Vfs::Env &vfs_env, Xml_node config)
|
||||
:
|
||||
Single_file_system { Node_type::CONTINUOUS_FILE, name(),
|
||||
Node_rwx::ro(), config },
|
||||
_alloc { vfs_env.alloc() },
|
||||
_root_dir { Directory(vfs_env) },
|
||||
_seed_file_path { _get_seed_file_path(config) }
|
||||
{ }
|
||||
|
||||
static char const *name() { return "xoroshiro"; }
|
||||
char const *type() override { return "xoroshiro"; }
|
||||
|
||||
/*********************************
|
||||
** Directory service interface **
|
||||
*********************************/
|
||||
|
||||
Open_result open(char const *path, unsigned,
|
||||
Vfs_handle **out_handle,
|
||||
Allocator &alloc) override
|
||||
{
|
||||
if (!_single_file(path))
|
||||
return OPEN_ERR_UNACCESSIBLE;
|
||||
|
||||
try {
|
||||
/*
|
||||
* The primary reason for making the seed-file part of the
|
||||
* handle object and opening it implicitly while creating
|
||||
* the handle is to prevent accessing it during VFS construction,
|
||||
* which will fail.
|
||||
*/
|
||||
|
||||
*out_handle =
|
||||
new (alloc) Xoroshiro_vfs_handle(*this, *this, alloc,
|
||||
_root_dir,
|
||||
_seed_file_path);
|
||||
return OPEN_OK;
|
||||
}
|
||||
catch (Genode::Out_of_ram) { return OPEN_ERR_OUT_OF_RAM; }
|
||||
catch (Genode::Out_of_caps) { return OPEN_ERR_OUT_OF_CAPS; }
|
||||
/* handled non-existing path */
|
||||
catch (Genode::File::Open_failed) { return OPEN_ERR_UNACCESSIBLE; }
|
||||
}
|
||||
|
||||
Stat_result stat(char const *path, Stat &out) override {
|
||||
return Single_file_system::stat(path, out); }
|
||||
};
|
||||
|
||||
|
||||
struct Xoroshiro_factory : Vfs::File_system_factory
|
||||
{
|
||||
Vfs::File_system *create(Vfs::Env &env, Xml_node node) override
|
||||
{
|
||||
return new (env.alloc()) Vfs::Xoroshiro_file_system(env, node);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
extern "C" Vfs::File_system_factory *vfs_file_system_factory(void)
|
||||
{
|
||||
static Xoroshiro_factory factory;
|
||||
return &factory;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user