noux: Keep track of how dataspaces are used

This patch eliminates the "no attachment at..." warnings, which
were caused by a use-after-free problem of dataspaces. When a
dataspace was destroyed, the users of the dataspace were not
informed and therefore could not revert possible attachments to
RM sessions. The fix introduces a callback mechanism that allows
dataspace users (i.e., RM regions) to register for the event that
a dataspace vanishes.

The following types of dataspaces are handled:
* RAM dataspaces
* ROM dataspaces
* The process binary
* The binary of the dynamic linker
* Args dataspace
* Sysio dataspace
* Env dataspace
* managed RM dataspaces

The handling of ROM dataspaces is still not complete. When forking,
the ROM dataspace of the parent process gets just reused without
creating proper meta data ('Dataspace_info') for the forked process.
Similar issues might arise from other special dataspaces (e.g.,
args, env, sysio).

This patch removes all "no attachment at..." warnings except for
one (an attachment at 0).

Issue #485
This commit is contained in:
Norman Feske 2013-07-18 16:27:42 +02:00
parent 959282403c
commit 956cab5fdb
10 changed files with 367 additions and 27 deletions

View File

@ -35,6 +35,7 @@
#include <local_cpu_service.h>
#include <local_ram_service.h>
#include <local_rom_service.h>
namespace Noux {
@ -82,6 +83,11 @@ namespace Noux {
*/
Io_receptor_registry *io_receptor_registry();
/**
* Return ELF binary of dynamic linker
*/
Dataspace_capability ldso_ds_cap();
class Child;
bool is_init_process(Child *child);
@ -181,9 +187,17 @@ namespace Noux {
Local_ram_service _local_ram_service;
Local_cpu_service _local_cpu_service;
Local_rm_service _local_rm_service;
Local_rom_service _local_rom_service;
Service_registry &_parent_services;
Static_dataspace_info _binary_ds_info;
Static_dataspace_info _sysio_ds_info;
Static_dataspace_info _ldso_ds_info;
Static_dataspace_info _args_ds_info;
Static_dataspace_info _env_ds_info;
Child_policy _child_policy;
Genode::Child _child;
/**
@ -270,8 +284,7 @@ namespace Noux {
_args(ARGS_DS_SIZE, args),
_env(env),
_root_dir(root_dir),
_binary_ds(forked ? Dataspace_capability()
: root_dir->dataspace(name)),
_binary_ds(root_dir->dataspace(name)),
_sysio_ds(Genode::env()->ram_session(), SYSIO_DS_SIZE),
_sysio(_sysio_ds.local_addr<Sysio>()),
_noux_session_cap(Session_capability(_entrypoint.manage(this))),
@ -279,12 +292,20 @@ namespace Noux {
_local_ram_service(_entrypoint),
_local_cpu_service(_entrypoint, _resources.cpu.cpu_cap()),
_local_rm_service(_entrypoint, _resources.ds_registry),
_local_rom_service(_entrypoint, _resources.ds_registry),
_parent_services(parent_services),
_binary_ds_info(_resources.ds_registry, _binary_ds),
_sysio_ds_info(_resources.ds_registry, _sysio_ds.cap()),
_ldso_ds_info(_resources.ds_registry, ldso_ds_cap()),
_args_ds_info(_resources.ds_registry, _args.cap()),
_env_ds_info(_resources.ds_registry, _env.cap()),
_child_policy(name, _binary_ds, _args.cap(), _env.cap(),
_entrypoint, _local_noux_service,
_local_rm_service, _parent_services,
_local_rm_service, _local_rom_service,
_parent_services,
*this, *this, _destruct_context_cap, _resources.ram),
_child(_binary_ds, _resources.ram.cap(), _resources.cpu.cap(),
_child(forked ? Dataspace_capability() : _binary_ds,
_resources.ram.cap(), _resources.cpu.cap(),
_resources.rm.cap(), &_entrypoint, &_child_policy,
/**
* Override the implicit assignment to _parent_service

View File

@ -22,6 +22,7 @@
#include <file_descriptor_registry.h>
#include <local_noux_service.h>
#include <local_rm_service.h>
#include <local_rom_service.h>
namespace Noux {
@ -38,6 +39,7 @@ namespace Noux {
Init::Child_policy_provide_rom_file _env_policy;
Local_noux_service &_local_noux_service;
Local_rm_service &_local_rm_service;
Local_rom_service &_local_rom_service;
Service_registry &_parent_services;
Family_member &_family_member;
File_descriptor_registry &_file_descriptor_registry;
@ -53,6 +55,7 @@ namespace Noux {
Rpc_entrypoint &entrypoint,
Local_noux_service &local_noux_service,
Local_rm_service &local_rm_service,
Local_rom_service &local_rom_service,
Service_registry &parent_services,
Family_member &family_member,
File_descriptor_registry &file_descriptor_registry,
@ -66,6 +69,7 @@ namespace Noux {
_env_policy( "env", env_ds, &entrypoint),
_local_noux_service(local_noux_service),
_local_rm_service(local_rm_service),
_local_rom_service(local_rom_service),
_parent_services(parent_services),
_family_member(family_member),
_file_descriptor_registry(file_descriptor_registry),
@ -98,6 +102,12 @@ namespace Noux {
if (strcmp(service_name, Rm_session::service_name()) == 0)
return &_local_rm_service;
/*
* Check for local ROM service
*/
if (strcmp(service_name, Rom_session::service_name()) == 0)
return &_local_rom_service;
return _parent_services.find(service_name);
}

View File

@ -20,6 +20,13 @@
namespace Noux {
class Dataspace_registry;
class Dataspace_info;
struct Dataspace_user : List<Dataspace_user>::Element
{
virtual void dissolve(Dataspace_info &ds) = 0;
};
class Dataspace_info : public Object_pool<Dataspace_info>::Entry
@ -28,6 +35,8 @@ namespace Noux {
size_t _size;
Dataspace_capability _ds_cap;
Lock _users_lock;
List<Dataspace_user> _users;
public:
@ -43,6 +52,32 @@ namespace Noux {
size_t size() const { return _size; }
Dataspace_capability ds_cap() const { return _ds_cap; }
void register_user(Dataspace_user &user)
{
Lock::Guard guard(_users_lock);
_users.insert(&user);
}
void unregister_user(Dataspace_user &user)
{
Lock::Guard guard(_users_lock);
_users.remove(&user);
}
void dissolve_users()
{
for (;;) {
Dataspace_user *user = 0;
{
Lock::Guard guard(_users_lock);
user = _users.first();
if (!user)
break;
}
user->dissolve(*this);
}
}
/**
* Create shadow copy of dataspace
*
@ -112,6 +147,45 @@ namespace Noux {
return _pool.lookup_and_lock(ds_cap);
}
};
struct Static_dataspace_info : Dataspace_info
{
Dataspace_registry &_ds_registry;
Static_dataspace_info(Dataspace_registry &ds_registry,
Dataspace_capability ds)
: Dataspace_info(ds), _ds_registry(ds_registry)
{
_ds_registry.insert(this);
}
~Static_dataspace_info()
{
Static_dataspace_info *info =
dynamic_cast<Static_dataspace_info *>(_ds_registry.lookup_info(ds_cap()));
if (!info) {
PERR("lookup of binary ds info failed");
return;
}
info->dissolve_users();
_ds_registry.remove(info);
}
Dataspace_capability fork(Ram_session_capability,
Dataspace_registry &,
Rpc_entrypoint &)
{
return ds_cap();
}
void poke(addr_t dst_offset, void const *src, size_t len)
{
PERR("Attempt to poke onto a static dataspace");
}
};
}
#endif /* _NOUX__DATASPACE_REGISTRY_H_ */

View File

@ -26,7 +26,7 @@ namespace Noux {
{
private:
Rpc_entrypoint &_ep;
Rpc_entrypoint &_ep;
public:

View File

@ -133,6 +133,7 @@ namespace Noux {
/* release dataspace info */
Dataspace_info *info = _ds_registry.lookup_info(ds_cap);
if (info) {
info->dissolve_users();
_ds_registry.remove(info);
destroy(env()->heap(), info);
} else {

View File

@ -0,0 +1,78 @@
/*
* \brief ROM service provided to Noux processes
* \author Norman Feske
* \date 2013-07-18
*
* The local ROM service has the sole purpose of tracking ROM dataspaces
* so that they are properly detached from RM sessions when the corresponding
* ROM sessions are closed.
*/
/*
* Copyright (C) 2013 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU General Public License version 2.
*/
#ifndef _NOUX__LOCAL_ROM_SERVICE_H_
#define _NOUX__LOCAL_ROM_SERVICE_H_
/* Genode includes */
#include <base/service.h>
/* Noux includes */
#include <dataspace_registry.h>
#include <rom_session_component.h>
namespace Noux {
class Local_rom_service : public Service
{
private:
Rpc_entrypoint &_ep;
Dataspace_registry &_ds_registry;
public:
Local_rom_service(Rpc_entrypoint &ep, Dataspace_registry &ds_registry)
:
Service(Rom_session::service_name()), _ep(ep),
_ds_registry(ds_registry)
{ }
Genode::Session_capability session(const char *args)
{
enum { NAME_MAX_LEN = 128 };
char name[NAME_MAX_LEN];
Arg_string::find_arg(args, "filename").string(name, sizeof(name), "<noname>");
Rom_session_component *rom = new (env()->heap())
Rom_session_component(_ds_registry, name);
return _ep.manage(rom);
}
void upgrade(Genode::Session_capability, const char *args) { }
void close(Genode::Session_capability session)
{
/* acquire locked session object */
Rom_session_component *rom_session =
dynamic_cast<Rom_session_component *>(_ep.lookup_and_lock(session));
if (!rom_session) {
PWRN("Unexpected call of close with non-ROM-session argument");
return;
}
_ep.dissolve(rom_session);
destroy(env()->heap(), rom_session);
}
};
}
#endif /* _NOUX__LOCAL_ROM_SERVICE_H_ */

View File

@ -819,6 +819,18 @@ Terminal::Connection *Noux::terminal()
}
Genode::Dataspace_capability Noux::ldso_ds_cap()
{
try {
static Genode::Rom_connection rom("ld.lib.so");
static Genode::Dataspace_capability ldso_ds = rom.dataspace();
return ldso_ds;
} catch (...) { }
return Genode::Dataspace_capability();
}
void *operator new (Genode::size_t size) {
return Genode::env()->heap()->alloc(size); }
@ -828,11 +840,8 @@ int main(int argc, char **argv)
using namespace Noux;
PINF("--- noux started ---");
/* look for dynamic linker */
try {
static Genode::Rom_connection rom("ld.lib.so");
Genode::Process::dynamic_linker(rom.dataspace());
} catch (...) { }
/* register dynamic linker */
Genode::Process::dynamic_linker(ldso_ds_cap());
/* whitelist of service requests to be routed to the parent */
static Genode::Service_registry parent_services;

View File

@ -160,7 +160,9 @@ namespace Noux {
PERR("RAM free: dataspace lookup failed");
return;
}
ds_info->dissolve_users();
_registry.remove(ds_info);
_list.remove(ds_info);
_used_quota -= ds_info->size();

View File

@ -24,6 +24,8 @@
namespace Noux {
static bool verbose_attach = false;
class Rm_session_component : public Rpc_object<Rm_session>
{
private:
@ -31,17 +33,20 @@ namespace Noux {
/**
* Record of an attached dataspace
*/
struct Region : List<Region>::Element
struct Region : List<Region>::Element, Dataspace_user
{
Dataspace_capability ds;
size_t size;
off_t offset;
addr_t local_addr;
Rm_session_component &rm;
Dataspace_capability ds;
size_t size;
off_t offset;
addr_t local_addr;
Region(Dataspace_capability ds, size_t size,
Region(Rm_session_component &rm,
Dataspace_capability ds, size_t size,
off_t offset, addr_t local_addr)
:
ds(ds), size(size), offset(offset), local_addr(local_addr)
rm(rm), ds(ds), size(size), offset(offset),
local_addr(local_addr)
{ }
/**
@ -52,6 +57,13 @@ namespace Noux {
return (addr >= local_addr)
&& (addr < local_addr + size);
}
Region *next_region()
{
return List<Region>::Element::next();
}
inline void dissolve(Dataspace_info &ds);
};
List<Region> _regions;
@ -59,7 +71,7 @@ namespace Noux {
Region *_lookup_region_by_addr(addr_t local_addr)
{
Region *curr = _regions.first();
for (; curr; curr = curr->next()) {
for (; curr; curr = curr->next_region()) {
if (curr->contains(local_addr))
return curr;
}
@ -103,7 +115,7 @@ namespace Noux {
Dataspace_registry &ds_registry,
Rpc_entrypoint &ep)
{
for (Region *curr = _regions.first(); curr; curr = curr->next()) {
for (Region *curr = _regions.first(); curr; curr = curr->next_region()) {
Dataspace_capability ds;
@ -121,6 +133,9 @@ namespace Noux {
} else {
PERR("replay: missing ds_info for dataspace at addr 0x%lx",
curr->local_addr);
/*
* If the dataspace is not a RAM dataspace, assume that
* it's a ROM dataspace.
@ -195,21 +210,35 @@ namespace Noux {
if (size == 0)
size = Dataspace_client(ds).size() - offset;
/*
* XXX look if we can identify the specified dataspace.
* Is it a dataspace allocated via 'Local_ram_session'?
*/
local_addr = _rm.attach(ds, size, offset,
use_local_addr, local_addr,
executable);
Region *region = new (env()->heap())
Region(*this, ds, size, offset, local_addr);
/* register region as user of RAM dataspaces */
Object_pool<Dataspace_info>::Guard info(_ds_registry.lookup_info(ds));
if (info) {
info->register_user(*region);
} else {
if (verbose_attach) {
PWRN("Trying to attach unknown dataspace type");
PWRN(" ds_info@%p at 0x%lx size=%zd offset=0x%lx",
info.object(), (long)local_addr,
Dataspace_client(ds).size(), (long)offset);
}
}
/*
* Record attachment for later replay (needed during
* fork)
*/
_regions.insert(new (env()->heap())
Region(ds, size, offset, local_addr));
_regions.insert(region);
return local_addr;
}
@ -225,6 +254,11 @@ namespace Noux {
}
_regions.remove(region);
Object_pool<Dataspace_info>::Guard info(_ds_registry.lookup_info(region->ds));
if (info)
info->unregister_user(*region);
destroy(env()->heap(), region);
}
@ -253,6 +287,20 @@ namespace Noux {
return _rm.dataspace();
}
};
inline void Rm_session_component::Region::dissolve(Dataspace_info &ds)
{
/*
* When this function is called, the 'ds' is already locked by the
* caller. To prevent 'detach' from taking the lock twice
* ('_ds_registry.lookup_info'), the temporarily release and re-acquire
* the lock.
*/
ds.release();
rm.detach(local_addr);
ds.acquire();
}
}
#endif /* _NOUX__RM_SESSION_COMPONENT_H_ */

View File

@ -0,0 +1,97 @@
/*
* \brief ROM session implementation used by Noux processes
* \author Norman Feske
* \date 2013-07-18
*/
/*
* Copyright (C) 2013 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU General Public License version 2.
*/
#ifndef _NOUX__ROM_SESSION_COMPONENT_H_
#define _NOUX__ROM_SESSION_COMPONENT_H_
/* Genode includes */
#include <rom_session/connection.h>
#include <base/rpc_server.h>
namespace Noux {
struct Rom_dataspace_info : Dataspace_info
{
Rom_dataspace_info(Dataspace_capability ds) : Dataspace_info(ds) { }
~Rom_dataspace_info() { }
Dataspace_capability fork(Ram_session_capability,
Dataspace_registry &ds_registry,
Rpc_entrypoint &)
{
return ds_cap();
}
void poke(addr_t dst_offset, void const *src, size_t len)
{
PERR("Attempt to poke onto a ROM dataspace");
}
};
class Rom_session_component : public Rpc_object<Rom_session>
{
private:
/**
* Wrapped ROM session at core
*/
Rom_connection _rom;
Dataspace_registry &_ds_registry;
Rom_dataspace_info _ds_info;
public:
Rom_session_component(Dataspace_registry &ds_registry,
char const *name)
:
_rom(name), _ds_registry(ds_registry), _ds_info(_rom.dataspace())
{
_ds_registry.insert(&_ds_info);
}
~Rom_session_component()
{
/*
* Lookup and lock ds info instead of directly acccessing
* the '_ds_info' member.
*/
Object_pool<Dataspace_info>::Guard
info(_ds_registry.lookup_info(_ds_info.ds_cap()));
if (info)
info->dissolve_users();
else
PERR("~Rom_session_component: unexpected !info");
_ds_registry.remove(&_ds_info);
}
/***************************
** ROM session interface **
***************************/
Rom_dataspace_capability dataspace()
{
return static_cap_cast<Rom_dataspace>(_ds_info.ds_cap());
}
void sigh(Signal_context_capability) { }
};
}
#endif /* _NOUX__ROM_SESSION_COMPONENT_H_ */