diff options
-rw-r--r-- | src/firejail/pulseaudio.c | 170 |
1 files changed, 110 insertions, 60 deletions
diff --git a/src/firejail/pulseaudio.c b/src/firejail/pulseaudio.c index ce1692ba2..4ddaba7ed 100644 --- a/src/firejail/pulseaudio.c +++ b/src/firejail/pulseaudio.c | |||
@@ -102,74 +102,124 @@ void pulseaudio_init(void) { | |||
102 | if (set_perms(RUN_PULSE_DIR, getuid(), getgid(), 0700)) | 102 | if (set_perms(RUN_PULSE_DIR, getuid(), getgid(), 0700)) |
103 | errExit("set_perms"); | 103 | errExit("set_perms"); |
104 | 104 | ||
105 | // create ~/.config directory if necessary | 105 | // create ~/.config/pulse directory if not present |
106 | char *homeusercfg; | 106 | char *dir1; |
107 | if (asprintf(&homeusercfg, "%s/.config", cfg.homedir) == -1) | 107 | if (asprintf(&dir1, "%s/.config", cfg.homedir) == -1) |
108 | errExit("asprintf"); | 108 | errExit("asprintf"); |
109 | create_empty_dir_as_user(homeusercfg, 0700); | 109 | if (lstat(dir1, &s) == -1) { |
110 | // set environment variable if creating ~/.config wasn't successful or if it is not a directory owned by the user | 110 | pid_t child = fork(); |
111 | if (lstat(homeusercfg, &s) != 0 || !S_ISDIR(s.st_mode) || s.st_uid != getuid()) { | 111 | if (child < 0) |
112 | if (arg_debug) | 112 | errExit("fork"); |
113 | printf("Setting PULSE_CLIENTCONFIG environment variable\n"); | 113 | if (child == 0) { |
114 | if (setenv("PULSE_CLIENTCONFIG", pulsecfg, 1) < 0) | 114 | // drop privileges |
115 | errExit("setenv"); | 115 | drop_privs(0); |
116 | free(homeusercfg); | 116 | |
117 | free(pulsecfg); | 117 | int rv = mkdir(dir1, 0755); |
118 | return; | 118 | if (rv == 0) { |
119 | if (chmod(dir1, 0755)) | ||
120 | {;} // do nothing | ||
121 | } | ||
122 | #ifdef HAVE_GCOV | ||
123 | __gcov_flush(); | ||
124 | #endif | ||
125 | _exit(0); | ||
126 | } | ||
127 | // wait for the child to finish | ||
128 | waitpid(child, NULL, 0); | ||
129 | fs_logger2("create", dir1); | ||
119 | } | 130 | } |
120 | free(homeusercfg); | 131 | else { |
132 | // we expect a user owned directory | ||
133 | if (!S_ISDIR(s.st_mode) || s.st_uid != getuid()) { | ||
134 | if (S_ISLNK(s.st_mode)) | ||
135 | fprintf(stderr, "Error: user .config is a symbolic link\n"); | ||
136 | else | ||
137 | fprintf(stderr, "Error: user .config is not a directory owned by the current user\n"); | ||
138 | exit(1); | ||
139 | } | ||
140 | } | ||
141 | free(dir1); | ||
142 | |||
143 | if (asprintf(&dir1, "%s/.config/pulse", cfg.homedir) == -1) | ||
144 | errExit("asprintf"); | ||
145 | if (lstat(dir1, &s) == -1) { | ||
146 | pid_t child = fork(); | ||
147 | if (child < 0) | ||
148 | errExit("fork"); | ||
149 | if (child == 0) { | ||
150 | // drop privileges | ||
151 | drop_privs(0); | ||
152 | |||
153 | int rv = mkdir(dir1, 0700); | ||
154 | if (rv == 0) { | ||
155 | if (chmod(dir1, 0700)) | ||
156 | {;} // do nothing | ||
157 | } | ||
158 | #ifdef HAVE_GCOV | ||
159 | __gcov_flush(); | ||
160 | #endif | ||
161 | _exit(0); | ||
162 | } | ||
163 | // wait for the child to finish | ||
164 | waitpid(child, NULL, 0); | ||
165 | fs_logger2("create", dir1); | ||
166 | } | ||
167 | else { | ||
168 | // we expect a user owned directory | ||
169 | if (!S_ISDIR(s.st_mode) || s.st_uid != getuid()) { | ||
170 | if (S_ISLNK(s.st_mode)) | ||
171 | fprintf(stderr, "Error: user .config/pulse is a symbolic link\n"); | ||
172 | else | ||
173 | fprintf(stderr, "Error: user .config/pulse is not a directory owned by the current user\n"); | ||
174 | exit(1); | ||
175 | } | ||
176 | } | ||
177 | free(dir1); | ||
121 | 178 | ||
122 | // create ~/.config/pulse directory if necessary | 179 | // if we have ~/.config/pulse mount the new directory, else set environment variable. |
180 | char *homeusercfg; | ||
123 | if (asprintf(&homeusercfg, "%s/.config/pulse", cfg.homedir) == -1) | 181 | if (asprintf(&homeusercfg, "%s/.config/pulse", cfg.homedir) == -1) |
124 | errExit("asprintf"); | 182 | errExit("asprintf"); |
125 | create_empty_dir_as_user(homeusercfg, 0700); | 183 | if (stat(homeusercfg, &s) == 0) { |
126 | // set environment variable if creating ~/.config/pulse wasn't successful or if it is not a directory owned by the user | 184 | // get a file descriptor for ~/.config/pulse, fails if there is any symlink |
127 | if (lstat(homeusercfg, &s) != 0 || !S_ISDIR(s.st_mode) || s.st_uid != getuid()) { | 185 | int fd = safe_fd(homeusercfg, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); |
128 | if (arg_debug) | 186 | if (fd == -1) |
129 | printf("Setting PULSE_CLIENTCONFIG environment variable\n"); | 187 | errExit("safe_fd"); |
130 | if (setenv("PULSE_CLIENTCONFIG", pulsecfg, 1) < 0) | 188 | // confirm the actual mount destination is owned by the user |
131 | errExit("setenv"); | 189 | if (fstat(fd, &s) == -1 || s.st_uid != getuid()) |
132 | free(homeusercfg); | 190 | errExit("fstat"); |
133 | free(pulsecfg); | 191 | // preserve a read-only mount |
134 | return; | 192 | struct statvfs vfs; |
193 | if (fstatvfs(fd, &vfs) == -1) | ||
194 | errExit("fstatvfs"); | ||
195 | if ((vfs.f_flag & MS_RDONLY) == MS_RDONLY) | ||
196 | fs_rdonly(RUN_PULSE_DIR); | ||
197 | // mount via the link in /proc/self/fd | ||
198 | char *proc; | ||
199 | if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1) | ||
200 | errExit("asprintf"); | ||
201 | if (mount(RUN_PULSE_DIR, proc, "none", MS_BIND, NULL) < 0) | ||
202 | errExit("mount pulseaudio"); | ||
203 | fs_logger2("tmpfs", homeusercfg); | ||
204 | free(proc); | ||
205 | close(fd); | ||
206 | // check /proc/self/mountinfo to confirm the mount is ok | ||
207 | MountData *mptr = get_last_mount(); | ||
208 | if (strcmp(mptr->dir, homeusercfg) != 0 || strcmp(mptr->fstype, "tmpfs") != 0) | ||
209 | errLogExit("invalid pulseaudio mount"); | ||
210 | |||
211 | char *p; | ||
212 | if (asprintf(&p, "%s/client.conf", homeusercfg) == -1) | ||
213 | errExit("asprintf"); | ||
214 | fs_logger2("create", p); | ||
215 | free(p); | ||
135 | } | 216 | } |
136 | 217 | ||
137 | // get a file descriptor for ~/.config/pulse, fails if there is any symlink | 218 | else { |
138 | int fd = safe_fd(homeusercfg, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); | 219 | // set environment |
139 | if (fd == -1) | 220 | if (setenv("PULSE_CLIENTCONFIG", pulsecfg, 1) < 0) |
140 | errExit("safe_fd"); | 221 | errExit("setenv"); |
141 | // confirm again the actual mount destination is owned by the user | ||
142 | if (fstat(fd, &s) == -1) | ||
143 | errExit("fstat"); | ||
144 | if (s.st_uid != getuid()) { | ||
145 | fprintf(stderr, "Error: %s is not owned by the current user\n", homeusercfg); | ||
146 | exit(1); | ||
147 | } | 222 | } |
148 | // preserve a read-only mount | ||
149 | struct statvfs vfs; | ||
150 | if (fstatvfs(fd, &vfs) == -1) | ||
151 | errExit("fstatvfs"); | ||
152 | if ((vfs.f_flag & MS_RDONLY) == MS_RDONLY) | ||
153 | fs_rdonly(RUN_PULSE_DIR); | ||
154 | // mount via the link in /proc/self/fd | ||
155 | char *proc; | ||
156 | if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1) | ||
157 | errExit("asprintf"); | ||
158 | if (mount(RUN_PULSE_DIR, proc, "none", MS_BIND, NULL) < 0) | ||
159 | errExit("mount pulseaudio"); | ||
160 | free(proc); | ||
161 | close(fd); | ||
162 | // check /proc/self/mountinfo to confirm the mount is ok | ||
163 | MountData *mptr = get_last_mount(); | ||
164 | if (strcmp(mptr->dir, homeusercfg) != 0 || strcmp(mptr->fstype, "tmpfs") != 0) | ||
165 | errLogExit("invalid pulseaudio mount"); | ||
166 | fs_logger2("tmpfs", homeusercfg); | ||
167 | |||
168 | char *p; | ||
169 | if (asprintf(&p, "%s/client.conf", homeusercfg) == -1) | ||
170 | errExit("asprintf"); | ||
171 | fs_logger2("create", p); | ||
172 | free(p); | ||
173 | 223 | ||
174 | free(pulsecfg); | 224 | free(pulsecfg); |
175 | free(homeusercfg); | 225 | free(homeusercfg); |