From aefbae589e2f72bac3d25bd4d98d0c09060591be Mon Sep 17 00:00:00 2001 From: "Christian S.J. Peron" Date: Thu, 12 Nov 2026 19:10:49 -0500 Subject: [PATCH] cblock: add --sysvshm launch option; add postgres example Cellblocks are created with sysvshm=disable, the jail default, so nothing inside them can use SysV shared memory. Some services need it. PostgreSQL still creates a small SysV segment at startup, which it uses as a data directory interlock, even with shared_memory_type=mmap. Add "cblock launch -S/--sysvshm" (and "sysvshm: true" in the cblock_warden manifest). The option gives the jail its own SysV shared memory namespace (sysvshm=new): segments created in the cellblock are hidden from the host's namespace and from other cellblocks, even ones running under the same user ID. It is off by default to keep the syscall surface small for images that do not need it. The option is carried in a new p_sysvshm field of struct cblock_launch. This changes the wire format, so CBLOCK_PROTO_VERSION goes to 2. cblockd passes CBLOCK_SYSVSHM=new|disable to stage_launch.sh, which sets sysvshm explicitly on both the vnet and non-vnet jail -c paths. Add examples/postgres: a PostgreSQL 17 image whose entrypoint runs initdb only when the data directory holds no cluster yet. The superuser password comes from /run/secrets/postgres_password. The README covers the data volume, the secrets mount and --sysvshm. Document --sysvshm in README.md, including that kern.ipc limits are system-wide and how to cap each cellblock with RCTL. --- README.md | 15 +++++++++++++++ src/cblock/launch.c | 9 ++++++++- .../fixtures/launchWithPseudoFs.yaml | 1 + src/cblock_warden/main.go | 4 ++++ src/cblock_warden/parse_test.go | 1 + src/cblockd/dispatch.c | 9 +++++++++ src/include/cblock/libcblock.h | 3 ++- src/libcblock/proto.c | 2 ++ src/shell/stage_launch.sh | 3 ++- 9 files changed, 44 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 31174f7..024f740 100644 --- a/README.md +++ b/README.md @@ -510,3 +510,18 @@ cellblock needs a Vault token. Secrets are not available during `cblock build`. Do not put them in the build directory: the whole build context is copied to the cblockd host. + +### SysV shared memory + +Cellblocks cannot use SysV shared memory unless launched with +`--sysvshm` (`sysvshm: true` in the `cblock_warden` manifest). Some +services need it, PostgreSQL among them. The flag gives the jail its own +namespace (`sysvshm=new`): segments created in the cellblock are hidden +from the host's namespace and from other cellblocks, even ones running as +the same user ID. + +The kernel limits (`kern.ipc.shmmni`, `kern.ipc.shmall` and so on) are +shared by the whole system, so one cellblock can use them up for +everyone. With `kern.racct.enable=1` set at boot, RCTL rules such as +`jail::nshm:deny=8` and `jail::shmsize:deny=64m` cap each +cellblock. diff --git a/src/cblock/launch.c b/src/cblock/launch.c index fc8adac..9c945d1 100644 --- a/src/cblock/launch.c +++ b/src/cblock/launch.c @@ -52,6 +52,7 @@ struct launch_config { char *l_tag; char *l_ports; int l_host_networking; + int l_sysvshm; }; static struct option launch_options[] = { @@ -67,6 +68,7 @@ static struct option launch_options[] = { { "verbose", no_argument, 0, 'v' }, { "port", required_argument, 0, 'P' }, { "host-networking", no_argument, 0, 'H' }, + { "sysvshm", no_argument, 0, 'S' }, { 0, 0, 0, 0 } }; @@ -86,6 +88,7 @@ launch_usage(void) " -A, --no-attach Do not attach to container console\n" " -v, --verbose Launch container with verbosity enabled\n" " -H, --host-networking Use host networking instead of NAT/bridge\n" + " -S, --sysvshm Give the container its own SysV shared memory\n" ); exit(1); } @@ -120,6 +123,7 @@ launch_container(int sock, struct launch_config *lcp) vec_free(lcp->l_vec); } pl.p_verbose = lcp->l_verbose; + pl.p_sysvshm = lcp->l_sysvshm; snprintf(pl.p_tag, sizeof(pl.p_tag), "%s", lcp->l_tag); snprintf(pl.p_name, sizeof(pl.p_name), "%s", lcp->l_name); snprintf(pl.p_term, sizeof(pl.p_term), "%s", term); @@ -180,7 +184,7 @@ launch_main(int argc, char *argv [], int ctlsock) reset_getopt_state(); while (1) { option_index = 0; - c = getopt_long(argc, argv, "AHP:vN:Fpn:t:V:T", launch_options, + c = getopt_long(argc, argv, "AHP:vN:Fpn:t:V:TS", launch_options, &option_index); if (c == -1) { break; @@ -189,6 +193,9 @@ launch_main(int argc, char *argv [], int ctlsock) case 'H': lc.l_host_networking = 1; break; + case 'S': + lc.l_sysvshm = 1; + break; case 'P': vec_append(ports, optarg); break; diff --git a/src/cblock_warden/fixtures/launchWithPseudoFs.yaml b/src/cblock_warden/fixtures/launchWithPseudoFs.yaml index c22fe8a..7915449 100644 --- a/src/cblock_warden/fixtures/launchWithPseudoFs.yaml +++ b/src/cblock_warden/fixtures/launchWithPseudoFs.yaml @@ -4,4 +4,5 @@ cellblocks: fdescfs: true procfs: true tmpfs: true + sysvshm: true network: vlan0 diff --git a/src/cblock_warden/main.go b/src/cblock_warden/main.go index be5d31c..aa8f8fa 100644 --- a/src/cblock_warden/main.go +++ b/src/cblock_warden/main.go @@ -62,6 +62,7 @@ type Cellblock struct { Fdescfs bool `yaml:"fdescfs"` Procfs bool `yaml:"procfs"` Tmpfs bool `yaml:"tmpfs"` + Sysvshm bool `yaml:"sysvshm"` Volumes []Volume `yaml:"volumes"` Ports []PortMapping `yaml:"ports"` } @@ -143,6 +144,9 @@ func ProcessManifest(gcfg Config, prog string) ([]CmdVec, error) { if cb.Tmpfs { cmd.AddBool("tmpfs") } + if cb.Sysvshm { + cmd.AddBool("sysvshm") + } if len(cb.Volumes) > 0 { processVolumes(cb.Volumes, &cmd) } diff --git a/src/cblock_warden/parse_test.go b/src/cblock_warden/parse_test.go index b50378e..3c5793b 100644 --- a/src/cblock_warden/parse_test.go +++ b/src/cblock_warden/parse_test.go @@ -125,6 +125,7 @@ func TestLaunchCellblocks(t *testing.T) { "--fdescfs", "--procfs", "--tmpfs", + "--sysvshm", }, }, }, diff --git a/src/cblockd/dispatch.c b/src/cblockd/dispatch.c index 747bab4..f57c276 100644 --- a/src/cblockd/dispatch.c +++ b/src/cblockd/dispatch.c @@ -301,6 +301,15 @@ dispatch_launch_cblock(int sock) sprintf(buf, "CBLOCK_FS=%s", gcfg.c_underlying_fs); vec_append(env_vec, buf); + /* + * --sysvshm gives the jail its own SysV shared memory namespace. + * Without it the jail cannot use SysV shared memory at all. + */ + if (pl.p_sysvshm) { + vec_append(env_vec, "CBLOCK_SYSVSHM=new"); + } else { + vec_append(env_vec, "CBLOCK_SYSVSHM=disable"); + } vec_finalize(env_vec); sprintf(buf, "%s/lib/stage_launch.sh", gcfg.c_data_dir); vec_append(cmd_vec, "/bin/sh"); diff --git a/src/include/cblock/libcblock.h b/src/include/cblock/libcblock.h index fa01ef9..f88cf55 100644 --- a/src/include/cblock/libcblock.h +++ b/src/include/cblock/libcblock.h @@ -71,7 +71,7 @@ enum { * CBLOCK_PROTO_VERSION. Bump the version whenever the wire format changes. */ #define CBLOCK_PROTO_MAGIC 0x43424c4b /* "CBLK" */ -#define CBLOCK_PROTO_VERSION 1 +#define CBLOCK_PROTO_VERSION 2 /* * Maximum size of a single framed message. Console output larger than this @@ -125,6 +125,7 @@ struct cblock_launch { char p_ports[MAX_ARG_STRING]; char p_network[IF_NAMESIZE]; int p_verbose; + int p_sysvshm; }; struct cblock_signal_instance { diff --git a/src/libcblock/proto.c b/src/libcblock/proto.c index 7ff89f3..ab4b2c2 100644 --- a/src/libcblock/proto.c +++ b/src/libcblock/proto.c @@ -169,6 +169,7 @@ launch_enc(struct wire *w, const void *arg) wire_put_str(w, l->p_ports); wire_put_str(w, l->p_network); wire_put_u32(w, l->p_verbose); + wire_put_u32(w, l->p_sysvshm); } static void @@ -184,6 +185,7 @@ launch_dec(struct wire *w, void *arg) wire_get_str(w, l->p_ports, sizeof(l->p_ports)); wire_get_str(w, l->p_network, sizeof(l->p_network)); l->p_verbose = wire_get_int(w); + l->p_sysvshm = wire_get_int(w); } int diff --git a/src/shell/stage_launch.sh b/src/shell/stage_launch.sh index 1f91da2..5cf8d04 100755 --- a/src/shell/stage_launch.sh +++ b/src/shell/stage_launch.sh @@ -511,6 +511,7 @@ do_launch() "vnet.interface=$netif" \ "name=${image_name}-${instance_hostname}" \ "allow.chflags=1" \ + "sysvshm=${CBLOCK_SYSVSHM:-disable}" \ "osrelease=$(emit_os_release)" \ "path=${instance_root}" \ command="$@" @@ -531,7 +532,7 @@ do_launch() fi jailcmd="$jailcmd jail -c host.hostname=${instance_hostname} " jailcmd="$jailcmd name=${image_name}-${instance_hostname} " - jailcmd="$jailcmd allow.chflags=1 path=${instance_root} " + jailcmd="$jailcmd allow.chflags=1 sysvshm=${CBLOCK_SYSVSHM:-disable} path=${instance_root} " jailcmd="$jailcmd $netspec osrelease=$(emit_os_release) command=$@" eval $jailcmd fi