La programmation noyau
Implémentation de fichiers cachés


Le but de cette documentation est de montrer comment ajouter un nouvel appel sytème grâce à un petit exemple de modification du file system FFS.

ATTENTION, toute modification maladroite du noyau peut entraîner des crashs qui peuvent endommager gravement votre système en cas de bugs.

Nous allons créer un nouvel appel système dont le but sera de permettre à un utilisateur de cacher un fichier, ou un répertoire de quatres façons différentes :

  • Le rendre invisible pour le propriétaire du fichier
  • Le rendre invisible pour le groupe
  • Le rendre invisible pour les autres
  • Le rendre invisible pour le système entier (si l'utilisateur est root)
  • Pour cela nous allons d'abord apporter une petite modification au file system FFS, puis implémenter l'appel système correspondant Afin d'obtenir CECI.

    Nous allons reprendre l'idée des flags en les modifiant afin qu'un utilisateur quelconque puisse appeler un nouvel appel système pour cacher un fichier ou un répertoire . Les fichiers cachés par un utilisateur non root, le seront pour tout le monde sauf root. Par contre les fichiers ou répertoires cachés par le super utilisateur pourront être invisible par le système entier.

    Les fichiers ne seront cachés que le secure level sera > 0.

    L'introduction dans le noyau de ces routines ralentissent sensiblement l'accès aux fichiers, En effet dans l'implémentation initiale de readdir (sur les architectures little endian) le contenu du répertoire est recopié dans les structures uio sans aucunes vérifications. L'implémentation que nous allons rajouter va devoir vérifier un à un les fichiers du répertoire

    Les différentes phases à effectuer sont :

    I Création de la fonction pour le file system.

    Nous allons utiliser le champ destiné aux flags. Pour les flags 16 bits sont destinés a l'utilisateur, et 16 pour le système. Pour chacun des groupes (utilisateur et systeme) 4 bits seulement sont utilisés. Nous allons donc pouvoir utiliser ces champs.

    Nous allons donc implémenter 4 nouveaux flags :
    Extrait du /usr/src/sys/sys/stat.h :
    /*
     * Definitions of flags stored in file flags word.
     *
     * Super-user and owner changeable flags.
     */
    #define	UF_SETTABLE	0x0000ffff	/* mask of owner changeable flags */
    #define	UF_NODUMP	0x00000001	/* do not dump file */
    #define	UF_IMMUTABLE	0x00000002	/* file may not be changed */
    #define	UF_APPEND	0x00000004	/* writes to file may only append */
    #define UF_OPAQUE	0x00000008	/* directory is opaque wrt. union */
    #define UF_USERHIDE	0x00000010	/* user hidden file   <== ICI */
    #define UF_GROUPHIDE	0x00000020	/* group hidden file   <== ICI */
    #define UF_OTHERHIDE	0x00000040	/* other hidden file   <== ICI */
    /*
     * Super-user changeable flags.
     */
    #define	SF_SETTABLE	0xffff0000	/* mask of superuser changeable flags */
    #define	SF_ARCHIVED	0x00010000	/* file is archived */
    #define	SF_IMMUTABLE	0x00020000	/* file may not be changed */
    #define	SF_APPEND	0x00040000	/* writes to file may only append */
    #define	SF_HIDE		0x00100000	/* system hidden file  <== ICI */
    
    
    Nous allons modifier l'appel système qui récupère les noms des fichiers d'un répertoire (la fonction readdir). Cette fonction se trouve dans le répertoire /usr/src/sys/ufs/ufs/ufs_vnops.c et se nomme ufs_readdir.
    Cette fonction récupère les noms des fichiers d'un répertoire grâce à la fonction VOP_READ. Nous allons insérer une fonction à ce niveau qui va vérifier que le fichier demandé n'est pas caché.

    Pour cela nous allons créer une nouvelle fonction que nous allons appeler à ce niveau :
    [...] ligne 1582
    #if	(BYTE_ORDER == LITTLE_ENDIAN)
    	if (ap->a_vp->v_mount->mnt_maxsymlinklen > 0) 
    	  {
    	    struct dirent	*dp;
    	    
    	    dp = (struct dirent *)uio->uio_iov->iov_base;
    	    if ((!(error = VOP_READ(ap->a_vp, uio, 0, ap->a_cred)))
    		&& (securelevel > 0))
    	      hide_ffs_file(ap, dp, count - uio->uio_resid);  /* ICI */
    	  }
    	else
    	  {
    		    struct dirent *dp, *edp;
    		    struct uio auio;
    [...]
    
    Comme nous allons créer deux nouvelles fonctions il faut les mettre dans le .h correspondant (ufs_extern.h) :

    Insérez dans le fichier ufs_extern.h le prototypage de nos nouvelles fonctions au niveau du "__BEGIN_DECLS" :
    [...] Ligne 59
    struct dirent;
    
    __BEGIN_DECLS
    int	is_a_ffs_hide_file	__P((struct vop_readdir_args *, u_int32_t));
    void	hide_ffs_file		__P((struct vop_readdir_args *, struct dirent *dp, int));
    [...]
    

    Voici ces deux fonctions que nous allons mettre dans le fichier /usr/src/sys/ufs/ufs/ufs_vnops.c :

    La fonction is_a_ffs_hide_file récupère la vnode associée au numéro d'inode passée en paramètre (grâce a la fonction VFS_GET). Avec celle-ci nous récupérons l'inode correspondante (grâce a la fonction VTOI) pour pouvoir vérifier les flag.

    Enfin nous libérons la vnode avec la fonction vput.
    int                     is_a_ffs_hide_file(ap, inode_number)
    struct vop_readdir_args *ap;
    u_int32_t               inode_number;
    {
      struct vnode          *vnode;
      struct inode          *inode;
      int                   result;
    
      result = 0;
      if (!VFS_VGET(ap->a_vp->v_mount, inode_number, &vnode))
        {
          inode = VTOI(vnode);
          result = (inode->i_ffs_flags & SF_HIDE);
          if ((ap->a_cred->cr_uid != inode->i_ffs_uid)
              && (ap->a_cred->cr_gid != inode->i_ffs_gid))
            result |= (inode->i_ffs_flags & UF_OTHERHIDE);
          else
            {
              if (ap->a_cred->cr_uid == inode->i_ffs_uid)
                result |= (inode->i_ffs_flags & UF_USERHIDE);
              else
    	    if (ap->a_cred->cr_gid == inode->i_ffs_gid)
                  result |= (inode->i_ffs_flags & UF_GROUPHIDE);
            }
          vput(vnode);
        }
      return ((ap->a_cred->cr_uid)?result:(result & SF_HIDE));
    }
    
    
    La fonction hide_ffs_file modifie la structure dirent passée en paramètre. Si un fichier ne doit pas apparaitre on modifie le champ d_reclen de l'élement précécent de la structure pour qu'il pointe sur la prochaine entrée qui n'est pas cachée, de plus nous mettons a zéro les données qui doivent être cachées.

    void			hide_ffs_file(ap, dp, len)
    struct vop_readdir_args *ap;
    struct dirent		*dp;
    int			len;
    {
      struct dirent		*dp_prev;
      struct dirent		*end;
      int			tmp;
    
      end = (struct dirent *)((char *)dp + len);
      dp_prev = dp;
      if ((dp < end) && (dp->d_reclen > 0))
        dp = (struct dirent *)((char *) dp + dp->d_reclen);
      while (dp < end)
        {
          while ((is_a_ffs_hide_file(ap, dp->d_fileno))
    	     && (dp < end))
    	{
    	  if (!(dp->d_reclen > 0))
    	    return;
    	  dp_prev->d_reclen += dp->d_reclen;
    	  tmp = dp->d_reclen;
    	  bzero(dp, tmp);
    	  dp = (struct dirent *)((char *) dp + tmp);
    	}
          if (!(dp->d_reclen > 0))
    	return;
          dp = (struct dirent *)((char *) dp + dp->d_reclen);
          dp_prev = (struct dirent *)((char *) dp_prev + dp_prev->d_reclen);
        } 
    }
    
    Nous pouvons dès maintenant tester notre petite modification après avoir recompilé le noyau. Nous n'avons qu'a utiliser l'appel système chflags a qui nous passons un flag de type UF_USERHIDE (0x00000010) dans cet exemple :

    root /tmp >cat > chflags.c
    int	main(int argc, char **argv)
    {
      int	error;
      
      if (argc >1)
       {
         error = chflags(argv[1], 0x00000010);
         printf("error = %d\n", error);
       }
    }
    root /tmp >cc chflags.c 
    root /tmp >mkdir titi
    root /tmp >chmod 755 titi
    root /tmp >./a.out titi
    error = 0
    root /tmp >whoami
    root
    root /tmp >ls -l
    total 34
    -rwx------  1 root  wheel  14758 Nov 14 23:00 a.out
    -rw-------  1 root  wheel    142 Nov 14 22:59 chflags.c
    drwx------  2 root  wheel    512 Nov 14 23:00 titi
    root /tmp >su modu
    modu /tmp >ls -l
    total 32
    -rwx------  1 root  wheel  14758 Nov 14 23:00 a.out
    -rw-------  1 root  wheel    142 Nov 14 22:59 chflags.c
    modu /tmp >cd titi
    modu /tmp/titi >pwd
    pwd: getcwd: No such file or directory
    modu /tmp/titi >
    
    Nous voyons donc que le répertoire existe mais qu'il ne nous apparait plus . Par contre certaines commandes ne marcherons plus dans ces répertoires cachés (exemple pwd).


    II Création d'un appel système

    Maintenant que nous avons modifié les fonctions du file system, il ne reste plus qu'à ajouter les appels systèmes correspondants.

    On pourrait simplement modifier l'appel système chflags, mais le but de cette documentation étant aussi l'implémentation d'un nouvel appel système nous allons en créer un nouveau. Celui ci ne sera qu'une recopie de l'appel système chflag, mais portera un nom different.


    Pour rajouter un syscall, il faut ajouter une réference a celui-ci dans le fichier /usr/src/sys/kern/syscall.master.

    Le premier paramètre etant le numéro du syscall le deuxième le type de syscall (standart, obsolète ...) et enfin le prototypage nous allons le rajouter à la fin des syscalls:

    modu /usr/src/sys/kern >tail -1 syscalls.master
    260     STD             { int sys_chview(const char *path, u_int flags); }
    modu /usr/src/sys/kern >
    

    Ensuite il faut executer le script makesyscall.sh (par l'intermédiaire de make):

    Maintenant notre syscall a été rajouté dans le tableau de syscalls:

    modu /usr/src/sys/kern >make
    modu /usr/src/sys/kern >tail -2 syscalls.c
            "chview",                       /* 260 = chview */
    };
    root /usr/src/sys/kern >
    

    L'appel système sys_chview est le même que sys_chflags, qui lui
    même est pratiquement identique à chmod. Nous rajoutons donc cette fonction
    dans le fichier /usr/src/sys/kern/vfs_syscalls.c :

    modu /usr/src/sys/kern >grep -B5 -A32 sys_view vfs_syscalls.c
    /*
     * Change view of a file given a path name.
     */
    /* ARGSUSED */
    
    int
    sys_chview(p, v, retval)
    	struct proc *p;
    	void *v;
    	register_t *retval;
    {
    	register struct sys_chflags_args /* {
    		syscallarg(char *) path;
    		syscallarg(unsigned int) flags;
    	} */*uap = v;
    	register struct vnode *vp;
    	struct vattr vattr;
    	int error;
    	struct nameidata nd;
    
    	NDINIT(&nd, LOOKUP, FOLLOW, UIO_USERSPACE, SCARG(uap, path), p);
    	if ((error = namei(&nd)) != 0)
    		return (error);
    	vp = nd.ni_vp;
    	VOP_LEASE(vp, p, p->p_ucred, LEASE_WRITE);
    	vn_lock(vp, LK_EXCLUSIVE | LK_RETRY, p);
    	if (vp->v_mount->mnt_flag & MNT_RDONLY)
    		error = EROFS;
    	else if (SCARG(uap, flags) == VNOVAL)
    		error = EINVAL;
    	else {
    		VATTR_NULL(&vattr);
    		vattr.va_flags = SCARG(uap, flags);
    		error = VOP_SETATTR(vp, &vattr, p->p_ucred, p);
    	}
    	vput(vp);
    	return (error);
    }
    
    modu /usr/src/sys/kern >
    

    La fonction sys_chview appelle la fonction VOP_SETATTR qui appellera la fonction vop_setattr propre au file system :

    modu /usr/src/sys/kern >grep vop_setattr /usr/src/sys/ufs/ffs/ffs_vnops.c
            { &vop_setattr_desc, ufs_setattr },             /* setattr */
            { &vop_setattr_desc, ufs_setattr },             /* setattr */
            { &vop_setattr_desc, ufs_setattr },             /* setattr */
    modu /usr/src/sys/kern >
    

    Cette fonction est ufs_setattr pour FFS. Nous allons donc rajouter la gestion de nos nouveaux flags dans cette fontion. En fait il suffit juste de vérifier que l'UID de la personne souhaitant mettre le flag SF_HIDE soit bien 0. Ceci est en fait deja implémenté par ces lignes :
    modu /usr/src/sys/kern >grep -A5 -B1  SF_IMMUTABLE
    /usr/src/sys/ufs/ufs/ufs_vnops.c
                    if (cred->cr_uid == 0) {
                            if ((ip->i_ffs_flags & (SF_IMMUTABLE | SF_APPEND))&&
                                securelevel > 0)
                                    return (EPERM);
                            ip->i_ffs_flags = vap->va_flags;
                    } else {
                            if (ip->i_ffs_flags & (SF_IMMUTABLE | SF_APPEND) ||
                                (vap->va_flags & UF_SETTABLE) != vap->va_flags)
                                    return (EPERM); /* ICI */
                            ip->i_ffs_flags &= SF_SETTABLE;
                            ip->i_ffs_flags |= (vap->va_flags & UF_SETTABLE);
                    }
    modu /usr/src/sys/kern >
    

    Nous n'avons donc pas à modifier cette fonction. Comme nous avons créé un nouvel appel système, nous devons aussi le rajouter dans la libc et recompiler celle-ci.

    III Recompilation de la librairie C Standard.
    Nous allons rajouter une entrée dans le fichier /usr/src/lib/libc/sys/Makefile.inc :
    modu /usr/src/lib/libc >grep chview sys/Makefile.inc 
    ASM=    accept.o access.o acct.o adjtime.o bind.o chdir.o chflags.o chview.o chmod.o \
    modu /usr/src/lib/libc >
    

    Il suffit juste de rajouter le "syscall.o" dans le Makefile.inc pour qu'il soit pris en compte.

    Il faut aussi mettre à jours les includes avant de compiler :

    modu /usr/src/lib/libc >cp /usr/src/sys/sys/syscall* /usr/include/sys/
    modu /usr/src/lib/libc >cp /usr/src/sys/sys/stat.h /usr/include/sys/
    modu /usr/src/lib/libc >make && make install
    
    Et voilà, c'est fini, il ne reste plus qu'à tester le tout:
    modu ~/work/doc >cat > chview.c 
    #include <sys/stat.h>
    
    int     main(int argc, char **argv)
    {
      if (argc > 2)
        {
          if (!strcmp(argv[1], "user"))
            return (chview(argv[2], UF_USERHIDE));
          if (!strcmp(argv[1], "group"))
            return (chview(argv[2], UF_GROUPHIDE));
          if (!strcmp(argv[1], "other"))
            return (chview(argv[2], UF_OTHERHIDE));
          if (!strcmp(argv[1], "sys"))
            return (chview(argv[2], SF_HIDE));
        }
       return (-1);
    }
    modu ~/work/doc >cc chview.c -o chview 
    modu ~/work/doc >touch user group other 
    modu ~/work/doc >mkdir titi && ls -l
    total 58 
    -rw-------  1 modu  work    149 Nov 21 20:40 chview.c
    -rwx------  1 modu  work  14758 Nov 21 20:40 chview
    -rw-r--r--  1 modu  work  11853 Nov 21 20:40 syscall
    drwx------  2 modu  work    512 Nov 21 20:38 titi
    -rwx------  1 modu  work      0 Nov 21 20:38 user
    -rwx------  1 modu  work      0 Nov 21 20:38 group
    -rwx------  1 modu  work      0 Nov 21 20:38 other
    modu ~/work/doc >id
    uid=1001(modu) gid=1000(work) groups=1000(work)
    modu ~/work/doc >./chview user user
    modu ~/work/doc >./chview user titi
    modu ~/work/doc >./chview group group
    modu ~/work/doc >./chview other other
    modu ~/work/doc >ls -la
    total 56
    -rw-------  1 modu  work    354 Nov 21 20:40 chview.c
    -rwx------  1 modu  work  14758 Nov 21 20:37 chview
    -rw-r--r--  1 modu  work  11033 Nov 21 20:40 syscall
    -rwx------  1 modu  work      0 Nov 21 20:38 group
    -rwx------  1 modu  work      0 Nov 21 20:38 other
    modu ~/work/doc >cd titi
    modu ~/work/doc/titi >ls -la ; cd ..
    total 4
    drwx------  2 modu  modu  512 Nov 21 20:38 .
    drwxr-xr-x  3 modu  modu  512 Nov 21 20:39 ..
    modu ~/work/doc >su uidzero
    uidzero /home/work/doc >id
    uid=1002(uidzero) gid=1000(work) groups=1000(work)
    uidzero /home/modu/work/doc >ls -la
    total 58 
    -rw-------  1 modu  work    149 Nov 21 20:40 chview.c
    -rwx------  1 modu  work  14758 Nov 21 20:40 chview
    -rw-r--r--  1 modu  work  11853 Nov 21 20:40 syscall
    drwx------  2 modu  work    512 Nov 21 20:38 titi
    -rwx------  1 modu  work      0 Nov 21 20:38 user
    -rwx------  1 modu  work      0 Nov 21 20:38 other
    uidzero /home/modu/work/doc >su
    #whoami
    root
    #ls -la
    total 58 
    -rw-------  1 modu  work    149 Nov 21 20:40 chview.c
    -rwx------  1 modu  work  14758 Nov 21 20:40 chview
    -rw-r--r--  1 modu  work  11853 Nov 21 20:40 syscall
    drwx------  2 modu  work    512 Nov 21 20:38 titi
    -rwx------  1 modu  work      0 Nov 21 20:38 user
    -rwx------  1 modu  work      0 Nov 21 20:38 group
    -rwx------  1 modu  work      0 Nov 21 20:38 other
    #
    
    Vous pouvez évidemment faire des combinaison de flags, en ajouter, en retirer, ou même mettre le fichier en immuable après l'avoir caché.

    [back]
    ----------
    infosec.bsdfr.org
    $INFOSEC: Programmation noyau 11/26/99 18:34:55 By DEMBOUR Olivier.
    Mailto:olivier.dembour@bsdfr.org