github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/cmd/snap-seccomp-blacklist/snap-seccomp-blacklist.c (about) 1 #include <errno.h> 2 #include <fcntl.h> 3 #include <limits.h> 4 #include <stdarg.h> 5 #include <stdbool.h> 6 #include <stdio.h> 7 #include <stdlib.h> 8 #include <sys/ioctl.h> 9 #include <sys/stat.h> 10 #include <sys/types.h> 11 #include <unistd.h> 12 13 #include <seccomp.h> 14 15 __attribute__((format(printf, 1, 2))) static void showerr(const char *fmt, ...); 16 17 static void showerr(const char *fmt, ...) { 18 va_list va; 19 va_start(va, fmt); 20 vfprintf(stderr, fmt, va); 21 fputc('\n', stderr); 22 va_end(va); 23 } 24 25 static int populate_filter(scmp_filter_ctx ctx, const uint32_t *arch_tags, size_t num_arch_tags) { 26 int sc_err; 27 28 /* If the native architecture is not one of the supported 64bit 29 * architectures listed in main in le_arch_tags and be_arch_tags, then 30 * remove it. 31 * 32 * Libseccomp automatically adds the native architecture to each new filter. 33 * If the native architecture is a 32bit-one then we will hit a bug in libseccomp 34 * and the generated BPF program is incorrect as described below. */ 35 uint32_t native_arch = seccomp_arch_native(); 36 bool remove_native_arch = true; 37 for (size_t i = 0; i < num_arch_tags; ++i) { 38 if (arch_tags[i] == native_arch) { 39 remove_native_arch = false; 40 break; 41 } 42 } 43 if (remove_native_arch) { 44 sc_err = seccomp_arch_remove(ctx, SCMP_ARCH_NATIVE); 45 if (sc_err < 0) { 46 showerr("cannot remove native architecture"); 47 return sc_err; 48 } 49 } 50 51 /* Add 64-bit architectures supported by snapd into the seccomp filter. 52 * 53 * The documentation of seccomp_arch_add() is confusing. It says that after 54 * this call any new rules will be added to this architecture. This is 55 * correct. It doesn't, however, explain that the rules will be multiplied 56 * and re-written as explained below. */ 57 for (size_t i = 0; i < num_arch_tags; ++i) { 58 uint32_t arch_tag = arch_tags[i]; 59 sc_err = seccomp_arch_add(ctx, arch_tag); 60 if (sc_err < 0 && sc_err != -EEXIST) { 61 showerr("cannot add architecture %x", arch_tag); 62 return sc_err; 63 } 64 } 65 66 /* When the rule set doesn't match one of the architectures above then the 67 * resulting action should be a "allow" rather than "kill". We don't add 68 * any of the 32bit architectures since there is no need for any extra 69 * filtering there. */ 70 sc_err = seccomp_attr_set(ctx, SCMP_FLTATR_ACT_BADARCH, SCMP_ACT_ALLOW); 71 if (sc_err < 0) { 72 showerr("cannot set action for unknown architectures"); 73 return sc_err; 74 } 75 76 /* Resolve the name of "ioctl" on this architecture. We are not using the 77 * system call number as available through the appropriate linux-specific 78 * header. This allows us to use a system call number that is not defined 79 * for the current architecture. This does not matter here, in this 80 * specific program, however it is more generic. In addition this is more 81 * in sync with the snap-seccomp program, which does the same for every 82 * system call. */ 83 int sys_ioctl_nr; 84 sys_ioctl_nr = seccomp_syscall_resolve_name("ioctl"); 85 if (sys_ioctl_nr == __NR_SCMP_ERROR) { 86 showerr("cannot resolve ioctl system call number"); 87 return -ESRCH; 88 } 89 90 /* All of the rules must be added for the native architecture (using native 91 * system call numbers). When the final program is generated the set of 92 * architectures added earlier will be used to determine the correct system 93 * call number for each architecture. 94 * 95 * In other words, arguments to scmp_rule_add() must always use native 96 * system call numbers. Translation for the correct architecture will be 97 * performed internally. This is not documented in libseccomp, but correct 98 * operation was confirmed using the pseudo-code program and the bpf_dbg 99 * tool from the kernel tools/bpf directory. 100 * 101 * NOTE: not using scmp_rule_add_exact as that was not doing anything 102 * at all (presumably due to having all the architectures defined). */ 103 104 const struct scmp_arg_cmp no_tty_inject = { 105 /* We learned that existing programs make legitimate requests with all 106 * bits set in the more significant 32bit word of the 64 bit double 107 * word. While this kernel behavior remains suspect and presumably 108 * undesired it is unlikely to change for backwards compatibility 109 * reasons. As such we cannot block all requests with high-bits set. 110 * 111 * When faced with ioctl(fd, request); refuse to proceed when 112 * request&0xffffffff == TIOCSTI. This specific way to encode the 113 * filter has the following important properties: 114 * 115 * - it blocks ioctl(fd, TIOCSTI, ptr). 116 * - it also blocks ioctl(fd, (1UL<<32) | TIOCSTI, ptr). 117 * - it doesn't block ioctl(fd, (1UL<<32) | (request not equal to TIOCSTI), ptr); */ 118 .arg = 1, 119 .op = SCMP_CMP_MASKED_EQ, 120 .datum_a = 0xffffffffUL, 121 .datum_b = TIOCSTI, 122 }; 123 sc_err = seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), sys_ioctl_nr, 1, no_tty_inject); 124 125 if (sc_err < 0) { 126 showerr("cannot add rule preventing the use high bits in ioctl"); 127 return sc_err; 128 } 129 return 0; 130 } 131 132 typedef struct arch_set { 133 const char *name; 134 const uint32_t *arch_tags; 135 size_t num_arch_tags; 136 } arch_set; 137 138 int main(int argc, char **argv) { 139 const uint32_t le_arch_tags[] = { 140 SCMP_ARCH_X86_64, 141 SCMP_ARCH_AARCH64, 142 SCMP_ARCH_PPC64LE, 143 SCMP_ARCH_S390X, 144 }; 145 const uint32_t be_arch_tags[] = { 146 SCMP_ARCH_S390X, 147 }; 148 const arch_set arch_sets[] = { 149 {"LE", le_arch_tags, sizeof le_arch_tags / sizeof *le_arch_tags}, 150 {"BE", be_arch_tags, sizeof be_arch_tags / sizeof *be_arch_tags}, 151 }; 152 int rc = -1; 153 154 for (size_t i = 0; i < sizeof arch_sets / sizeof *arch_sets; ++i) { 155 const arch_set *as = &arch_sets[i]; 156 int sc_err; 157 int fd = -1; 158 int fname_len; 159 char fname[PATH_MAX]; 160 161 scmp_filter_ctx ctx = NULL; 162 ctx = seccomp_init(SCMP_ACT_ALLOW); 163 if (ctx == NULL) { 164 showerr("cannot construct seccomp context"); 165 return -rc; 166 } 167 sc_err = populate_filter(ctx, as->arch_tags, as->num_arch_tags); 168 if (sc_err < 0) { 169 seccomp_release(ctx); 170 return -rc; 171 } 172 173 /* Save pseudo-code program */ 174 fname_len = snprintf(fname, sizeof fname, "%s-blacklist.pfc", as->name); 175 if (fname_len < 0 || fname_len >= sizeof fname) { 176 showerr("cannot format file name (%s)", as->name); 177 seccomp_release(ctx); 178 return -rc; 179 } 180 fd = open(fname, O_CREAT | O_TRUNC | O_WRONLY | O_NOFOLLOW, 0644); 181 if (fd < 0) { 182 showerr("cannot open file %s", fname); 183 seccomp_release(ctx); 184 return -rc; 185 } 186 sc_err = seccomp_export_pfc(ctx, fd); 187 if (sc_err < 0) { 188 showerr("cannot export PFC program %s", fname); 189 seccomp_release(ctx); 190 close(fd); 191 return -rc; 192 } 193 194 close(fd); 195 196 /* Save binary program. */ 197 fname_len = snprintf(fname, sizeof fname, "%s-blacklist.bpf", as->name); 198 if (fname_len < 0 || fname_len >= sizeof fname) { 199 showerr("cannot format file name (%s)", as->name); 200 seccomp_release(ctx); 201 return -rc; 202 } 203 fd = open(fname, O_CREAT | O_TRUNC | O_WRONLY | O_NOFOLLOW, 0644); 204 if (fd < 0) { 205 showerr("cannot open file %s", fname); 206 seccomp_release(ctx); 207 return -rc; 208 } 209 sc_err = seccomp_export_bpf(ctx, fd); 210 if (sc_err < 0) { 211 showerr("cannot export BPF program %s", fname); 212 seccomp_release(ctx); 213 close(fd); 214 return -rc; 215 } 216 217 close(fd); 218 seccomp_release(ctx); 219 } 220 rc = 0; 221 return -rc; 222 }