root/usr/src/cmd/fs.d/ufs/fsck/pass3b.c
/*
 * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*      Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T     */
/*        All Rights Reserved   */

/*
 * Copyright (c) 1980, 1986, 1990 The Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms are permitted
 * provided that: (1) source distributions retain this entire copyright
 * notice and comment, and (2) distributions including binaries display
 * the following acknowledgement:  ``This product includes software
 * developed by the University of California, Berkeley and its contributors''
 * in the documentation or other materials provided with the distribution
 * and in all advertising materials mentioning features or use of this
 * software. Neither the name of the University nor the names of its
 * contributors may be used to endorse or promote products derived
 * from this software without specific prior written permission.
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

#include <stdio.h>
#include <stdlib.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/mntent.h>
#include <sys/acl.h>
#include <sys/fs/ufs_acl.h>
#include <sys/fs/ufs_fs.h>
#include <sys/vnode.h>
#include <string.h>
#include <sys/fs/ufs_inode.h>
#include "fsck.h"

/*
 * We can be run on multiple filesystems (processed serially), so
 * these need to be re-initialized each time we start the pass.
 */
static caddr_t aclbuf;          /* hold acl's for parsing */
static int64_t aclbufoff;       /* offset into aclbuf */
static int64_t maxaclsize;      /* how big aclbuf is */

static int aclblksort(const void *, const void *);
static int bufchk(char *, int64_t, fsck_ino_t);
static void clear_shadow_client(struct shadowclientinfo *,
            struct shadowclients *, int);

void
pass3b(void)
{
        fsck_ino_t inumber;
        struct dinode *dp;
        struct inoinfo *aclp;
        struct inodesc curino;
        struct shadowclientinfo *sci;
        struct shadowclients *scc;
        int64_t acl_size_limit;
        int i;

        /*
         * Sort the acl list into disk block order.
         */
        qsort((char *)aclpsort, (int)aclplast, sizeof (*aclpsort), aclblksort);
        /*
         * Scan all the acl inodes, finding the largest acl file.
         *
         * The largest legal size is (4 * MAX_ACL_ENTRIES + 8) entries.
         * The four are the categories of specific users, specific
         * groups, default specific users, and default specific groups.
         * The eight are the entries for the owning user/group/other/class
         * plus the equivalent defaults.
         *
         * We double this to allow for a truly worst-case but legal
         * situation of every single acl having its own fsd_t wrapper.
         * Doubling is a bit pessimistic (sizeof (acl_t) > sizeof (fsd_t)).
         */
        acl_size_limit = sizeof (ufs_acl_t) * (4 * MAX_ACL_ENTRIES + 8);
        acl_size_limit *= 2;

        maxaclsize = 0;
        for (inumber = 0; inumber < aclplast; inumber++) {
                aclp = aclpsort[inumber];
                if ((int64_t)aclp->i_isize > acl_size_limit) {
                        (void) printf(
                            "ACL I=%d is excessively large (%lld > %lld)",
                            inumber,
                            (longlong_t)aclp->i_isize,
                            (longlong_t)acl_size_limit);
                        if (preen) {
                                (void) printf(" (IGNORING)\n");
                        } else if (reply("CLEAR") == 1) {
                                freeino(inumber, TI_PARENT);
                        } else {
                                iscorrupt = 1;
                                (void) printf("IGNORING SHADOW I=%d\n",
                                    inumber);
                        }
                        continue;
                }
                if ((int64_t)aclp->i_isize > maxaclsize)
                        maxaclsize = (int64_t)aclp->i_isize;
        }

        maxaclsize = ((maxaclsize / sblock.fs_bsize) + 1) * sblock.fs_bsize;
        if (maxaclsize == 0)
                goto noacls;

        if (aclbuf != NULL) {
                free((void *)aclbuf);
        }
        if ((aclbuf = malloc(maxaclsize)) == NULL) {
                errexit("cannot alloc %lld bytes for aclbuf\n",
                        (longlong_t)maxaclsize);
        }
        /*
         * Scan all the acl inodes, checking contents
         */
        for (inumber = 0; inumber < aclplast; inumber++) {
                aclp = aclpsort[inumber];
                if ((int64_t)aclp->i_isize > acl_size_limit) {
                        continue;
                }
                if ((statemap[aclp->i_number] & STMASK) != SSTATE) {
                        continue;
                }
                dp = ginode(aclp->i_number);
                init_inodesc(&curino);
                curino.id_fix = FIX;
                curino.id_type = ACL;
                curino.id_func = pass3bcheck;
                curino.id_number = aclp->i_number;
                curino.id_filesize = aclp->i_isize;
                aclbufoff = 0;
                (void) memset(aclbuf, 0, (size_t)maxaclsize);
                if ((ckinode(dp, &curino, CKI_TRAVERSE) & KEEPON) == 0 ||
                    bufchk(aclbuf, (int64_t)aclp->i_isize, aclp->i_number)) {
                        dp = ginode(aclp->i_number); /* defensive no-op */
                        if (dp->di_nlink <= 0) {
                                statemap[aclp->i_number] = FSTATE;
                                continue;
                        }
                        (void) printf("ACL I=%d BAD/CORRUPT", aclp->i_number);
                        if (preen || reply("CLEAR") == 1) {
                                if (preen)
                                        (void) printf("\n");
                                freeino(aclp->i_number, TI_PARENT);
                        } else {
                                iscorrupt = 1;
                        }
                }
        }
        /*
         * Now scan all shadow inodes, checking that any inodes that previously
         * had an acl still have an acl.
         */
noacls:
        for (sci = shadowclientinfo; sci; sci = sci->next) {
                if ((statemap[sci->shadow] & STMASK) != SSTATE) {
                        for (scc = sci->clients; scc; scc = scc->next) {
                                for (i = 0; i < scc->nclients; i++) {
                                        clear_shadow_client(sci, scc, i);
                                }
                        }
                }
        }
        free((void *)aclbuf);
        aclbuf = NULL;
}

static void
clear_shadow_client(struct shadowclientinfo *sci, struct shadowclients *scc,
        int client)
{
        int suppress_update = 0;
        caddr_t flow;
        struct inodesc ldesc;
        struct dinode *dp;

        (void) printf("I=%d HAS BAD/CLEARED ACL I=%d",
            scc->client[client], sci->shadow);
        if (preen || reply("FIX") == 1) {
                if (preen)
                        (void) printf("\n");

                /*
                 * If we clear the ACL, then the permissions should
                 * be as restrictive as possible until the user can
                 * set it to something reasonable.  If we keep the
                 * ACL, then the permissions are pretty much
                 * irrelevant.  So, just always clear the permission
                 * bits.
                 */
                dp = ginode(scc->client[client]);
                dp->di_mode &= IFMT;
                dp->di_shadow = 0;
                inodirty();

                /*
                 * Decrement in-memory link count - pass1 made sure
                 * the shadow inode # is a valid inode number.  But
                 * first, see if we're going to overflow our sixteen
                 * bits.
                 */
                LINK_RANGE(flow, lncntp[dp->di_shadow], 1);
                if (flow != NULL) {
                        LINK_CLEAR(flow, scc->client[client], dp->di_mode,
                            &ldesc);
                        if (statemap[scc->client[client]] == USTATE)
                                suppress_update = 1;
                }

                /*
                 * We don't touch the shadow's on-disk link count,
                 * because we've already cleared its state in pass3b().
                 * Here we're just trying to keep lncntp[] in sync, so
                 * we can detect spurious links.
                 */
                if (!suppress_update)
                        TRACK_LNCNTP(sci->shadow, lncntp[sci->shadow]++);
        } else {
                iscorrupt = 1;
        }
}

/*
 * Collect all the (data) blocks of an acl file into a buffer.
 * Later we'll scan the buffer and validate the acl data.
 */
int
pass3bcheck(struct inodesc *idesc)
{
        struct bufarea *bp;
        size_t size, bsize;

        if (aclbufoff == idesc->id_filesize) {
                return (STOP);
        }
        bsize = size = sblock.fs_fsize * idesc->id_numfrags;
        if ((size + aclbufoff) > idesc->id_filesize)
                size = idesc->id_filesize - aclbufoff;
        if (aclbufoff + size > maxaclsize)
                errexit("acl size %lld exceeds maximum calculated "
                        "size of %lld bytes",
                        (longlong_t)aclbufoff + size, (longlong_t)maxaclsize);
        bp = getdatablk(idesc->id_blkno, bsize);
        if (bp->b_errs != 0) {
                brelse(bp);
                return (STOP);
        }
        (void) memmove((void *)(aclbuf + aclbufoff), (void *)bp->b_un.b_buf,
                (size_t)size);
        aclbufoff += size;
        brelse(bp);
        return (KEEPON);
}

/*
 * Routine to sort disk blocks.
 */
static int
aclblksort(const void *pp1, const void *pp2)
{
        const struct inoinfo **aclpp1 = (const struct inoinfo **)pp1;
        const struct inoinfo **aclpp2 = (const struct inoinfo **)pp2;

        return ((*aclpp1)->i_blks[0] - (*aclpp2)->i_blks[0]);
}

/*
 * Scan a chunk of a shadow file.  Return zero if no ACLs were found,
 * or when all that were found were valid.
 */
static int
bufchk(char *buf, int64_t len, fsck_ino_t inum)
{
        ufs_fsd_t *fsdp;
        ufs_acl_t *ufsaclp = NULL;
        int numacls;
        int curacl;
        struct type_counts_s {
                int nuser_objs;
                int ngroup_objs;
                int nother_objs;
                int nclass_objs;
                int ndef_user_objs;
                int ndef_group_objs;
                int ndef_other_objs;
                int ndef_class_objs;
                int nusers;
                int ngroups;
                int ndef_users;
                int ndef_groups;
        } type_counts[3];       /* indexed by FSD_ACL and FSD_DFACL */
        struct type_counts_s *tcp, *tcp_all, *tcp_def, *tcp_norm;
        int numdefs;
        caddr_t bad;
        caddr_t end = buf + len;
        int64_t recsz = 0;
        int64_t min_recsz = FSD_RECSZ(fsdp, sizeof (*fsdp));
        struct shadowclientinfo *sci;
        struct shadowclients *scc;
        fsck_ino_t target;
        int numtargets = 0;

        /*
         * check we have a non-zero length for this shadow inode
         */
        if (len == 0) {
                pwarn("ACL I=%d HAS ZERO LENGTH\n", inum);
                return (1);
        }

        (void) memset(type_counts, 0, sizeof (type_counts));

        /* LINTED pointer cast alignment (aligned buffer always passed in) */
        for (fsdp = (ufs_fsd_t *)buf;
            (caddr_t)fsdp < end;
            /* LINTED as per the above */
            fsdp = (ufs_fsd_t *)((caddr_t)fsdp + recsz)) {

                recsz = FSD_RECSZ(fsdp, fsdp->fsd_size);
                if ((recsz < min_recsz) ||
                    (((caddr_t)fsdp + recsz) > (buf + len))) {
                        pwarn("Bad FSD entry size %lld in shadow inode %d",
                            recsz, inum);
                        if (reply("CLEAR SHADOW INODE") == 1) {
                                freeino(inum, TI_PARENT);
                        } else {
                                /*
                                 * Bad size can cause the kernel to
                                 * go traipsing off into never-never land.
                                 */
                                iscorrupt = 1;
                        }
                        return (0);
                }

                switch (fsdp->fsd_type) {
                case FSD_FREE:  /* ignore empty slots */
                        break;
                case FSD_ACL:
                case FSD_DFACL:
                        /*
                         * Subtract out the two ints in the fsd_type,
                         * leaving us just the size of fsd_data[].
                         */
                        numacls = (fsdp->fsd_size - 2 * sizeof (int)) /
                                                        sizeof (ufs_acl_t);
                        tcp = &type_counts[fsdp->fsd_type];
                        curacl = 0;
                        /* LINTED pointer cast alignment */
                        for (ufsaclp = (ufs_acl_t *)fsdp->fsd_data;
                                                numacls; ufsaclp++, curacl++) {
                                switch (ufsaclp->acl_tag) {
                                case USER_OBJ:          /* Owner */
                                        tcp->nuser_objs++;
                                        break;
                                case GROUP_OBJ:         /* Group */
                                        tcp->ngroup_objs++;
                                        break;
                                case OTHER_OBJ:         /* Other */
                                        tcp->nother_objs++;
                                        break;
                                case CLASS_OBJ:         /* Mask */
                                        tcp->nclass_objs++;
                                        break;
                                case DEF_USER_OBJ:      /* Default Owner */
                                        tcp->ndef_user_objs++;
                                        break;
                                case DEF_GROUP_OBJ:     /* Default Group */
                                        tcp->ndef_group_objs++;
                                        break;
                                case DEF_OTHER_OBJ:     /* Default Other */
                                        tcp->ndef_other_objs++;
                                        break;
                                case DEF_CLASS_OBJ:     /* Default Mask */
                                        tcp->ndef_class_objs++;
                                        break;
                                case USER:              /* Users */
                                        tcp->nusers++;
                                        break;
                                case GROUP:             /* Groups */
                                        tcp->ngroups++;
                                        break;
                                case DEF_USER:          /* Default Users */
                                        tcp->ndef_users++;
                                        break;
                                case DEF_GROUP:         /* Default Groups */
                                        tcp->ndef_groups++;
                                        break;
                                default:
                                        return (1);
                                }

                                if ((ufsaclp->acl_perm & ~07) != 0) {
                                        /*
                                         * Caller will report inode, etc
                                         */
                                        pwarn("Bad permission 0%o in ACL\n",
                                            ufsaclp->acl_perm);
                                        return (1);
                                }

                                numacls--;
                        }
                        break;
                default:
                        if (fsdp->fsd_type >= FSD_RESERVED3 &&
                            fsdp->fsd_type <= FSD_RESERVED7)
                                bad = "Unexpected";
                        else
                                bad = "Unknown";
                        pwarn("%s FSD type %d in shadow inode %d",
                            bad, fsdp->fsd_type, inum);
                        /*
                         * This is relatively harmless, since the
                         * kernel will ignore any entries it doesn't
                         * recognize.  Don't bother with iscorrupt.
                         */
                        if (preen) {
                                (void) printf(" (IGNORED)\n");
                        } else if (reply("IGNORE") == 0) {
                                if (reply("CLEAR SHADOW INODE") == 1) {
                                        freeino(inum, TI_PARENT);
                                }
                                return (0);
                        }
                        break;
                }
        }
        if ((caddr_t)fsdp != (buf + len)) {
                return (1);
        }

        /* If we didn't find any acls, ignore the unknown attribute */
        if (ufsaclp == NULL)
                return (0);

        /*
         * Should only have default ACLs in FSD_DFACL records.
         * However, the kernel can handle it, so just report that
         * something odd might be going on.
         */
        tcp = &type_counts[FSD_DFACL];
        if (verbose &&
            (tcp->nuser_objs != 0 ||
            tcp->ngroup_objs != 0 ||
            tcp->nother_objs != 0 ||
            tcp->nclass_objs != 0 ||
            tcp->nusers != 0 ||
            tcp->ngroups != 0)) {
                (void) printf("NOTE: ACL I=%d has miscategorized ACLs.  ",
                    inum);
                (void) printf("This is harmless, but not normal.\n");
        }

        /*
         * Similarly for default ACLs in FSD_ACL records.
         */
        tcp = &type_counts[FSD_ACL];
        if (verbose &&
            (tcp->ndef_user_objs != 0 ||
            tcp->ndef_group_objs != 0 ||
            tcp->ndef_other_objs != 0 ||
            tcp->ndef_class_objs != 0 ||
            tcp->ndef_users != 0 ||
            tcp->ndef_groups != 0)) {
                (void) printf("NOTE: ACL I=%d has miscategorized ACLs.",
                    inum);
                (void) printf("  This is harmless, but not normal.\n");
        }

        /*
         * Get consolidated totals, now that we're done with checking
         * the segregation above.  Assumes that neither FSD_ACL nor
         * FSD_DFACL are zero.
         */
        tcp_all = &type_counts[0];
        tcp_norm = &type_counts[FSD_ACL];
        tcp_def = &type_counts[FSD_DFACL];

        tcp_all->nuser_objs = tcp_def->nuser_objs + tcp_norm->nuser_objs;
        tcp_all->ngroup_objs = tcp_def->ngroup_objs + tcp_norm->ngroup_objs;
        tcp_all->nother_objs = tcp_def->nother_objs + tcp_norm->nother_objs;
        tcp_all->nclass_objs = tcp_def->nclass_objs + tcp_norm->nclass_objs;
        tcp_all->ndef_user_objs =
                tcp_def->ndef_user_objs + tcp_norm->ndef_user_objs;
        tcp_all->ndef_group_objs =
                tcp_def->ndef_group_objs + tcp_norm->ndef_group_objs;
        tcp_all->ndef_other_objs =
                tcp_def->ndef_other_objs + tcp_norm->ndef_other_objs;
        tcp_all->ndef_class_objs =
                tcp_def->ndef_class_objs + tcp_norm->ndef_class_objs;
        tcp_all->nusers = tcp_def->nusers + tcp_norm->nusers;
        tcp_all->ngroups = tcp_def->ngroups + tcp_norm->ngroups;
        tcp_all->ndef_users = tcp_def->ndef_users + tcp_norm->ndef_users;
        tcp_all->ndef_groups = tcp_def->ndef_groups + tcp_norm->ndef_groups;

        /*
         * Check relationships among acls
         */
        if (tcp_all->nuser_objs != 1 ||
            tcp_all->ngroup_objs != 1 ||
            tcp_all->nother_objs != 1 ||
            tcp_all->nclass_objs > 1) {
                return (1);
        }

        if (tcp_all->ngroups && !tcp_all->nclass_objs) {
                return (1);
        }

        if (tcp_all->ndef_user_objs > 1 ||
            tcp_all->ndef_group_objs > 1 ||
            tcp_all->ndef_other_objs > 1 ||
            tcp_all->ndef_class_objs > 1) {
                return (1);
        }

        /*
         * Check relationships among default acls
         */
        numdefs = tcp_all->ndef_other_objs + tcp_all->ndef_user_objs +
                tcp_all->ndef_group_objs;

        if (numdefs != 0 && numdefs != 3) {
                return (1);
        }

        /*
         * If there are default acls, then the shadow inode's clients
         * must be a directory or an xattr directory.
         */
        if (numdefs != 0) {
                /* This is an ACL so find it's clients */
                for (sci = shadowclientinfo; sci != NULL; sci = sci->next)
                        if (sci->shadow == inum)
                            break;
                if ((sci ==  NULL) || (sci->clients == NULL))
                        return (1);

                /* Got shadow info, now look at clients */
                for (scc = sci->clients; scc != NULL; scc = scc->next) {
                        for (numtargets = 0; numtargets < scc->nclients;
                            numtargets++) {
                                target = scc->client[numtargets];
                                if (!INO_IS_DVALID(target))
                                        return (1);
                        }
                }
        }

        if (tcp_all->ndef_groups && !tcp_all->ndef_class_objs) {
                return (1);
        }

        if ((tcp_all->ndef_users || tcp_all->ndef_groups) &&
            ((numdefs != 3) && !tcp_all->ndef_class_objs)) {
                return (1);
        }

        return (0);
}