Determine if a symlink points outside the current directory tree. * This is considered "unsafe" because e.g. when mirroring somebody * else's machine it might allow them to establish a symlink to * /etc/passwd, and then read it through a web server. * * Returns 1 if unsafe, 0 if safe. * * Null symlinks and absolute symlinks are always unsafe. * * Basically here we are concerned with symli
| 1396 | * resulting in the link being transferred now becoming unsafe |
| 1397 | */ |
| 1398 | int unsafe_symlink(const char *dest, const char *src) |
| 1399 | { |
| 1400 | const char *name, *slash; |
| 1401 | int depth = 0; |
| 1402 | |
| 1403 | /* all absolute and null symlinks are unsafe */ |
| 1404 | if (!dest || !*dest || *dest == '/') |
| 1405 | return 1; |
| 1406 | |
| 1407 | // reject destinations with /../ in the name other than at the start of the name |
| 1408 | const char *dest2 = dest; |
| 1409 | while (strncmp(dest2, "../", 3) == 0) { |
| 1410 | dest2 += 3; |
| 1411 | while (*dest2 == '/') { |
| 1412 | // allow for ..//..///../foo |
| 1413 | dest2++; |
| 1414 | } |
| 1415 | } |
| 1416 | if (strstr(dest2, "/../")) |
| 1417 | return 1; |
| 1418 | |
| 1419 | // reject if the destination ends in /.. |
| 1420 | const size_t dlen = strlen(dest); |
| 1421 | if (dlen > 3 && strcmp(&dest[dlen-3], "/..") == 0) |
| 1422 | return 1; |
| 1423 | |
| 1424 | /* find out what our safety margin is */ |
| 1425 | for (name = src; (slash = strchr(name, '/')) != 0; name = slash+1) { |
| 1426 | /* ".." segment starts the count over. "." segment is ignored. */ |
| 1427 | if (*name == '.' && (name[1] == '/' || (name[1] == '.' && name[2] == '/'))) { |
| 1428 | if (name[1] == '.') |
| 1429 | depth = 0; |
| 1430 | } else |
| 1431 | depth++; |
| 1432 | while (slash[1] == '/') slash++; /* just in case src isn't clean */ |
| 1433 | } |
| 1434 | if (*name == '.' && name[1] == '.' && name[2] == '\0') |
| 1435 | depth = 0; |
| 1436 | |
| 1437 | for (name = dest; (slash = strchr(name, '/')) != 0; name = slash+1) { |
| 1438 | if (*name == '.' && (name[1] == '/' || (name[1] == '.' && name[2] == '/'))) { |
| 1439 | if (name[1] == '.') { |
| 1440 | /* if at any point we go outside the current directory |
| 1441 | then stop - it is unsafe */ |
| 1442 | if (--depth < 0) |
| 1443 | return 1; |
| 1444 | } |
| 1445 | } else |
| 1446 | depth++; |
| 1447 | while (slash[1] == '/') slash++; |
| 1448 | } |
| 1449 | if (*name == '.' && name[1] == '.' && name[2] == '\0') |
| 1450 | depth--; |
| 1451 | |
| 1452 | return depth < 0; |
| 1453 | } |
| 1454 | |
| 1455 | /* Return the date and time as a string. Some callers tweak returned buf. */ |
no outgoing calls
no test coverage detected