LiNEAR セキュリティ監査レポート

これは2022年4月にLiNEAR社に対して実施したセキュリティ監査報告書です。

LiNEAR セキュリティ監査レポート

レポートマニフェスト

項目 説明
クライアント LiNEAR Protocol
対象 LiNEAR

バージョン履歴

バージョン 日付 説明
1.0 2022年4月1日 初版

1. はじめに

1.1 対象コントラクトについて

情報 説明
タイプ スマートコントラクト
言語 Rust
アプローチ 半自動および手動検証

監査されたリポジトリにはLiNEAR^1が含まれています。

監査プロセスは反復的です。具体的には、発見された問題に対処するコミットを監査します。新しい問題がある場合は、このプロセスを続行します。監査中のコミットSHA値は以下の通りです。当社の監査レポートは、初期バージョン(バージョン1)および問題に対処するための新しいコード(以下のバージョン)に責任を負います。

1.2 セキュリティモデル

リスクを評価するために、OWASPリスク評価方法論^2や共通脆弱性識別子(CWE)^3など、産業界および学界で広く採用されている基準または提案に従います。リスクの全体的な重大度は、可能性影響によって決定されます。具体的には、可能性は、特定の脆弱性が攻撃者によって発見および悪用される可能性を推定するために使用され、影響は、正常な悪用の結果を測定するために使用されます。

このレポートでは、可能性と影響の両方がそれぞれの2つの評価に分類され、その組み合わせは表1.1に示されています。

したがって、このレポートで測定される重大度は、の3つのカテゴリに分類されます。網羅性を期すため、リスクを適切に判断できない状況をカバーするために未定も使用されます。

さらに、発見された問題のステータスは、次の4つのカテゴリのいずれかに分類されます。

  • 未定 まだ応答がありません。

  • 承認済み 問題はクライアントによって受信されましたが、まだ確認されていません。

  • 確認済み 問題はクライアントによって認識されましたが、まだ修正されていません。

  • 修正済み 問題はクライアントによって確認され、修正されました。

2. 調査結果

合計で、スマートコントラクトで4つの潜在的な問題を発見しました。また、以下の4つの推奨事項があります。

  • 高リスク: 0

  • 中リスク: 2

  • 低リスク: 2

  • 推奨事項: 4

ID 重大度 説明 カテゴリ ステータス
1 精度損失 ソフトウェアセキュリティ 修正済み
2 ユーザーの利用可能残高が一時的にロックされる可能性があります DeFiセキュリティ 確認済み
3 受益者への無制限の報酬配布 DeFiセキュリティ 修正済み
4 ユーザーのアンステーキングリクエストが時間通りに満たされない可能性があります DeFiセキュリティ 修正済み
7 - epoch_update_rewards関数が、無制限の受益者により機能しない可能性があります 推奨事項 修正済み
8 - 冗長なコード 推奨事項 確認済み
6 - ft_transfer_call関数でのプリペイドガスチェックの欠落 推奨事項 修正済み
9 - 中央集権的な設計のリスク 推奨事項 確認済み

詳細は以下のセクションで提供します。

2.1 ソフトウェアセキュリティ

2.1.1 潜在的な問題1: 精度損失

情報 説明
ステータス バージョン2で修正済み
起因 バージョン1

説明 reward_per_sessionを計算する際に、関数internal_calculate_distributionの125行目で、乗算の前に除算が行われています。

fn internal_calculate_distribution(
        &self,
        farm: &Farm,
        total_staked: Balance,
    ) -> Option<RewardDistribution> {
        if farm.start_date > env::block_timestamp() {
            // Farm hasn't started.
            return None;
        }
        let mut distribution = farm.last_distribution.clone();
        if distribution.undistributed == 0 {
            // Farm has ended.
            return Some(distribution);
        }
        distribution.reward_round = (env::block_timestamp() - farm.start_date) / SESSION_INTERVAL;
        let reward_per_session =
            farm.amount / (farm.end_date - farm.start_date) as u128 * SESSION_INTERVAL as u128;

リスト 2.1: contracts/linear/src/farm.rs

影響 Rust言語では、整数除算は切り捨てられます。この場合、整数に対して乗算の前に除算を行うと、精度が失われる可能性があります。

提案I この計算を変更して、乗算を先に行い、その後除算を実行するようにします。

提案II ファームが追加される際にreward_per_sessionの値を事前に計算します。これは、ファームのamountend_datestart_dateは、オーナーが停止しない限り、ファーミングプロセス中に変更されないためです。

2.2 DeFiセキュリティ

2.2.1 潜在的な問題2: ユーザーの利用可能残高が一時的にロックされる可能性があります

情報 説明
ステータス 確認済み
起因 バージョン1

説明 ユーザーが預けたNEARは、ユーザーのアンステーキング残高に直接追加されます。したがって、ユーザーがunstake/unstake_all関数を呼び出した場合、その額の利用可能NEARは、次の0から8エポックの間ロックされます。

pub(crate) fn internal_deposit(&mut self, amount: Balance) {
		let account_id = env::predecessor_account_id();
		let mut account = self.internal_get_account(&account_id);
		account.unstaked += amount;
		self.internal_save_account(&account_id, &account);
  
		Event::Deposit {
			account_id: &account_id,
			amount: &U128(amount),
			new_unstaked_balance: &U128(account.unstaked),
		}
		.emit();
	} 

リスト 2.2: contracts/linear/src/internal.rs

影響 ユーザーがこのコントラクトのワークフローを認識せず、直接このコントラクトとやり取りした場合、ユーザーの利用可能残高が一時的にロックされる可能性があります。

提案I Account構造体に別の属性(例: available_amount)を追加して、利用可能なNEARを維持します。

プロジェクトからのフィードバック これは意図したものです。基本的に、ステーキングプールの元のインターフェースと設計に従っています。潜在的な問題を解決するために、unstakedから区別するために'unstaking'という別のフィールドを追加し、このアカウントの次のアンステーキングプロセスを開始する前に、'unstaking''unstaked'に移動させることができます。しかし、現時点では、ワークフローをステーキングプールと一致させるために、変更は行いたくありません。回避策として、ユーザーがフロントエンドからアンステーキングを行う際に、アカウントに'unstaked'残高がある場合、UIはまず引き出しを行うようにユーザーに通知します。

2.2.2 潜在的な問題3: 受益者への無制限の報酬配布

情報 説明
ステータス バージョン2で修正済み
起因 バージョン1

説明 このコントラクトは、新しい受益者を設定する際に、assert_valid関数ですべての受益者の総ウェイトをチェックしません。

pub fn set_beneficiary(&mut self, account_id: AccountId, fraction: Fraction) {
		self.assert_owner();
		fraction.assert_valid();
		self.beneficiaries.insert(&account_id, &fraction);
	} 

リスト 2.3: contracts/linear/src/owner.rs

pub fn assert_valid(&self) {
		require!(self.denominator != 0, ERR_FRACTION_BAD_DENOMINATOR);
		require!(
			self.numerator <= self.denominator,
			ERR_FRACTION_BAD_NUMERATOR
		);
	} 

リスト 2.4: contracts/linear/src/utils.rs

影響 受益者の総ウェイトが100%を超えると、受益者のためにミントされたLiNEARは、epoch_update_rewardsアクションの実行後、LiNEARの価格を低下させる可能性があります。

提案I 受益者に配布される総報酬を制限するための合理的な閾値を導入します。

2.2.3 潜在的な問題4: ユーザーのアンステーキングリクエストが時間通りに満たされない可能性があります

情報 説明
ステータス バージョン2で修正済み
起因 バージョン1

説明 get_num_epoch_to_unstake関数から返されるエポック数は、一部のコーナーケースで2倍になる必要があります。例えば、保留中のステータスではないバリデーターのステーキングプールにステークされた総NEARが不足している場合、ユーザーは4エポック後に要求されたアンステーキングされたNEARをすべて引き出すことができません。

pub fn get_num_epoch_to_unstake(&self, _amount: u128) -> EpochHeight {
		// the num of epoches can be doubled or trippled if not enough stake is available
		NUM_EPOCHS_TO_UNLOCK
	} 

リスト 2.5: contracts/linear/src/validator_pool.rs

影響 ユーザーのアンステーキングリクエストは、常に時間通りに満たされない可能性があります。

提案I バリデーターのステーキングプールのステータスに基づいた、ユーザーのアンステーキング待機時間を予測する戦略を実装します。

2.3 追加の推奨事項

2.3.1 `epoch_update_rewards`関数が、無制限の受益者により機能しない可能性があります

情報 説明
ステータス バージョン2で修正済み
起因 バージョン1

説明 受益者の数が無制限です。この場合、internal_distribute_staking_rewards関数を呼び出すepoch_update_rewards関数がガス不足になる可能性があります。

pub fn epoch_update_rewards(&mut self, validator_id: AccountId) {
	let min_gas = GAS_EPOCH_UPDATE_REWARDS + GAS_EXT_GET_BALANCE + GAS_CB_VALIDATOR_GET_BALANCE;
	require!(
		env::prepaid_gas() >= min_gas,
		format!("{}. require at least {:?}", ERR_NO_ENOUGH_GAS, min_gas)
	);

	let validator = self
		.validator_pool
		.get_validator(&validator_id)
		.expect(ERR_VALIDATOR_NOT_EXIST);

	if validator.staked_amount == 0 && validator.unstaked_amount == 0 {
		return;
	}

	validator
		.refresh_total_balance()
		.then(ext_self_action_cb::validator_get_balance_callback(
			validator.account_id,
			env::current_account_id(),
			NO_DEPOSIT,
			GAS_CB_VALIDATOR_GET_BALANCE,
		));
}

リスト 2.6: contracts/linear/src/epoch_actions.rs

/// When there are rewards, a part of them will be
	/// given to executor, manager or treasury by minting new LiNEAR tokens.
	pub(crate) fn internal_distribute_staking_rewards(&mut self, rewards: Balance) {
		let hashmap: HashMap<AccountId, Fraction> = self.internal_get_beneficiaries();
		for (account_id, fraction) in hashmap.iter() {
			let reward_near_amount: Balance = fraction.multiply(rewards);
			// mint extra LiNEAR for him
			self.internal_mint_beneficiary_rewards(&account_id, reward_near_amount);
		}
	} 

リスト 2.7: contract/src/internal.rs

影響 受益者が多すぎると、ガスが制限されるため、epoch_update_rewards関数が機能しない可能性があります。

提案I 受益者の数を制限するための合理的な閾値を導入することが推奨されます。

2.3.2 冗長なコード

情報 説明
ステータス 確認済み
起因 バージョン1

説明 storage_deposit関数がこのパラメータに対してロジックを実装していないため、registration_onlyパラメータは冗長です。

fn storage_deposit(
		&mut self,
		account_id: Option<AccountId>,
		registration_only: Option<bool>,
	) -> StorageBalance {
		let amount: Balance = env::attached_deposit();
		let account_id = account_id.unwrap_or_else(env::predecessor_account_id);
		if let Some(account) = self.accounts.get(&account_id) {
			log!("The account is already registered, refunding the deposit");
			if amount > 0 {
				Promise::new(env::predecessor_account_id()).transfer(amount);
			}
		} else {
			let min_balance = self.storage_balance_bounds().min.0;
			if amount < min_balance {
				env::panic_str("The attached deposit is less than the minimum storage balance");
			}
  
			self.internal_register_account(&account_id);
			let refund = amount - min_balance;
			if refund > 0 {
				Promise::new(env::predecessor_account_id()).transfer(refund);
			}
		}
		self.internal_storage_balance_of(&account_id).unwrap()
	} 

リスト 2.8: contracts/linear/src/fungible_token/storage.rs

提案I storage_deposit関数で、この未使用のパラメータを削除することが推奨されます。

プロジェクトからのフィードバック その通りです。near-contract-standardsクレートの標準FT実装でも同様です。標準のstorage_deposit(account_id, registration_only)インターフェースとの一貫性を保つために、未使用のregistration_onlyパラメータを保持します。

2.3.3 `ft_transfer_call`関数でのプリペイドガスチェックの欠落

情報 説明
ステータス バージョン2で修正済み
起因 バージョン1

説明 ft_on_transferおよびft_resolve_transferのターゲット関数に十分であることを確認するために、prepaid_gasをチェックする必要があります。

#[payable]
	fn ft_transfer_call(
		&mut self,
		receiver_id: AccountId,
		amount: U128,
		memo: Option<String>,
		msg: String,
	) -> PromiseOrValue<U128> {
		assert_one_yocto();
		let sender_id = env::predecessor_account_id();
		let amount = amount.into();
		self.internal_ft_transfer(&sender_id, &receiver_id, amount, memo);
		// Initiating receiver's call and the callback
		ext_fungible_token_receiver::ft_on_transfer(
			sender_id.clone(),
			amount.into(),
			msg,
			receiver_id.clone(),
			NO_DEPOSIT,
			env::prepaid_gas() - GAS_FOR_FT_TRANSFER_CALL,
		)
		.then(ext_ft_self::ft_resolve_transfer(
			sender_id,
			receiver_id,
			amount.into(),
			env::current_account_id(),
			NO_DEPOSIT,
			GAS_FOR_RESOLVE_TRANSFER,
		))
		.into()
	}
 

リスト 2.9: contracts/linear/src/fungible_token/core.rs

提案I prepaid_gasをチェックします。

2.3.4 中央集権的な設計のリスク

情報 説明
ステータス 確認済み
起因 バージョン1

説明 このプロジェクトには、潜在的な中央集権化の問題があります。

提案I コントラクトに、マルチシグネチャやDAOなどの分散化設計を導入することが推奨されます。

プロジェクトからのフィードバック I はい。これはgithub.com/linear-protocol/LiNEAR/issues/60で言及されている計画通りです。

提案II プロジェクトオーナーは、OWNER_ROLE/MANAGERS_ROLEの秘密鍵のセキュリティを確保し、単一障害点のリスクを軽減するためにマルチシグネチャスキームを使用する必要があります。

プロジェクトからのフィードバック II はい。単一障害点のリスクを軽減するためのセキュリティポリシーに取り組んできました。

3. 通知と注記

3.1 免責事項

この監査レポートは、投資アドバイスまたは個人的な推奨事項を構成するものではありません。トークン、トークンセール、またはその他の製品、サービス、またはその他の資産の潜在的な経済性を考慮しておらず、考慮されていると解釈されるべきではありません。いかなるエンティティも、トークン、製品、サービス、またはその他の資産の購入または販売の決定を行う目的を含め、いかなる方法でもこのレポートに依存してはなりません。

この監査レポートは、特定のプロジェクトまたはチームの推奨ではなく、レポートはいかなる特定のプロジェクトのセキュリティも保証しません。この監査は、すべてのセキュリティ問題を検出することに保証を与えるものではありません。つまり、評価結果は、さらなるセキュリティ問題の発見がないことを保証するものではありません。1回の監査では包括的とは見なされないため、スマートコントラクトのセキュリティを確保するために、常に独立した監査と公開バグバウンティプログラムの実施を推奨します。

この監査の範囲は、セクション1.1に記載されているコードに限定されます。明示的に指定されていない限り、言語自体のセキュリティ(例: Solidity言語)、基盤となるコンパイラツールチェーン、およびコンピューティングインフラストラクチャは範囲外です。

3.2 監査手順

監査は、以下の手順に従って実行します。

  • 脆弱性検出 まず、自動コードアナライザーでスマートコントラクトをスキャンし、その後、それらによって報告された問題を、手動で検証(却下または確認)します。

  • 意味解析 スマートコントラクトのビジネスロジックを調査し、自動ファジングツール(当社の研究チームによって開発された)を使用して、潜在的な脆弱性についてさらに調査します。また、独立した監査人とともに、可能性のある攻撃シナリオを手動で分析し、結果を相互に確認します。

  • 推奨事項 ガス最適化、コードスタイルなど、良いプログラミングプラクティスの観点から、開発者に役立つアドバイスを提供します。

主な具体的なチェックポイントを以下に示します。

3.2.1 ソフトウェアセキュリティ

  • 再入可能性

  • DoS(サービス拒否)

  • アクセス制御

  • データ処理とデータフロー

  • 例外処理

  • 信頼できない外部呼び出しと制御フロー

  • 初期化の一貫性

  • イベント操作

  • エラーが発生しやすい乱数

  • プロキシシステムの不適切な使用

3.2.2 DeFiセキュリティ

  • 意味の一貫性

  • 機能の一貫性

  • アクセス制御

  • ビジネスロジック

  • トークン操作

  • 緊急メカニズム

  • オラクルセキュリティ

  • ホワイトリストとブラックリスト

  • 経済的影響

  • バッチ転送

3.2.3 NFTセキュリティ

  • 重複アイテム

  • トークン受信者の検証

  • オフチェーンメタデータセキュリティ

3.2.4 追加の推奨事項

  • ガス最適化

  • コード品質とスタイル

::: 注記 前述のチェックポイントは主なものです。プロジェクトの機能に応じて、監査プロセス中にさらに多くのチェックポイントを使用する場合があります。 :::

Sign up for the latest updates