AtlatestRepositorysigil-wm

sigil-wm / tree / testsecurity-context-client.c

1/* SPDX-License-Identifier: BSD-3-Clause
2 * An isolated security-context fixture; never issues privileged requests.
3 * Bind probes require rejection, and run mode exercises an ordinary client. */
4#define _GNU_SOURCE
5#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"
21static 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]))
33struct registry {
34 struct wl_registry *object;
35 uint32_t ids[GLOBAL_COUNT];
36 bool compositor,shm,xdg,foreign_list,xwayland_shell;
37};
38struct context {
39 struct wp_security_context_v1 *object;
40 int lifetime;
41 char directory[96],path[108];
42};
43static void require(bool value,const char *message) {
44 if(!value) { fprintf(stderr,"security context fixture: %s\n",message); exit(1); }
46static 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;
58static void removed(void *data,struct wl_registry *registry,uint32_t id) {
59 (void)data; (void)registry; (void)id;
61static const struct wl_registry_listener registry_listener={global,removed};
62static 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,&registry_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");
70static 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);
75static void native_finished(void *data,struct ext_foreign_toplevel_list_v1 *list) {
76 (void)list;
77 *(bool *)data=true;
79static const struct ext_foreign_toplevel_list_v1_listener native_list_listener={
80 .toplevel=native_toplevel,.finished=native_finished,
81};
82static 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");
97static 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]);
122static 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;
130static 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);
136static 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);
179int 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;