diff options
| author | Peter Marko <peter.marko@siemens.com> | 2025-08-10 15:22:18 +0200 |
|---|---|---|
| committer | Steve Sakoman <steve@sakoman.com> | 2025-08-15 09:06:40 -0700 |
| commit | ae3cd7bd8a33ca0a87c64fe14e2d27b9df6794e8 (patch) | |
| tree | adfce5181cfde70b6d25d6ff1181b42bba96d6e3 | |
| parent | 2dc7ee3894c0918e6718242f7f381a5a39b03fd6 (diff) | |
| download | poky-ae3cd7bd8a33ca0a87c64fe14e2d27b9df6794e8.tar.gz | |
dropbear: patch CVE-2025-47203
Pick patch per Debian security page [1].
[1] https://security-tracker.debian.org/tracker/CVE-2025-47203
(From OE-Core rev: b109c117e68bf44f623124ea1bb2468f3657665c)
Signed-off-by: Peter Marko <peter.marko@siemens.com>
Signed-off-by: Steve Sakoman <steve@sakoman.com>
| -rw-r--r-- | meta/recipes-core/dropbear/dropbear/CVE-2025-47203.patch | 373 | ||||
| -rw-r--r-- | meta/recipes-core/dropbear/dropbear_2024.86.bb | 1 |
2 files changed, 374 insertions, 0 deletions
diff --git a/meta/recipes-core/dropbear/dropbear/CVE-2025-47203.patch b/meta/recipes-core/dropbear/dropbear/CVE-2025-47203.patch new file mode 100644 index 0000000000..9ce0f10588 --- /dev/null +++ b/meta/recipes-core/dropbear/dropbear/CVE-2025-47203.patch | |||
| @@ -0,0 +1,373 @@ | |||
| 1 | From e5a0ef27c227f7ae69d9a9fec98a056494409b9b Mon Sep 17 00:00:00 2001 | ||
| 2 | From: Matt Johnston <matt@ucc.asn.au> | ||
| 3 | Date: Mon, 5 May 2025 23:14:19 +0800 | ||
| 4 | Subject: [PATCH] Execute multihop commands directly, no shell | ||
| 5 | |||
| 6 | This avoids problems with shell escaping if arguments contain special | ||
| 7 | characters. | ||
| 8 | |||
| 9 | CVE: CVE-2025-47203 | ||
| 10 | Upstream-Status: Backport [https://github.com/mkj/dropbear/commit/e5a0ef27c227f7ae69d9a9fec98a056494409b9b] | ||
| 11 | Signed-off-by: Peter Marko <peter.marko@siemens.com> | ||
| 12 | --- | ||
| 13 | src/cli-main.c | 59 +++++++++++++++++--------- | ||
| 14 | src/cli-runopts.c | 104 ++++++++++++++++++++++++++++------------------ | ||
| 15 | src/dbutil.c | 9 +++- | ||
| 16 | src/dbutil.h | 1 + | ||
| 17 | src/runopts.h | 5 +++ | ||
| 18 | 5 files changed, 117 insertions(+), 61 deletions(-) | ||
| 19 | |||
| 20 | diff --git a/src/cli-main.c b/src/cli-main.c | ||
| 21 | index 065fd76..2fafa88 100644 | ||
| 22 | --- a/src/cli-main.c | ||
| 23 | +++ b/src/cli-main.c | ||
| 24 | @@ -77,9 +77,8 @@ int main(int argc, char ** argv) { | ||
| 25 | } | ||
| 26 | |||
| 27 | #if DROPBEAR_CLI_PROXYCMD | ||
| 28 | - if (cli_opts.proxycmd) { | ||
| 29 | + if (cli_opts.proxycmd || cli_opts.proxyexec) { | ||
| 30 | cli_proxy_cmd(&sock_in, &sock_out, &proxy_cmd_pid); | ||
| 31 | - m_free(cli_opts.proxycmd); | ||
| 32 | if (signal(SIGINT, kill_proxy_sighandler) == SIG_ERR || | ||
| 33 | signal(SIGTERM, kill_proxy_sighandler) == SIG_ERR || | ||
| 34 | signal(SIGHUP, kill_proxy_sighandler) == SIG_ERR) { | ||
| 35 | @@ -101,7 +100,8 @@ int main(int argc, char ** argv) { | ||
| 36 | } | ||
| 37 | #endif /* DBMULTI stuff */ | ||
| 38 | |||
| 39 | -static void exec_proxy_cmd(const void *user_data_cmd) { | ||
| 40 | +#if DROPBEAR_CLI_PROXYCMD | ||
| 41 | +static void shell_proxy_cmd(const void *user_data_cmd) { | ||
| 42 | const char *cmd = user_data_cmd; | ||
| 43 | char *usershell; | ||
| 44 | |||
| 45 | @@ -110,41 +110,62 @@ static void exec_proxy_cmd(const void *user_data_cmd) { | ||
| 46 | dropbear_exit("Failed to run '%s'\n", cmd); | ||
| 47 | } | ||
| 48 | |||
| 49 | -#if DROPBEAR_CLI_PROXYCMD | ||
| 50 | +static void exec_proxy_cmd(const void *unused) { | ||
| 51 | + (void)unused; | ||
| 52 | + run_command(cli_opts.proxyexec[0], cli_opts.proxyexec, ses.maxfd); | ||
| 53 | + dropbear_exit("Failed to run '%s'\n", cli_opts.proxyexec[0]); | ||
| 54 | +} | ||
| 55 | + | ||
| 56 | static void cli_proxy_cmd(int *sock_in, int *sock_out, pid_t *pid_out) { | ||
| 57 | - char * ex_cmd = NULL; | ||
| 58 | - size_t ex_cmdlen; | ||
| 59 | + char * cmd_arg = NULL; | ||
| 60 | + void (*exec_fn)(const void *user_data) = NULL; | ||
| 61 | int ret; | ||
| 62 | |||
| 63 | + /* exactly one of cli_opts.proxycmd or cli_opts.proxyexec should be set */ | ||
| 64 | + | ||
| 65 | /* File descriptor "-j &3" */ | ||
| 66 | - if (*cli_opts.proxycmd == '&') { | ||
| 67 | + if (cli_opts.proxycmd && *cli_opts.proxycmd == '&') { | ||
| 68 | char *p = cli_opts.proxycmd + 1; | ||
| 69 | int sock = strtoul(p, &p, 10); | ||
| 70 | /* must be a single number, and not stdin/stdout/stderr */ | ||
| 71 | if (sock > 2 && sock < 1024 && *p == '\0') { | ||
| 72 | *sock_in = sock; | ||
| 73 | *sock_out = sock; | ||
| 74 | - return; | ||
| 75 | + goto cleanup; | ||
| 76 | } | ||
| 77 | } | ||
| 78 | |||
| 79 | - /* Normal proxycommand */ | ||
| 80 | + if (cli_opts.proxycmd) { | ||
| 81 | + /* Normal proxycommand */ | ||
| 82 | + size_t shell_cmdlen; | ||
| 83 | + /* So that spawn_command knows which shell to run */ | ||
| 84 | + fill_passwd(cli_opts.own_user); | ||
| 85 | |||
| 86 | - /* So that spawn_command knows which shell to run */ | ||
| 87 | - fill_passwd(cli_opts.own_user); | ||
| 88 | + shell_cmdlen = strlen(cli_opts.proxycmd) + 6; /* "exec " + command + '\0' */ | ||
| 89 | + cmd_arg = m_malloc(shell_cmdlen); | ||
| 90 | + snprintf(cmd_arg, shell_cmdlen, "exec %s", cli_opts.proxycmd); | ||
| 91 | + exec_fn = shell_proxy_cmd; | ||
| 92 | + } else { | ||
| 93 | + /* No shell */ | ||
| 94 | + exec_fn = exec_proxy_cmd; | ||
| 95 | + } | ||
| 96 | |||
| 97 | - ex_cmdlen = strlen(cli_opts.proxycmd) + 6; /* "exec " + command + '\0' */ | ||
| 98 | - ex_cmd = m_malloc(ex_cmdlen); | ||
| 99 | - snprintf(ex_cmd, ex_cmdlen, "exec %s", cli_opts.proxycmd); | ||
| 100 | - | ||
| 101 | - ret = spawn_command(exec_proxy_cmd, ex_cmd, | ||
| 102 | - sock_out, sock_in, NULL, pid_out); | ||
| 103 | - DEBUG1(("cmd: %s pid=%d", ex_cmd,*pid_out)) | ||
| 104 | - m_free(ex_cmd); | ||
| 105 | + ret = spawn_command(exec_fn, cmd_arg, sock_out, sock_in, NULL, pid_out); | ||
| 106 | if (ret == DROPBEAR_FAILURE) { | ||
| 107 | dropbear_exit("Failed running proxy command"); | ||
| 108 | *sock_in = *sock_out = -1; | ||
| 109 | } | ||
| 110 | + | ||
| 111 | +cleanup: | ||
| 112 | + m_free(cli_opts.proxycmd); | ||
| 113 | + m_free(cmd_arg); | ||
| 114 | + if (cli_opts.proxyexec) { | ||
| 115 | + char **a = NULL; | ||
| 116 | + for (a = cli_opts.proxyexec; *a; a++) { | ||
| 117 | + m_free_direct(*a); | ||
| 118 | + } | ||
| 119 | + m_free(cli_opts.proxyexec); | ||
| 120 | + } | ||
| 121 | } | ||
| 122 | |||
| 123 | static void kill_proxy_sighandler(int UNUSED(signo)) { | ||
| 124 | diff --git a/src/cli-runopts.c b/src/cli-runopts.c | ||
| 125 | index b664293..a21b7a2 100644 | ||
| 126 | --- a/src/cli-runopts.c | ||
| 127 | +++ b/src/cli-runopts.c | ||
| 128 | @@ -556,62 +556,88 @@ void loadidentityfile(const char* filename, int warnfail) { | ||
| 129 | |||
| 130 | /* Fill out -i, -y, -W options that make sense for all | ||
| 131 | * the intermediate processes */ | ||
| 132 | -static char* multihop_passthrough_args(void) { | ||
| 133 | - char *args = NULL; | ||
| 134 | - unsigned int len, total; | ||
| 135 | +static char** multihop_args(const char* argv0, const char* prior_hops) { | ||
| 136 | + /* null terminated array */ | ||
| 137 | + char **args = NULL; | ||
| 138 | + size_t max_args = 14, pos = 0, len; | ||
| 139 | #if DROPBEAR_CLI_PUBKEY_AUTH | ||
| 140 | m_list_elem *iter; | ||
| 141 | #endif | ||
| 142 | - /* Sufficient space for non-string args */ | ||
| 143 | - len = 100; | ||
| 144 | |||
| 145 | - /* String arguments have arbitrary length, so determine space required */ | ||
| 146 | - if (cli_opts.proxycmd) { | ||
| 147 | - len += strlen(cli_opts.proxycmd); | ||
| 148 | - } | ||
| 149 | #if DROPBEAR_CLI_PUBKEY_AUTH | ||
| 150 | for (iter = cli_opts.privkeys->first; iter; iter = iter->next) | ||
| 151 | { | ||
| 152 | - sign_key * key = (sign_key*)iter->item; | ||
| 153 | - len += 4 + strlen(key->filename); | ||
| 154 | + /* "-i file" for each */ | ||
| 155 | + max_args += 2; | ||
| 156 | } | ||
| 157 | #endif | ||
| 158 | |||
| 159 | - args = m_malloc(len); | ||
| 160 | - total = 0; | ||
| 161 | + args = m_malloc(sizeof(char*) * max_args); | ||
| 162 | + pos = 0; | ||
| 163 | |||
| 164 | - /* Create new argument string */ | ||
| 165 | + args[pos] = m_strdup(argv0); | ||
| 166 | + pos++; | ||
| 167 | |||
| 168 | if (cli_opts.quiet) { | ||
| 169 | - total += m_snprintf(args+total, len-total, "-q "); | ||
| 170 | + args[pos] = m_strdup("-q"); | ||
| 171 | + pos++; | ||
| 172 | } | ||
| 173 | |||
| 174 | if (cli_opts.no_hostkey_check) { | ||
| 175 | - total += m_snprintf(args+total, len-total, "-y -y "); | ||
| 176 | + args[pos] = m_strdup("-y"); | ||
| 177 | + pos++; | ||
| 178 | + args[pos] = m_strdup("-y"); | ||
| 179 | + pos++; | ||
| 180 | } else if (cli_opts.always_accept_key) { | ||
| 181 | - total += m_snprintf(args+total, len-total, "-y "); | ||
| 182 | + args[pos] = m_strdup("-y"); | ||
| 183 | + pos++; | ||
| 184 | } | ||
| 185 | |||
| 186 | if (cli_opts.batch_mode) { | ||
| 187 | - total += m_snprintf(args+total, len-total, "-o BatchMode=yes "); | ||
| 188 | + args[pos] = m_strdup("-o"); | ||
| 189 | + pos++; | ||
| 190 | + args[pos] = m_strdup("BatchMode=yes"); | ||
| 191 | + pos++; | ||
| 192 | } | ||
| 193 | |||
| 194 | if (cli_opts.proxycmd) { | ||
| 195 | - total += m_snprintf(args+total, len-total, "-J '%s' ", cli_opts.proxycmd); | ||
| 196 | + args[pos] = m_strdup("-J"); | ||
| 197 | + pos++; | ||
| 198 | + args[pos] = m_strdup(cli_opts.proxycmd); | ||
| 199 | + pos++; | ||
| 200 | } | ||
| 201 | |||
| 202 | if (opts.recv_window != DEFAULT_RECV_WINDOW) { | ||
| 203 | - total += m_snprintf(args+total, len-total, "-W %u ", opts.recv_window); | ||
| 204 | + args[pos] = m_strdup("-W"); | ||
| 205 | + pos++; | ||
| 206 | + args[pos] = m_malloc(11); | ||
| 207 | + m_snprintf(args[pos], 11, "%u", opts.recv_window); | ||
| 208 | + pos++; | ||
| 209 | } | ||
| 210 | |||
| 211 | #if DROPBEAR_CLI_PUBKEY_AUTH | ||
| 212 | for (iter = cli_opts.privkeys->first; iter; iter = iter->next) | ||
| 213 | { | ||
| 214 | sign_key * key = (sign_key*)iter->item; | ||
| 215 | - total += m_snprintf(args+total, len-total, "-i %s ", key->filename); | ||
| 216 | + args[pos] = m_strdup("-i"); | ||
| 217 | + pos++; | ||
| 218 | + args[pos] = m_strdup(key->filename); | ||
| 219 | + pos++; | ||
| 220 | } | ||
| 221 | #endif /* DROPBEAR_CLI_PUBKEY_AUTH */ | ||
| 222 | |||
| 223 | + /* last hop */ | ||
| 224 | + args[pos] = m_strdup("-B"); | ||
| 225 | + pos++; | ||
| 226 | + len = strlen(cli_opts.remotehost) + strlen(cli_opts.remoteport) + 2; | ||
| 227 | + args[pos] = m_malloc(len); | ||
| 228 | + snprintf(args[pos], len, "%s:%s", cli_opts.remotehost, cli_opts.remoteport); | ||
| 229 | + pos++; | ||
| 230 | + | ||
| 231 | + /* hostnames of prior hops */ | ||
| 232 | + args[pos] = m_strdup(prior_hops); | ||
| 233 | + pos++; | ||
| 234 | + | ||
| 235 | return args; | ||
| 236 | } | ||
| 237 | |||
| 238 | @@ -626,7 +652,7 @@ static char* multihop_passthrough_args(void) { | ||
| 239 | * etc for as many hosts as we want. | ||
| 240 | * | ||
| 241 | * Note that "-J" arguments aren't actually used, instead | ||
| 242 | - * below sets cli_opts.proxycmd directly. | ||
| 243 | + * below sets cli_opts.proxyexec directly. | ||
| 244 | * | ||
| 245 | * Ports for hosts can be specified as host/port. | ||
| 246 | */ | ||
| 247 | @@ -634,7 +660,7 @@ static void parse_multihop_hostname(const char* orighostarg, const char* argv0) | ||
| 248 | char *userhostarg = NULL; | ||
| 249 | char *hostbuf = NULL; | ||
| 250 | char *last_hop = NULL; | ||
| 251 | - char *remainder = NULL; | ||
| 252 | + char *prior_hops = NULL; | ||
| 253 | |||
| 254 | /* both scp and rsync parse a user@host argument | ||
| 255 | * and turn it into "-l user host". This breaks | ||
| 256 | @@ -652,6 +678,8 @@ static void parse_multihop_hostname(const char* orighostarg, const char* argv0) | ||
| 257 | } | ||
| 258 | userhostarg = hostbuf; | ||
| 259 | |||
| 260 | + /* Split off any last hostname and use that as remotehost/remoteport. | ||
| 261 | + * That is used for authorized_keys checking etc */ | ||
| 262 | last_hop = strrchr(userhostarg, ','); | ||
| 263 | if (last_hop) { | ||
| 264 | if (last_hop == userhostarg) { | ||
| 265 | @@ -659,32 +687,28 @@ static void parse_multihop_hostname(const char* orighostarg, const char* argv0) | ||
| 266 | } | ||
| 267 | *last_hop = '\0'; | ||
| 268 | last_hop++; | ||
| 269 | - remainder = userhostarg; | ||
| 270 | + prior_hops = userhostarg; | ||
| 271 | userhostarg = last_hop; | ||
| 272 | } | ||
| 273 | |||
| 274 | + /* Update cli_opts.remotehost and cli_opts.remoteport */ | ||
| 275 | parse_hostname(userhostarg); | ||
| 276 | |||
| 277 | - if (last_hop) { | ||
| 278 | - /* Set up the proxycmd */ | ||
| 279 | - unsigned int cmd_len = 0; | ||
| 280 | - char *passthrough_args = multihop_passthrough_args(); | ||
| 281 | - cmd_len = strlen(argv0) + strlen(remainder) | ||
| 282 | - + strlen(cli_opts.remotehost) + strlen(cli_opts.remoteport) | ||
| 283 | - + strlen(passthrough_args) | ||
| 284 | - + 30; | ||
| 285 | - /* replace proxycmd. old -J arguments have been copied | ||
| 286 | - to passthrough_args */ | ||
| 287 | - cli_opts.proxycmd = m_realloc(cli_opts.proxycmd, cmd_len); | ||
| 288 | - m_snprintf(cli_opts.proxycmd, cmd_len, "%s -B %s:%s %s %s", | ||
| 289 | - argv0, cli_opts.remotehost, cli_opts.remoteport, | ||
| 290 | - passthrough_args, remainder); | ||
| 291 | + /* Construct any multihop proxy command. Use proxyexec to | ||
| 292 | + * avoid worrying about shell escaping. */ | ||
| 293 | + if (prior_hops) { | ||
| 294 | + cli_opts.proxyexec = multihop_args(argv0, prior_hops); | ||
| 295 | + /* Any -J argument has been copied to proxyexec */ | ||
| 296 | + if (cli_opts.proxycmd) { | ||
| 297 | + m_free(cli_opts.proxycmd); | ||
| 298 | + } | ||
| 299 | + | ||
| 300 | #ifndef DISABLE_ZLIB | ||
| 301 | - /* The stream will be incompressible since it's encrypted. */ | ||
| 302 | + /* This outer stream will be incompressible since it's encrypted. */ | ||
| 303 | opts.compress_mode = DROPBEAR_COMPRESS_OFF; | ||
| 304 | #endif | ||
| 305 | - m_free(passthrough_args); | ||
| 306 | } | ||
| 307 | + | ||
| 308 | m_free(hostbuf); | ||
| 309 | } | ||
| 310 | #endif /* DROPBEAR_CLI_MULTIHOP */ | ||
| 311 | diff --git a/src/dbutil.c b/src/dbutil.c | ||
| 312 | index 2b44921..a70025e 100644 | ||
| 313 | --- a/src/dbutil.c | ||
| 314 | +++ b/src/dbutil.c | ||
| 315 | @@ -371,7 +371,6 @@ int spawn_command(void(*exec_fn)(const void *user_data), const void *exec_data, | ||
| 316 | void run_shell_command(const char* cmd, unsigned int maxfd, char* usershell) { | ||
| 317 | char * argv[4]; | ||
| 318 | char * baseshell = NULL; | ||
| 319 | - unsigned int i; | ||
| 320 | |||
| 321 | baseshell = basename(usershell); | ||
| 322 | |||
| 323 | @@ -393,6 +392,12 @@ void run_shell_command(const char* cmd, unsigned int maxfd, char* usershell) { | ||
| 324 | argv[1] = NULL; | ||
| 325 | } | ||
| 326 | |||
| 327 | + run_command(usershell, argv, maxfd); | ||
| 328 | +} | ||
| 329 | + | ||
| 330 | +void run_command(const char* argv0, char** args, unsigned int maxfd) { | ||
| 331 | + unsigned int i; | ||
| 332 | + | ||
| 333 | /* Re-enable SIGPIPE for the executed process */ | ||
| 334 | if (signal(SIGPIPE, SIG_DFL) == SIG_ERR) { | ||
| 335 | dropbear_exit("signal() error"); | ||
| 336 | @@ -404,7 +409,7 @@ void run_shell_command(const char* cmd, unsigned int maxfd, char* usershell) { | ||
| 337 | m_close(i); | ||
| 338 | } | ||
| 339 | |||
| 340 | - execv(usershell, argv); | ||
| 341 | + execv(argv0, args); | ||
| 342 | } | ||
| 343 | |||
| 344 | #if DEBUG_TRACE | ||
| 345 | diff --git a/src/dbutil.h b/src/dbutil.h | ||
| 346 | index 05fc50c..bfbed73 100644 | ||
| 347 | --- a/src/dbutil.h | ||
| 348 | +++ b/src/dbutil.h | ||
| 349 | @@ -63,6 +63,7 @@ char * stripcontrol(const char * text); | ||
| 350 | int spawn_command(void(*exec_fn)(const void *user_data), const void *exec_data, | ||
| 351 | int *writefd, int *readfd, int *errfd, pid_t *pid); | ||
| 352 | void run_shell_command(const char* cmd, unsigned int maxfd, char* usershell); | ||
| 353 | +void run_command(const char* argv0, char** args, unsigned int maxfd); | ||
| 354 | #if ENABLE_CONNECT_UNIX | ||
| 355 | int connect_unix(const char* addr); | ||
| 356 | #endif | ||
| 357 | diff --git a/src/runopts.h b/src/runopts.h | ||
| 358 | index c4061a0..f255882 100644 | ||
| 359 | --- a/src/runopts.h | ||
| 360 | +++ b/src/runopts.h | ||
| 361 | @@ -197,7 +197,12 @@ typedef struct cli_runopts { | ||
| 362 | unsigned int netcat_port; | ||
| 363 | #endif | ||
| 364 | #if DROPBEAR_CLI_PROXYCMD | ||
| 365 | + /* A proxy command to run via the user's shell */ | ||
| 366 | char *proxycmd; | ||
| 367 | +#endif | ||
| 368 | +#if DROPBEAR_CLI_MULTIHOP | ||
| 369 | + /* Similar to proxycmd, but is arguments for execve(), not shell */ | ||
| 370 | + char **proxyexec; | ||
| 371 | #endif | ||
| 372 | const char *bind_arg; | ||
| 373 | char *bind_address; | ||
diff --git a/meta/recipes-core/dropbear/dropbear_2024.86.bb b/meta/recipes-core/dropbear/dropbear_2024.86.bb index be246a0ccd..10b7cb5c03 100644 --- a/meta/recipes-core/dropbear/dropbear_2024.86.bb +++ b/meta/recipes-core/dropbear/dropbear_2024.86.bb | |||
| @@ -21,6 +21,7 @@ SRC_URI = "http://matt.ucc.asn.au/dropbear/releases/dropbear-${PV}.tar.bz2 \ | |||
| 21 | file://dropbear.default \ | 21 | file://dropbear.default \ |
| 22 | ${@bb.utils.contains('DISTRO_FEATURES', 'pam', '${PAM_SRC_URI}', '', d)} \ | 22 | ${@bb.utils.contains('DISTRO_FEATURES', 'pam', '${PAM_SRC_URI}', '', d)} \ |
| 23 | ${@bb.utils.contains('PACKAGECONFIG', 'disable-weak-ciphers', 'file://dropbear-disable-weak-ciphers.patch', '', d)} \ | 23 | ${@bb.utils.contains('PACKAGECONFIG', 'disable-weak-ciphers', 'file://dropbear-disable-weak-ciphers.patch', '', d)} \ |
| 24 | file://CVE-2025-47203.patch \ | ||
| 24 | " | 25 | " |
| 25 | 26 | ||
| 26 | SRC_URI[sha256sum] = "e78936dffc395f2e0db099321d6be659190966b99712b55c530dd0a1822e0a5e" | 27 | SRC_URI[sha256sum] = "e78936dffc395f2e0db099321d6be659190966b99712b55c530dd0a1822e0a5e" |
