AtlatestRepositorysigil-wm
1
/* SPDX-License-Identifier: BSD-3-Clause2
* An isolated security-context fixture; never issues privileged requests.3
* Bind probes require rejection, and run mode exercises an ordinary client. */4
#define _GNU_SOURCE5
#include <errno.h>6
#include <stdbool.h>7
#include <stdint.h>8
#include <signal.h>9
#include <stdio.h>10
#include <stdlib.h>11
#include <string.h>12
#include <sys/prctl.h>13
#include <sys/socket.h>14
#include <sys/un.h>15
#include <sys/wait.h>16
#include <unistd.h>17
#include <wayland-client.h>18
#include "security-context-v1-client-protocol.h"19
#include "ext-foreign-toplevel-list-v1-client-protocol.h"21
static const char *const privileged[]={22
"ext_session_lock_manager_v1", "zwlr_screencopy_manager_v1",23
"ext_image_copy_capture_manager_v1", "ext_output_image_capture_source_manager_v1",24
"zwlr_data_control_manager_v1", "zwlr_output_power_manager_v1",25
"zwlr_gamma_control_manager_v1", "zwlr_output_manager_v1",26
"zwlr_layer_shell_v1", "ext_idle_notifier_v1",27
"zwp_virtual_keyboard_manager_v1", "zwlr_virtual_pointer_manager_v1",28
"zwlr_foreign_toplevel_manager_v1",29
"ext_foreign_toplevel_list_v1",30
"wp_security_context_manager_v1",31
};32
#define GLOBAL_COUNT (sizeof(privileged)/sizeof(privileged[0]))33
struct registry {34
struct wl_registry *object;35
uint32_t ids[GLOBAL_COUNT];36
bool compositor,shm,xdg,foreign_list,xwayland_shell;37
};38
struct context {39
struct wp_security_context_v1 *object;40
int lifetime;41
char directory[96],path[108];42
};43
static void require(bool value,const char *message) {44
if(!value) { fprintf(stderr,"security context fixture: %s\n",message); exit(1); }45
}46
static void global(void *data,struct wl_registry *registry,uint32_t id,47
const char *name,uint32_t version) {48
(void)registry; (void)version;49
struct registry *r=data;50
for(size_t i=0;i<GLOBAL_COUNT;i++)51
if(!strcmp(name,privileged[i])) r->ids[i]=id;52
if(!strcmp(name,"wl_compositor")) r->compositor=true;53
if(!strcmp(name,"wl_shm")) r->shm=true;54
if(!strcmp(name,"xdg_wm_base")) r->xdg=true;55
if(!strcmp(name,"ext_foreign_toplevel_list_v1")) r->foreign_list=true;56
if(!strcmp(name,"xwayland_shell_v1")) r->xwayland_shell=true;57
}58
static void removed(void *data,struct wl_registry *registry,uint32_t id) {59
(void)data; (void)registry; (void)id;60
}61
static const struct wl_registry_listener registry_listener={global,removed};62
static void registry_init(struct wl_display *display,struct registry *r) {63
r->object=wl_display_get_registry(display);64
require(r->object!=NULL,"get registry");65
wl_registry_add_listener(r->object,®istry_listener,r);66
require(wl_display_roundtrip(display)>=0,"registry roundtrip");67
require(r->compositor && r->shm && r->xdg,"ordinary rendering globals hidden");68
require(!r->xwayland_shell,"private Xwayland shell advertised to ordinary client");69
}70
static void native_toplevel(void *data,struct ext_foreign_toplevel_list_v1 *list,71
struct ext_foreign_toplevel_handle_v1 *handle) {72
(void)data; (void)list;73
ext_foreign_toplevel_handle_v1_destroy(handle);74
}75
static void native_finished(void *data,struct ext_foreign_toplevel_list_v1 *list) {76
(void)list;77
*(bool *)data=true;78
}79
static const struct ext_foreign_toplevel_list_v1_listener native_list_listener={80
.toplevel=native_toplevel,.finished=native_finished,81
};82
static void check_native_list(struct wl_display *display,const struct registry *baseline) {83
uint32_t id=0;84
for(size_t i=0;i<GLOBAL_COUNT;i++)85
if(!strcmp(privileged[i],ext_foreign_toplevel_list_v1_interface.name)) id=baseline->ids[i];86
require(id!=0 && baseline->foreign_list,"native foreign list missing");87
struct ext_foreign_toplevel_list_v1 *list=wl_registry_bind(baseline->object,id,88
&ext_foreign_toplevel_list_v1_interface,1);89
bool finished=false;90
ext_foreign_toplevel_list_v1_add_listener(list,&native_list_listener,&finished);91
require(wl_display_roundtrip(display)>=0,"native foreign list bind failed");92
ext_foreign_toplevel_list_v1_stop(list);93
require(wl_display_roundtrip(display)>=0 && finished,"native foreign list stop failed");94
ext_foreign_toplevel_list_v1_destroy(list);95
puts("NATIVE_FOREIGN_LIST_BIND=PASS");96
}97
static void context_init(struct wl_display *native,98
struct wp_security_context_manager_v1 *manager,struct context *c,bool labels) {99
const char *runtime=getenv("XDG_RUNTIME_DIR");100
require(runtime && snprintf(c->directory,sizeof(c->directory),"%s/context.XXXXXX",runtime)101
<(int)sizeof(c->directory),"private runtime path");102
require(mkdtemp(c->directory)!=NULL,"context directory");103
require(snprintf(c->path,sizeof(c->path),"%s/socket",c->directory)<(int)sizeof(c->path),"socket path");104
int fd=socket(AF_UNIX,SOCK_STREAM|SOCK_CLOEXEC,0),lifetime[2];105
require(fd>=0,"listener socket");106
struct sockaddr_un address={.sun_family=AF_UNIX};107
memcpy(address.sun_path,c->path,strlen(c->path)+1);108
require(bind(fd,(struct sockaddr *)&address,sizeof(address))==0 && listen(fd,16)==0,"listen");109
require(socketpair(AF_UNIX,SOCK_STREAM|SOCK_CLOEXEC,0,lifetime)==0,"lifetime socket");110
c->lifetime=lifetime[1];111
c->object=wp_security_context_manager_v1_create_listener(manager,fd,lifetime[0]);112
require(c->object!=NULL,"create security context");113
if(labels) {114
wp_security_context_v1_set_sandbox_engine(c->object,"org.flatpak");115
wp_security_context_v1_set_app_id(c->object,"org.example.NativeLookingHelper");116
wp_security_context_v1_set_instance_id(c->object,"fixture");117
}118
wp_security_context_v1_commit(c->object);119
require(wl_display_roundtrip(native)>=0,"commit security context");120
close(fd); close(lifetime[0]);121
}122
static int context_connect(const struct context *c) {123
int fd=socket(AF_UNIX,SOCK_STREAM|SOCK_CLOEXEC,0);124
require(fd>=0,"sandbox socket");125
struct sockaddr_un address={.sun_family=AF_UNIX};126
memcpy(address.sun_path,c->path,strlen(c->path)+1);127
require(connect(fd,(struct sockaddr *)&address,sizeof(address))==0,"sandbox connect");128
return fd;129
}130
static void context_finish(struct wl_display *native,struct context *c) {131
wp_security_context_v1_destroy(c->object);132
close(c->lifetime);133
require(wl_display_roundtrip(native)>=0,"native client lost after sandbox probes");134
unlink(c->path); rmdir(c->directory);135
}136
static void check_context(struct wl_display *native,137
struct wp_security_context_manager_v1 *manager,const struct registry *baseline,bool labels) {138
struct context c={0};139
context_init(native,manager,&c,labels);140
struct wl_display *sandbox=wl_display_connect_to_fd(context_connect(&c));141
require(sandbox!=NULL,"sandbox display");142
struct registry filtered={0};143
registry_init(sandbox,&filtered);144
require(!filtered.foreign_list,"sandbox received foreign list");145
for(size_t i=0;i<GLOBAL_COUNT;i++)146
require(filtered.ids[i]==0,"sandbox received privileged global");147
require(wl_display_roundtrip(sandbox)>=0,"second sandbox registry roundtrip");148
for(size_t i=0;i<GLOBAL_COUNT;i++)149
require(filtered.ids[i]==0,"late privileged global advertisement");150
wl_registry_destroy(filtered.object);151
wl_display_disconnect(sandbox);152
unsigned denied=0;153
for(size_t i=0;i<GLOBAL_COUNT;i++) {154
if(!baseline->ids[i]) continue;155
sandbox=wl_display_connect_to_fd(context_connect(&c));156
require(sandbox!=NULL,"denied-bind display");157
struct registry r={0}; registry_init(sandbox,&r);158
const struct wl_interface interface={.name=privileged[i],.version=1};159
struct wl_proxy *probe=wl_registry_bind(r.object,baseline->ids[i],&interface,1);160
require(probe!=NULL,"construct bind probe");161
require(wl_display_roundtrip(sandbox)<0 && wl_display_get_error(sandbox)==EPROTO,162
"sandbox bound a filtered global");163
const struct wl_interface *error_interface=NULL;164
uint32_t error_id=0;165
uint32_t error=wl_display_get_protocol_error(sandbox,&error_interface,&error_id);166
require(error==WL_DISPLAY_ERROR_INVALID_OBJECT && error_interface &&167
!strcmp(error_interface->name,"wl_registry") &&168
error_id==wl_proxy_get_id((struct wl_proxy *)r.object),169
"bind failed for a reason other than registry authorization");170
wl_proxy_destroy(probe); wl_registry_destroy(r.object);171
wl_display_disconnect(sandbox);172
require(wl_display_roundtrip(native)>=0,"native connection failed during denial");173
denied++;174
}175
printf("SECURITY_CONTEXT=PASS metadata=%s denied-binds=%u registry ordinary-globals same-pid-native\n",176
labels ? "labelled" : "empty",denied);177
context_finish(native,&c);178
}179
int main(int argc,char **argv) {180
bool run=argc>=4 && !strcmp(argv[1],"run") && !strcmp(argv[2],"--");181
bool check=argc==3 && !strcmp(argv[1],"check") &&182
(!strcmp(argv[2],"native") || !strcmp(argv[2],"virtual"));183
require(run || check,"usage: security-context-client check native|virtual; run -- PROGRAM [ARGS]");184
struct wl_display *native=wl_display_connect(NULL);185
require(native!=NULL,"native connection");186
struct registry baseline={0}; registry_init(native,&baseline);187
for(size_t i=0;i<GLOBAL_COUNT;i++) {188
bool virtual_input=i==10 || i==11;189
if(!virtual_input || (check && !strcmp(argv[2],"virtual")))190
require(baseline.ids[i]!=0,"native privileged global missing");191
}192
struct wp_security_context_manager_v1 *manager=wl_registry_bind(baseline.object,193
baseline.ids[GLOBAL_COUNT-1],&wp_security_context_manager_v1_interface,1);194
require(manager!=NULL,"native security context manager");195
int result=0;196
if(check) {197
check_native_list(native,&baseline);198
check_context(native,manager,&baseline,false);199
check_context(native,manager,&baseline,true);200
printf("NATIVE_PROTOCOL_COMPATIBILITY=PASS\n");201
} else {202
struct context c={0}; context_init(native,manager,&c,true);203
int fd=context_connect(&c);204
pid_t parent=getpid(),child=fork();205
require(child>=0,"fork rendering fixture");206
if(!child) {207
if(prctl(PR_SET_PDEATHSIG,SIGTERM)!=0 || getppid()!=parent) _exit(126);208
int inherited=dup(fd);209
if(inherited<0) _exit(126);210
close(fd); close(c.lifetime); close(wl_display_get_fd(native));211
char value[32]; snprintf(value,sizeof(value),"%d",inherited);212
if(setenv("WAYLAND_SOCKET",value,1)!=0) _exit(126);213
execvp(argv[3],argv+3);214
_exit(127);215
}216
close(fd);217
int status;218
while(waitpid(child,&status,0)<0) require(errno==EINTR,"wait rendering fixture");219
result=WIFEXITED(status) ? WEXITSTATUS(status) : 128+WTERMSIG(status);220
context_finish(native,&c);221
}222
wp_security_context_manager_v1_destroy(manager);223
wl_registry_destroy(baseline.object); wl_display_disconnect(native);224
return result;225
}