この記事の内容に関する参考文献:
LSM (Linux セキュリティ モジュール) フレームワークの分析principle_lsm Framework_pwl999 のブログ - CSDN ブログ
Linux LSM (Linux セキュリティ モジュール) フック テクノロジー_weixin_30929011 のブログ-CSDN ブログ
どうもありがとうございます!
1. 杭打ちの具体的な実施
杭挿入の原理は前回の記事で紹介しましたが、今回は杭挿入の原理の具体的な実装について詳しく説明します。
静的インスツルメンテーション ポイントは、セキュリティ関連の重要なシステム コールに明示的に挿入されます。例えば、open()システムコールでは、vfs_open関数を介して最終的にsecurity_file_open関数が呼び出されます(システムコールの具体的な処理については関連記事を参照してください。ここでは説明しません)。vfs_open 関数は fs/open.c にあり、コードは次のとおりです。
/**
* vfs_open - open the file at the given path
* @path: path to open
* @file: newly allocated file with f_flag initialized
* @cred: credentials to use
*/
int vfs_open(const struct path *path, struct file *file)
{
file->f_path = *path;
return do_dentry_open(file, d_backing_inode(path->dentry), NULL);
}
do_entry_open 関数は同じファイル内にあり、コードは次のとおりです。
static int do_dentry_open(struct file *f,
struct inode *inode,
int (*open)(struct inode *, struct file *))
{
static const struct file_operations empty_fops = {};
int error;
path_get(&f->f_path);
f->f_inode = inode;
f->f_mapping = inode->i_mapping;
f->f_wb_err = filemap_sample_wb_err(f->f_mapping);
f->f_sb_err = file_sample_sb_err(f);
if (unlikely(f->f_flags & O_PATH)) {
f->f_mode = FMODE_PATH | FMODE_OPENED;
f->f_op = &empty_fops;
return 0;
}
if ((f->f_mode & (FMODE_READ | FMODE_WRITE)) == FMODE_READ) {
i_readcount_inc(inode);
} else if (f->f_mode & FMODE_WRITE && !special_file(inode->i_mode)) {
error = get_write_access(inode);
if (unlikely(error))
goto cleanup_file;
error = __mnt_want_write(f->f_path.mnt);
if (unlikely(error)) {
put_write_access(inode);
goto cleanup_file;
}
f->f_mode |= FMODE_WRITER;
}
/* POSIX.1-2008/SUSv4 Section XSI 2.9.7 */
if (S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode))
f->f_mode |= FMODE_ATOMIC_POS;
f->f_op = fops_get(inode->i_fop);
if (WARN_ON(!f->f_op)) {
error = -ENODEV;
goto cleanup_all;
}
error = security_file_open(f);
if (error)
goto cleanup_all;
error = break_lease(locks_inode(f), f->f_flags);
if (error)
goto cleanup_all;
/* normally all 3 are set; ->open() can clear them if needed */
f->f_mode |= FMODE_LSEEK | FMODE_PREAD | FMODE_PWRITE;
if (!open)
open = f->f_op->open;
if (open) {
error = open(inode, f);
if (error)
goto cleanup_all;
}
f->f_mode |= FMODE_OPENED;
if ((f->f_mode & FMODE_READ) &&
likely(f->f_op->read || f->f_op->read_iter))
f->f_mode |= FMODE_CAN_READ;
if ((f->f_mode & FMODE_WRITE) &&
likely(f->f_op->write || f->f_op->write_iter))
f->f_mode |= FMODE_CAN_WRITE;
if ((f->f_mode & FMODE_LSEEK) && !f->f_op->llseek)
f->f_mode &= ~FMODE_LSEEK;
if (f->f_mapping->a_ops && f->f_mapping->a_ops->direct_IO)
f->f_mode |= FMODE_CAN_ODIRECT;
f->f_flags &= ~(O_CREAT | O_EXCL | O_NOCTTY | O_TRUNC);
f->f_iocb_flags = iocb_flags(f);
file_ra_state_init(&f->f_ra, f->f_mapping->host->i_mapping);
if ((f->f_flags & O_DIRECT) && !(f->f_mode & FMODE_CAN_ODIRECT))
return -EINVAL;
/*
* XXX: Huge page cache doesn't support writing yet. Drop all page
* cache for this file before processing writes.
*/
if (f->f_mode & FMODE_WRITE) {
/*
* Paired with smp_mb() in collapse_file() to ensure nr_thps
* is up to date and the update to i_writecount by
* get_write_access() is visible. Ensures subsequent insertion
* of THPs into the page cache will fail.
*/
smp_mb();
if (filemap_nr_thps(inode->i_mapping)) {
struct address_space *mapping = inode->i_mapping;
filemap_invalidate_lock(inode->i_mapping);
/*
* unmap_mapping_range just need to be called once
* here, because the private pages is not need to be
* unmapped mapping (e.g. data segment of dynamic
* shared libraries here).
*/
unmap_mapping_range(mapping, 0, 0, 0);
truncate_inode_pages(mapping, 0);
filemap_invalidate_unlock(inode->i_mapping);
}
}
return 0;
cleanup_all:
if (WARN_ON_ONCE(error > 0))
error = -EINVAL;
fops_put(f->f_op);
put_file_access(f);
cleanup_file:
path_put(&f->f_path);
f->f_path.mnt = NULL;
f->f_path.dentry = NULL;
f->f_inode = NULL;
return error;
}
fs/open.c の do_entry_open 関数の上記のコードから、上記の LSM フレームワークのフック メカニズムがわかります。理解を深めるためにもう一度比較してみましょう。
その仕組みは次のとおりです。
まず、ユーザーがシステム コールを実行すると、元のメモリ インターフェイスを通じて機能エラー チェックが実行され、次に自律アクセス制御 DAC チェックが実行され、カーネルの内部オブジェクトにアクセスする前に、LSM がフック関数を通じて呼び出されます。セキュリティモジュールとLSMは動作を確認し、サブジェクトとオブジェクトのコンテキストとセキュリティドメインの関連情報から、リクエストが許可されるかどうか、つまりアクセスの正当性を判断し、その情報を返します。
3 つのステップにまとめます。
(1) 最初に、元の内部インターフェイスを通じて機能エラー チェックを実行します。
(2) 次に、自律アクセス制御 DAC 検査を実行します。
(3) LSM の Hook 関数を呼び出します。
次のコード (スニペット) に注目してください。
error = security_file_open(f);
if (error)
goto cleanup_all;
このコードは、LSM を呼び出すフック関数、より正確には、上の図とプロセスの特定のセキュリティ モジュールに対応します。
この関数の詳細については、分解に関する次の章を参照してください。