(root)/
strace-6.5/
src/
pathtrace.c
       1  /*
       2   * Copyright (c) 2011 Comtrol Corp.
       3   * Copyright (c) 2011-2023 The strace developers.
       4   * All rights reserved.
       5   *
       6   * SPDX-License-Identifier: LGPL-2.1-or-later
       7   *
       8   */
       9  
      10  #include "defs.h"
      11  #include <limits.h>
      12  #include <poll.h>
      13  #include <sys/stat.h>
      14  #include <sys/types.h>
      15  #include <unistd.h>
      16  
      17  #include "largefile_wrappers.h"
      18  #include "number_set.h"
      19  #include "sen.h"
      20  #include "xstring.h"
      21  
      22  struct path_set global_path_set;
      23  
      24  /*
      25   * Return true if specified path matches one that we're tracing.
      26   */
      27  static bool
      28  pathmatch(const char *path, struct path_set *set)
      29  {
      30  	if (!set)
      31  		return false;
      32  
      33  	for (unsigned int i = 0; i < set->num_selected; ++i) {
      34  		if (strcmp(path, set->paths_selected[i].path) == 0)
      35  			return true;
      36  	}
      37  	return false;
      38  }
      39  
      40  /*
      41   * Return true if specified path (in user-space) matches.
      42   */
      43  static bool
      44  upathmatch(struct tcb *const tcp, const kernel_ulong_t upath,
      45  	   struct path_set *set)
      46  {
      47  	if (!set)
      48  		return false;
      49  
      50  	char path[PATH_MAX + 1];
      51  
      52  	return umovestr(tcp, upath, sizeof(path), path) > 0 &&
      53  		pathmatch(path, set);
      54  }
      55  
      56  /*
      57   * Return true if specified fd maps to a path we're tracing.
      58   */
      59  static bool
      60  fdmatch(struct tcb *tcp, int fd, struct path_set *set, struct number_set *fdset)
      61  {
      62  	if (fdset && fd >= 0 && is_number_in_set(fd, fdset))
      63  		return true;
      64  	if (!set)
      65  		return false;
      66  
      67  	char path[PATH_MAX + 1];
      68  	int n = getfdpath(tcp, fd, path, sizeof(path));
      69  
      70  	return n >= 0 && pathmatch(path, set);
      71  }
      72  
      73  /*
      74   * Add a path to the set we're tracing.
      75   * Specifying NULL will delete all paths.
      76   */
      77  static void
      78  storepath(const char *path, struct path_set *set)
      79  {
      80  	if (pathmatch(path, set))
      81  		return; /* already in table */
      82  
      83  	if (set->num_selected >= set->size)
      84  		set->paths_selected =
      85  			xgrowarray(set->paths_selected, &set->size,
      86  				   sizeof(set->paths_selected[0]));
      87  
      88  	set->paths_selected[set->num_selected++].path = path;
      89  }
      90  
      91  int
      92  get_proc_pid_fd_path(int proc_pid, int fd, char *buf, unsigned bufsize,
      93  		     bool *deleted)
      94  {
      95  	char linkpath[sizeof("/proc/%u/fd/%u") + 2 * sizeof(int)*3];
      96  	ssize_t n;
      97  
      98  	if (fd < 0)
      99  		return -1;
     100  
     101  	xsprintf(linkpath, "/proc/%u/fd/%u", proc_pid, fd);
     102  	n = readlink(linkpath, buf, bufsize - 1);
     103  	if (n < 0)
     104  		goto end;
     105  
     106  	/*
     107  	 * NB: if buf is too small, readlink doesn't fail,
     108  	 * it returns truncated result (IOW: n == bufsize - 1).
     109  	 */
     110  	buf[n] = '\0';
     111  	if (deleted)
     112  		*deleted = false;
     113  
     114  	/*
     115  	 * Try to figure out if the kernel has appended " (deleted)"
     116  	 * to the end of a potentially unlinked path and set deleted
     117  	 * if it is the case.
     118  	 */
     119  	static const char del_sfx[] = " (deleted)";
     120  	if ((size_t) n <= sizeof(del_sfx))
     121  		goto end;
     122  
     123  	char *del = buf + n + 1 - sizeof(del_sfx);
     124  
     125  	if (memcmp(del, del_sfx, sizeof(del_sfx)))
     126  		goto end;
     127  
     128  	strace_stat_t st_link;
     129  	strace_stat_t st_path;
     130  	int rc = stat_file(linkpath, &st_link);
     131  
     132  	if (rc)
     133  		goto end;
     134  
     135  	rc = lstat_file(buf, &st_path);
     136  
     137  	if (rc ||
     138  	    (st_link.st_ino != st_path.st_ino) ||
     139  	    (st_link.st_dev != st_path.st_dev)) {
     140  		*del = '\0';
     141  		n = del - buf + 1;
     142  		if (deleted)
     143  			*deleted = true;
     144  	}
     145  
     146  end:
     147  	return n;
     148  }
     149  
     150  /*
     151   * Get path associated with fd of a process with pid.
     152   */
     153  int
     154  getfdpath_pid(pid_t pid, int fd, char *buf, unsigned bufsize, bool *deleted)
     155  {
     156  	if (fd < 0)
     157  		return -1;
     158  
     159  	int proc_pid = get_proc_pid(pid);
     160  	if (!proc_pid)
     161  		return -1;
     162  
     163  	return get_proc_pid_fd_path(proc_pid, fd, buf, bufsize, deleted);
     164  }
     165  
     166  /*
     167   * Add a path to the set we're tracing.  Also add the canonicalized
     168   * version of the path.  Specifying NULL will delete all paths.
     169   */
     170  void
     171  pathtrace_select_set(const char *path, struct path_set *set)
     172  {
     173  	char *rpath;
     174  
     175  	storepath(path, set);
     176  
     177  	rpath = realpath(path, NULL);
     178  
     179  	if (rpath == NULL)
     180  		return;
     181  
     182  	/* if realpath and specified path are same, we're done */
     183  	if (strcmp(path, rpath) == 0) {
     184  		free(rpath);
     185  		return;
     186  	}
     187  
     188  	if (!is_number_in_set(QUIET_PATH_RESOLVE, quiet_set)) {
     189  		char *path_quoted = xmalloc(strlen(path) * 4 + 4);
     190  		char *rpath_quoted = xmalloc(strlen(rpath) * 4 + 4);
     191  
     192  		string_quote(path, path_quoted, strlen(path) + 1,
     193  			     QUOTE_0_TERMINATED, NULL);
     194  		string_quote(rpath, rpath_quoted, strlen(rpath) + 1,
     195  			     QUOTE_0_TERMINATED, NULL);
     196  
     197  		error_msg("Requested path %s resolved into %s",
     198  			  path_quoted, rpath_quoted);
     199  
     200  		free(path_quoted);
     201  		free(rpath_quoted);
     202  	}
     203  	storepath(rpath, set);
     204  }
     205  
     206  static bool
     207  match_xselect_args(struct tcb *tcp, const kernel_ulong_t *args,
     208  		   struct path_set *set, struct number_set *fdset)
     209  {
     210  	/* Kernel truncates arg[0] to int, we do the same. */
     211  	int nfds = (int) args[0];
     212  	/* Kernel rejects negative nfds, so we don't parse it either. */
     213  	if (nfds <= 0)
     214  		return false;
     215  	/* Beware of select(2^31-1, NULL, NULL, NULL) and similar... */
     216  	if (nfds > 1024*1024)
     217  		nfds = 1024*1024;
     218  	unsigned int fdsize = (((nfds + 7) / 8) + current_wordsize-1) & -current_wordsize;
     219  	fd_set *fds = xmalloc(fdsize);
     220  
     221  	for (unsigned int i = 1; i <= 3; ++i) {
     222  		if (args[i] == 0)
     223  			continue;
     224  		if (umoven(tcp, args[i], fdsize, fds) < 0)
     225  			continue;
     226  		for (int j = 0;; ++j) {
     227  			j = next_set_bit(fds, j, nfds);
     228  			if (j < 0)
     229  				break;
     230  			if (fdmatch(tcp, j, set, fdset)) {
     231  				free(fds);
     232  				return true;
     233  			}
     234  		}
     235  	}
     236  
     237  	free(fds);
     238  	return false;
     239  }
     240  
     241  /*
     242   * Return true if syscall accesses a selected path
     243   * (or if no paths have been specified for tracing).
     244   */
     245  bool
     246  pathtrace_match_set(struct tcb *tcp, struct path_set *set,
     247  		    struct number_set *fdset)
     248  {
     249  	const struct_sysent *s;
     250  
     251  	s = tcp_sysent(tcp);
     252  
     253  	if (!(s->sys_flags & (TRACE_FILE | TRACE_DESC | TRACE_NETWORK)))
     254  		return false;
     255  
     256  	/*
     257  	 * Check for special cases where we need to do something
     258  	 * other than test arg[0].
     259  	 */
     260  
     261  	switch (s->sen) {
     262  	case SEN_dup2:
     263  	case SEN_dup3:
     264  	case SEN_kexec_file_load:
     265  	case SEN_sendfile:
     266  	case SEN_sendfile64:
     267  	case SEN_tee:
     268  		/* fd, fd */
     269  		return fdmatch(tcp, tcp->u_arg[0], set, fdset) ||
     270  			fdmatch(tcp, tcp->u_arg[1], set, fdset);
     271  
     272  	case SEN_execveat:
     273  	case SEN_faccessat:
     274  	case SEN_faccessat2:
     275  	case SEN_fchmodat:
     276  	case SEN_fchmodat2:
     277  	case SEN_fchownat:
     278  	case SEN_fspick:
     279  	case SEN_fstatat64:
     280  	case SEN_futimesat:
     281  	case SEN_inotify_add_watch:
     282  	case SEN_mkdirat:
     283  	case SEN_mknodat:
     284  	case SEN_mount_setattr:
     285  	case SEN_name_to_handle_at:
     286  	case SEN_newfstatat:
     287  	case SEN_open_tree:
     288  	case SEN_openat:
     289  	case SEN_openat2:
     290  	case SEN_readlinkat:
     291  	case SEN_statx:
     292  	case SEN_unlinkat:
     293  	case SEN_utimensat_time32:
     294  	case SEN_utimensat_time64:
     295  		/* fd, path */
     296  		return fdmatch(tcp, tcp->u_arg[0], set, fdset) ||
     297  			upathmatch(tcp, tcp->u_arg[1], set);
     298  
     299  	case SEN_link:
     300  	case SEN_mount:
     301  	case SEN_pivotroot:
     302  		/* path, path */
     303  		return upathmatch(tcp, tcp->u_arg[0], set) ||
     304  			upathmatch(tcp, tcp->u_arg[1], set);
     305  
     306  	case SEN_quotactl:
     307  	case SEN_symlink:
     308  		/* x, path */
     309  		return upathmatch(tcp, tcp->u_arg[1], set);
     310  
     311  	case SEN_linkat:
     312  	case SEN_move_mount:
     313  	case SEN_renameat2:
     314  	case SEN_renameat:
     315  		/* fd, path, fd, path */
     316  		return fdmatch(tcp, tcp->u_arg[0], set, fdset) ||
     317  			fdmatch(tcp, tcp->u_arg[2], set, fdset) ||
     318  			upathmatch(tcp, tcp->u_arg[1], set) ||
     319  			upathmatch(tcp, tcp->u_arg[3], set);
     320  
     321  #if HAVE_ARCH_OLD_MMAP
     322  	case SEN_old_mmap:
     323  # if HAVE_ARCH_OLD_MMAP_PGOFF
     324  	case SEN_old_mmap_pgoff:
     325  # endif
     326  	{
     327  		kernel_ulong_t *args =
     328  			fetch_indirect_syscall_args(tcp, tcp->u_arg[0], 6);
     329  
     330  		return args && fdmatch(tcp, args[4], set, fdset);
     331  	}
     332  #endif /* HAVE_ARCH_OLD_MMAP */
     333  
     334  	case SEN_mmap:
     335  	case SEN_mmap_4koff:
     336  	case SEN_mmap_pgoff:
     337  	case SEN_ARCH_mmap:
     338  		/* x, x, x, x, fd */
     339  		return fdmatch(tcp, tcp->u_arg[4], set, fdset);
     340  
     341  	case SEN_symlinkat:
     342  		/* x, fd, path */
     343  		return fdmatch(tcp, tcp->u_arg[1], set, fdset) ||
     344  			upathmatch(tcp, tcp->u_arg[2], set);
     345  
     346  	case SEN_copy_file_range:
     347  	case SEN_splice:
     348  		/* fd, x, fd, x, x, x */
     349  		return fdmatch(tcp, tcp->u_arg[0], set, fdset) ||
     350  			fdmatch(tcp, tcp->u_arg[2], set, fdset);
     351  
     352  	case SEN_epoll_ctl:
     353  		/* x, x, fd, x */
     354  		return fdmatch(tcp, tcp->u_arg[2], set, fdset);
     355  
     356  
     357  	case SEN_fanotify_mark:
     358  	{
     359  		/* x, x, mask (64 bit), fd, path */
     360  		unsigned long long mask = 0;
     361  		unsigned int argn = getllval(tcp, &mask, 2);
     362  		return fdmatch(tcp, tcp->u_arg[argn], set, fdset) ||
     363  			upathmatch(tcp, tcp->u_arg[argn + 1], set);
     364  	}
     365  #if HAVE_ARCH_OLD_SELECT
     366  	case SEN_oldselect:
     367  	{
     368  		kernel_ulong_t *args =
     369  			fetch_indirect_syscall_args(tcp, tcp->u_arg[0], 5);
     370  
     371  		return args && match_xselect_args(tcp, args, set, fdset);
     372  	}
     373  #endif
     374  	case SEN_pselect6_time32:
     375  	case SEN_pselect6_time64:
     376  	case SEN_select:
     377  		return match_xselect_args(tcp, tcp->u_arg, set, fdset);
     378  	case SEN_poll_time32:
     379  	case SEN_poll_time64:
     380  	case SEN_ppoll_time32:
     381  	case SEN_ppoll_time64:
     382  	{
     383  		struct pollfd fds;
     384  
     385  		const kernel_ulong_t start = tcp->u_arg[0];
     386  		unsigned int nfds = tcp->u_arg[1];
     387  		if (nfds > 1024 * 1024)
     388  			nfds = 1024 * 1024;
     389  		const kernel_ulong_t end = start + sizeof(fds) * nfds;
     390  
     391  		if (nfds == 0 || end < start)
     392  			return false;
     393  
     394  		for (kernel_ulong_t cur = start; cur < end; cur += sizeof(fds)) {
     395  			if (umove(tcp, cur, &fds))
     396  				break;
     397  			if (fdmatch(tcp, fds.fd, set, fdset))
     398  				return true;
     399  		}
     400  
     401  		return false;
     402  	}
     403  
     404  	case SEN_fsconfig: {
     405  		/* x, x, x, maybe path, maybe fd */
     406  		const unsigned int cmd = tcp->u_arg[1];
     407  		switch (cmd) {
     408  			case 3 /* FSCONFIG_SET_PATH */:
     409  			case 4 /* FSCONFIG_SET_PATH_EMPTY */:
     410  				return fdmatch(tcp, tcp->u_arg[4], set, fdset)
     411  					|| upathmatch(tcp, tcp->u_arg[3], set);
     412  			case 5 /* FSCONFIG_SET_FD */:
     413  				return fdmatch(tcp, tcp->u_arg[4], set, fdset);
     414  		}
     415  
     416  		return false;
     417  	}
     418  
     419  	case SEN_bpf:
     420  	case SEN_epoll_create:
     421  	case SEN_epoll_create1:
     422  	case SEN_eventfd2:
     423  	case SEN_eventfd:
     424  	case SEN_fanotify_init:
     425  	case SEN_fsopen:
     426  	case SEN_inotify_init:
     427  	case SEN_inotify_init1:
     428  	case SEN_io_uring_setup:
     429  	case SEN_landlock_create_ruleset:
     430  	case SEN_memfd_create:
     431  	case SEN_memfd_secret:
     432  	case SEN_mq_open:
     433  	case SEN_perf_event_open:
     434  	case SEN_pidfd_open:
     435  	case SEN_pipe:
     436  	case SEN_pipe2:
     437  	case SEN_printargs:
     438  	case SEN_socket:
     439  	case SEN_socketpair:
     440  	case SEN_timerfd_create:
     441  	case SEN_userfaultfd:
     442  		/*
     443  		 * These have TRACE_FILE or TRACE_DESC or TRACE_NETWORK set,
     444  		 * but they don't have any file descriptor or path args to test.
     445  		 */
     446  		return false;
     447  	}
     448  
     449  	/*
     450  	 * Our fallback position for calls that haven't already
     451  	 * been handled is to just check arg[0].
     452  	 */
     453  
     454  	if (s->sys_flags & TRACE_FILE)
     455  		return upathmatch(tcp, tcp->u_arg[0], set);
     456  
     457  	if (s->sys_flags & (TRACE_DESC | TRACE_NETWORK))
     458  		return fdmatch(tcp, tcp->u_arg[0], set, fdset);
     459  
     460  	return false;
     461  }