cybersecurity · intermediate · ~15 min

Reject when O_NOFOLLOW would have refused

Encode the symlink + traversal refusal rule.

Challenge

Encode the open-path refusal policy that models openat(..., O_NOFOLLOW) plus a path-traversal pre-check. The two prior checks have already run; this function is the AND combinator that joins them.

Task

Implement int can_open_path_safely(const char *path, int last_is_symlink, int has_traversal) that returns 1 (safe to open) only when the path is non-empty and both checks pass.

Return 1 only if ALL hold:

  • path is non-NULL and non-empty;
  • last_is_symlink == 0 (the final component is not a symlink);
  • has_traversal == 0 (no .. segments).

Otherwise return 0 (refused).

Input

  • path: the candidate path string (or NULL/empty).
  • last_is_symlink: 1 if the final component is a symlink (as O_NOFOLLOW would report via ELOOP), else 0.
  • has_traversal: 1 if a .. segment was detected, else 0.

Output

Returns int: 1 if safe to open, 0 if refused.

Example

can_open_path_safely("/var/data/file", 0, 0)             ->   1
can_open_path_safely("/var/data/file", 1, 0)             ->   0   (symlink)
can_open_path_safely("/var/data/../etc/passwd", 0, 1)    ->   0   (traversal)
can_open_path_safely("/x", 1, 1)                         ->   0
can_open_path_safely(NULL, 0, 0)                         ->   0

Edge cases

  • NULL or empty path is refused.
  • Either flag being set refuses the open.

Rules

  • Pure boolean logic — the actual symlink/traversal detection happens in other layers.

Why this matters

openat(dirfd, name, O_NOFOLLOW) returns ELOOP when the path is a symlink. Encoding that in a small policy lets you reason about the defence without a real filesystem.

Input format

A path string plus two flags: last_is_symlink and has_traversal.

Output format

An int: 1 if safe to open, 0 if refused.

Constraints

Refuse on NULL/empty path or either flag set; pure boolean logic.

Starter code

int can_open_path_safely(const char *path, int last_is_symlink, int has_traversal) { /* TODO */ (void)path; (void)last_is_symlink; (void)has_traversal; return 0; }

Common mistakes

Returning 1 even when traversal is detected.

Edge cases to handle

NULL path. Both flags set.

Complexity

O(1).

Background lessons

Up next

Solve this exercise in the browser editor — compile and run against the test harness, no setup required.