小崎と申します

シロウトなんで口を挟むのに恐縮なんですが・・・


> In article <4a9f873c.22035a0a.40fb.2740 / mx.google.com>,
>   Nobuyoshi Nakada <nobu / ruby-lang.org> writes:
> 
> > O_EXCL自体がサポートされていないということは、既存のファイルを上
> > 書きしてしまうということじゃないでしょうか。
> 
> なかださんが [ruby-dev:39210] で引用したマニュアルの記述は、
> 単に O_EXCL が無視されるっていうニュアンスではないように思い
> ます。「競合状態 (race condition) に出会う可能性がある」とい
> う話なので、競合が起きなければ動くんじゃないでしょうか。
> 
> テンポラリファイルの場合、成功すべきところで失敗するだけなら
> ごみファイルができるだけで済みますが、失敗すべきところで成功
> すると上書きされる可能性が出てきます。
> 
> UDP で response が落ちるというだけなら前者で済むと思うんです
> が、それ以外にもなにか問題があると上書きしちゃうかもしれない
> ですね。
> 
> いちおう、RFC 1094 には
> 
> | 2.2.10.  Create File
> | 
> |            struct createargs {
> |                    diropargs where;
> |                    sattr attributes;
> |            };
> | 
> |            diropres
> |            NFSPROC_CREATE(createargs) = 9;
> | 
> |    The file "name" is created in the directory given by "dir".  The
> |    initial attributes of the new file are given by "attributes".  A
> |    reply "status" of NFS_OK indicates that the file was created, and
> |    reply "file" and reply "attributes" are its file handle and
> |    attributes.  Any other reply "status" means that the operation failed
> |    and no file was created.
> | 
> |    Notes:  This routine should pass an exclusive create flag, meaning
> |    "create the file only if it is not already there".
> 
> とあって、Notes のところで O_EXCL を渡すと読めるような気がし
> ないでもない記述があるんですが。

同RFCのstruct sattrの定義は以下

          struct sattr {
              unsigned int mode;
              unsigned int uid;
              unsigned int gid;
              unsigned int size;
              timeval      atime;
              timeval      mtime;
          };

なので、O_EXCLを入れる事が出来るメンバは無いように見えます。
どこかの上位ビットを使う暗黙の規約があるのかとも一瞬考えましたが、
少なくともLinuxにおいては、最新版においても、以下のように
素直にsattrにmodeを入れているだけのようです。

-----------------------------------------------------------------
static int nfs_create(struct inode *dir, struct dentry *dentry, int mode,
                struct nameidata *nd)
{
        struct iattr attr;
        int error;
        int open_flags = 0;

        dfprintk(VFS, "NFS: create(%s/%ld), %s\n",
                        dir->i_sb->s_id, dir->i_ino, dentry->d_name.name);

        attr.ia_mode = mode;
        attr.ia_valid = ATTR_MODE;

        if ((nd->flags & LOOKUP_CREATE) != 0)
                open_flags = nd->intent.open.flags;

        error = NFS_PROTO(dir)->create(dir, dentry, &attr, open_flags, nd); // 実体は nfs_proc_create
        if (error != 0)
                goto out_err;
        return 0;
out_err:
        d_drop(dentry);
        return error;
}

static int
nfs_proc_create(struct inode *dir, struct dentry *dentry, struct iattr *sattr,
                int flags, struct nameidata *nd)
{
        struct nfs_fh           fhandle;
        struct nfs_fattr        fattr;
        struct nfs_createargs   arg = {
                .fh             = NFS_FH(dir),
                .name           = dentry->d_name.name,
                .len            = dentry->d_name.len,
                .sattr          = sattr
        };
(snip)

}


static inline __be32 *
xdr_encode_sattr(__be32 *p, struct iattr *attr)
{
        if (attr->ia_valid & ATTR_MODE) {
                *p++ = xdr_one;
                *p++ = htonl(attr->ia_mode & S_IALLUGO);
        } else {
                *p++ = xdr_zero;
        }
        if (attr->ia_valid & ATTR_UID) {
                *p++ = xdr_one;
                *p++ = htonl(attr->ia_uid);
        } else {
                *p++ = xdr_zero;
        }
        if (attr->ia_valid & ATTR_GID) {
                *p++ = xdr_one;
                *p++ = htonl(attr->ia_gid);
        } else {
                *p++ = xdr_zero;
        }
        if (attr->ia_valid & ATTR_SIZE) {
                *p++ = xdr_one;
                p = xdr_encode_hyper(p, (__u64) attr->ia_size);
        } else {
                *p++ = xdr_zero;
        }
        if (attr->ia_valid & ATTR_ATIME_SET) {
                *p++ = xdr_two;
                p = xdr_encode_time3(p, &attr->ia_atime);
        } else if (attr->ia_valid & ATTR_ATIME) {
                *p++ = xdr_one;
        } else {
                *p++ = xdr_zero;
        }
        if (attr->ia_valid & ATTR_MTIME_SET) {
                *p++ = xdr_two;
                p = xdr_encode_time3(p, &attr->ia_mtime);
        } else if (attr->ia_valid & ATTR_MTIME) {
                *p++ = xdr_one;
        } else {
                *p++ = xdr_zero;
        }
        return p;
}
-----------------------------------------------------------------

上記文章は、createを使う事によってO_EXCLを実現できるとRFC策定者は思ってるよ。
と言ってるだけではないでしょうか?


http://www.linux.or.jp/JF/JFdocs/Secure-Programs-HOWTO/avoid-race.html
を見ると、はっきりと

>ここで注目なのは、安全でない関数の tempnam(3)が使われていても、ループ内部で
> O_CREAT と O_EXCL を使って、セキュリティ上の弱点をカバーしている点です。
> 注目して欲しいのは、ファイル名を free()する必要があるところです。
> 処理が終わったら、close()や unlink()すべきです。標準 C 入出力ライブラリ
> を使いたいなら、fdopen()を「w+b」で使用して、ファイルディスクリプタを 
> FILE * に変えてください。この解決方法は、NFS version 2 (v2)のシステム
> ではうまくいかないでしょう。理由は、古い NFS が O_EXCL をきちんとサポート
> していないからです。この解決方法にはちょっとした欠点もあって、tempnam 
> を安全に使わないと、コンパイラやセキュリティ・スキャナがうるさく警告を
> 出すかもしれません。これは mkstemp(3)では問題となりません。

と、一時ファイル用のO_EXCLはダメと言っていますね。


#でも実装を見るとちゃんと動きそうな気がするんだよなー
#O_EXCLはnfsにいく前のvfsでエラーにするかどうかの話なので
# unlinkとかESTALE絡みの話が出てくる操作をしないかぎり、nfsが
# 特殊な考慮をしなくても動く気がしてならない。ぶつぶつ