Sicherheitsauditbericht für LiNEAR

Dies ist der Sicherheitsauditbericht, den wir im April 2022 für LiNEAR durchgeführt haben.

Sicherheitsauditbericht für LiNEAR

Berichtsmanifest

Element Beschreibung
Auftraggeber LiNEAR Protocol
Zielobjekt LiNEAR

Versionshistorie

Version Datum Beschreibung
1.0 1. April 2022 Erste Veröffentlichung

1. Einleitung

1.1 Über Zielverträge

Information Beschreibung
Typ Smart Contract
Sprache Rust
Ansatz Semi-automatische und manuelle Verifizierung

Das Repository, das geprüft wurde, umfasst LiNEAR ^1.

Der Prüfungsprozess ist iterativ. Insbesondere werden wir die Commits prüfen, die die entdeckten Probleme beheben. Wenn es neue Probleme gibt, werden wir diesen Prozess fortsetzen. Die Commit-SHA-Werte während der Prüfung sind unten aufgeführt. Unser Prüfbericht ist für die Erstversion (d.h. Version 1) sowie für neuen Code (in den folgenden Versionen) zur Behebung von Problemen im Prüfbericht verantwortlich.

1.2 Sicherheitsmodell

Zur Risikobewertung folgen wir den Standards oder Vorschlägen, die sowohl von der Industrie als auch von der Wissenschaft weithin angenommen werden, einschließlich der OWASP Risk Rating Methodology ^2 und der Common Weakness Enumeration ^3. Die gesamte Schwere des Risikos wird durch Wahrscheinlichkeit und Auswirkung bestimmt. Insbesondere wird die Wahrscheinlichkeit verwendet, um abzuschätzen, wie wahrscheinlich es ist, dass eine bestimmte Schwachstelle von einem Angreifer entdeckt und ausgenutzt werden kann, während die Auswirkung verwendet wird, um die Folgen einer erfolgreichen Ausnutzung zu messen.

In diesem Bericht werden sowohl Wahrscheinlichkeit als auch Auswirkung in zwei Ratings kategorisiert, d.h. hoch und niedrig, und ihre Kombinationen sind in Tabelle 1.1 dargestellt.

Dementsprechend sind die in diesem Bericht gemessenen Risikoschweregrade in drei Kategorien eingeteilt: Hoch, Mittel, Niedrig. Der Vollständigkeit halber wird Unbestimmt auch verwendet, um Umstände abzudecken, bei denen das Risiko nicht gut bestimmt werden kann.

Darüber hinaus fällt der Status eines entdeckten Problems in eine der folgenden vier Kategorien:

  • Unbestimmt Noch keine Antwort.

  • Bestätigt Das Problem wurde vom Kunden erhalten, aber noch nicht bestätigt.

  • Bestätigt Das Problem wurde vom Kunden anerkannt, aber noch nicht behoben.

  • Behoben Das Problem wurde vom Kunden bestätigt und behoben.

2. Erkenntnisse

Insgesamt finden wir 4 potenzielle Probleme im Smart Contract. Wir haben außerdem 4 Empfehlungen, wie folgt:

  • Hohes Risiko: 0

  • Mittleres Risiko: 2

  • Niedriges Risiko: 2

  • Empfehlungen: 4

Details finden Sie in den folgenden Abschnitten.

2.1 Softwaresicherheit

2.1.1 Potenzielles Problem 1: Präzisionsverlust

Information Beschreibung
Status In Version 2 behoben
Eingeführt durch Version 1

Beschreibung In Zeile 125 der Funktion internal_calculate_distribution wird bei der Berechnung der Variablen reward_per_session vor der Multiplikation dividiert.

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;

Auflistung 2.1: contracts/linear/src/farm.rs

Auswirkung Division schneidet bei ganzen Zahlen in der Rust-Sprache ab. In diesem Fall kann die Division vor der Multiplikation für ganze Zahlen zu Präzisionsverlust führen.

Vorschlag I Modifizieren Sie diese Berechnung, um die Multiplikation vor der Division durchzuführen.

Vorschlag II Berechnen Sie den Wert von reward_per_session im Voraus, wenn ein Farm hinzugefügt wird. Dies liegt daran, dass farm.amount, farm.end_date und farm.start_date während des Farmings nicht geändert werden, es sei denn, der Eigentümer stoppt es.

2.2 DeFi-Sicherheit

2.2.1 Potenzielles Problem 2: Verfügbares Guthaben des Nutzers kann vorübergehend gesperrt sein

Information Beschreibung
Status Bestätigt
Eingeführt durch Version 1

Beschreibung Die vom Nutzer eingezahlten NEARs werden direkt zum nicht eingesetzten Guthaben des Nutzers addiert. Daher werden, wenn der Nutzer die Funktionen unstake/unstake_all aufruft, diese Menge an verfügbaren NEARs für die nächsten 0 bis 8 Epochen gesperrt sein.

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();
	} 

Auflistung 2.2: contracts/linear/src/internal.rs

Auswirkung Wenn ein Nutzer mit diesem Vertragsablauf nicht vertraut ist und direkt mit diesem Vertrag interagiert, kann das verfügbare Guthaben des Nutzers vorübergehend gesperrt sein.

Vorschlag I Fügen Sie der Struktur Account ein weiteres Attribut (z.B. available_amount) hinzu, um die verfügbaren NEARs zu verwalten.

Feedback vom Projekt Dies ist beabsichtigt und folgt im Wesentlichen der ursprünglichen Schnittstelle und dem Design des Staking-Pools. Um das potenzielle Problem zu lösen, könnten wir Verbesserungen vornehmen, um ein weiteres Feld 'unstaking' hinzuzufügen, um es von 'unstaked' zu unterscheiden, und 'unstaking' vor dem Start des nächsten Entnahmevorgangs für dieses Konto in 'unstaked' zu verschieben. Vorerst möchten wir die Änderung jedoch nicht vornehmen, um den Arbeitsablauf konsistent mit dem Staking-Pool zu halten. Als Workaround werden bei der Entnahme über das Frontend die Benutzeroberfläche die Nutzer daran erinnern, zuerst abzuheben, wenn sie einen 'nicht eingesetzten' Betrag in ihrem Konto haben.

2.2.2 Potenzielles Problem 3: Unbegrenzte Belohnungsverteilung an Begünstigte

Information Beschreibung
Status In Version 2 behoben
Eingeführt durch Version 1

Beschreibung Dieser Vertrag prüft nicht das Gesamtgewicht aller Begünstigten in der Funktion assert_valid, wenn ein neuer Begünstigter eingerichtet wird.

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

Auflistung 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
		);
	} 

Auflistung 2.4: contracts/linear/src/utils.rs

Auswirkung Sobald das Gesamtgewicht der Begünstigten 100 % übersteigt, können die für Begünstigte geprägten LiNEARs den Preis von LiNEAR nach Ausführung der Aktion epoch_update_rewards verringern.

Vorschlag I Führen Sie einen angemessenen Schwellenwert ein, um die an Begünstigte verteilten Gesamtbelohnungen zu begrenzen.

2.2.3 Potenzielles Problem 4: Anfragen zur Entnahme von Nutzern können möglicherweise nicht rechtzeitig erfüllt werden

Information Beschreibung
Status In Version 2 behoben
Eingeführt durch Version 1

Beschreibung Beschreibung Die Anzahl der von der Funktion get_num_epoch_to_unstake zurückgegebenen Epochen sollte in einigen Eckfällen verdoppelt werden. Wenn beispielsweise die gesamten NEARs, die in Validator-Staking-Pools gestakt sind und sich nicht im Wartezustand befinden, nicht ausreichen, können die Nutzer nicht alle angeforderten nicht eingesetzten NEARs nach 4 Epochen abheben.

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
	} 

Auflistung 2.5: contracts/linear/src/validator_pool.rs

Auswirkung Die Entnahmeaufträge der Nutzer können möglicherweise nicht immer rechtzeitig erfüllt werden.

Vorschlag I Implementieren Sie eine Strategie, um die Wartezeit für die Entnahme des Nutzers basierend auf dem Status der Validator-Staking-Pools vorherzusagen.

2.3 Zusätzliche Empfehlung

2.3.1 Funktion epoch_update_rewards funktioniert möglicherweise aufgrund unbegrenzter Begünstigter nicht

Information Beschreibung
Status In Version 2 behoben
Eingeführt durch Version 1

Beschreibung Die Anzahl der Begünstigten ist unbegrenzt. In diesem Fall kann die Funktion epoch_update_rewards, die die Funktion internal_distribute_staking_rewards aufruft, das Gas aufbrauchen.

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,
		));
}

Auflistung 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);
		}
	} 

Auflistung 2.7: contract/src/internal.rs

Auswirkung Die Funktion epoch_update_rewards funktioniert aufgrund von begrenztem Gas möglicherweise nicht, wenn es zu viele Begünstigte gibt.

Vorschlag I Es wird empfohlen, einen angemessenen Schwellenwert einzuführen, um die Anzahl der Begünstigten zu begrenzen.

2.3.2 Redundanter Code

Information Beschreibung
Status Bestätigt
Eingeführt durch Version 1

Beschreibung Der Parameter registration_only ist redundant, da die Funktion storage_deposit keine Logik für diesen Parameter implementiert.

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()
	} 

Auflistung 2.8: contracts/linear/src/fungible_token/storage.rs

Vorschlag I Es wird empfohlen, diesen ungenutzten Parameter in der Funktion storage_deposit zu entfernen.

Feedback vom Projekt Das stimmt. Dasselbe gilt für die Standard-FT-Implementierung im near-contract-standards-Crate. Wir werden den ungenutzten Parameter registration_only beibehalten, um die Konsistenz mit der Standard-Schnittstelle storage_deposit(account_id, registration_only) zu wahren.

2.3.3 Fehlende Überprüfung des vorausbezahlten Gas in der Funktion ft_transfer_call

Information Beschreibung
Status In Version 2 behoben
Eingeführt durch Version 1

Beschreibung Das vorausbezahlte Gas sollte überprüft werden, um sicherzustellen, dass es für die Ziel Funktionen, einschließlich ft_on_transfer und ft_resolve_transfer, ausreichend ist.

#[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()
	}
 

Auflistung 2.9: contracts/linear/src/fungible_token/core.rs

Vorschlag I Überprüfen Sie das vorausbezahlte Gas.

2.3.4 Das Risiko eines zentralisierten Designs

Information Beschreibung
Status Bestätigt
Eingeführt durch Version 1

Beschreibung Dieses Projekt hat potenzielle Zentralisierungsprobleme.

Vorschlag I Es wird empfohlen, ein dezentrales Design in den Vertrag einzuführen, wie z.B. Multi-Signatur oder DAO.

Feedback vom Projekt I Ja. Dies ist wie in github.com/linear-protocol/LiNEAR/issues/60 erwähnt geplant.

Vorschlag II Der Projektinhaber muss die Sicherheit des privaten Schlüssels der ROLLE DES EIGENTÜMERS/DER MANAGER-ROLLE gewährleisten und ein Multi-Signatur-Schema verwenden, um das Risiko eines Single Point of Failure zu verringern.

Feedback vom Projekt II Ja. Wir arbeiten an Sicherheitsrichtlinien, um die Risiken eines Single Point of Failure zu verringern.

3. Hinweise und Bemerkungen

3.1 Haftungsausschluss

Dieser Prüfbericht stellt keine Anlageberatung oder persönliche Empfehlung dar. Er berücksichtigt nicht und sollte nicht als Berücksichtigung oder Einflussnahme auf die potenzielle Wirtschaftlichkeit eines Tokens, eines Token-Verkaufs oder eines anderen Produkts, einer Dienstleistung oder eines anderen Vermögenswerts interpretiert werden. Kein Unternehmen sollte sich auf diesen Bericht in irgendeiner Weise verlassen, auch nicht zum Zwecke der Entscheidungsfindung über den Kauf oder Verkauf eines Tokens, Produkts, einer Dienstleistung oder eines anderen Vermögenswerts.

Dieser Prüfbericht ist keine Billigung eines bestimmten Projekts oder Teams, und der Bericht garantiert nicht die Sicherheit eines bestimmten Projekts. Diese Prüfung gibt keine Gewährleistung für die Entdeckung aller Sicherheitsprobleme von Smart Contracts, d.h. das Bewertungsergebnis garantiert nicht die Nichtexistenz weiterer Erkenntnisse von Sicherheitsproblemen. Da eine Prüfung nicht als umfassend betrachtet werden kann, empfehlen wir stets, unabhängige Prüfungen und ein öffentliches Bug-Bounty-Programm durchzuführen, um die Sicherheit von Smart Contracts zu gewährleisten.

Der Umfang dieser Prüfung beschränkt sich auf den in Abschnitt 1.1 genannten Code. Sofern nicht ausdrücklich angegeben, fallen die Sicherheit der Sprache selbst (z. B. die Solidity-Sprache), die zugrunde liegende Kompilierwerkzeugkette und die Computing-Infrastruktur aus dem Umfang heraus.

3.2 Prüfverfahren

Wir führen die Prüfung gemäß dem folgenden Verfahren durch.

  • Schwachstellenerkennung Wir scannen zunächst Smart Contracts mit automatischen Code-Analysatoren und überprüfen dann manuell (lehnen ab oder bestätigen) die von ihnen gemeldeten Probleme.

  • Semantische Analyse Wir untersuchen die Geschäftslogik von Smart Contracts und führen weitere Untersuchungen zu möglichen Schwachstellen mit einem automatischen Fuzzing-Tool (entwickelt von unserem Forschungsteam) durch. Wir analysieren auch manuell mögliche Angriffsszenarien mit unabhängigen Prüfern, um die Ergebnisse abzugleichen.

  • Empfehlung Wir geben Entwicklern nützliche Ratschläge aus der Perspektive guter Programmierpraktiken, einschließlich Gasoptimierung, Code-Stil usw.

Die wichtigsten konkreten Prüfpunkte zeigen wir im Folgenden.

3.2.1 Softwaresicherheit

  • Reentrancy

  • Denial of Service (DoS)

  • Zugriffskontrolle

  • Datenverarbeitung und Datenfluss

  • Ausnahmebehandlung

  • Nicht vertrauenswürdige externe Aufrufe und Kontrollfluss

  • Konsistenz der Initialisierung

  • Ereignisoperationen

  • Fehleranfällige Zufälligkeit

  • Unsachgemäße Verwendung des Proxy-Systems

3.2.2 DeFi-Sicherheit

  • Semantische Konsistenz

  • Funktionale Konsistenz

  • Zugriffskontrolle

  • Geschäftslogik

  • Token-Operation

  • Notfallmechanismus

  • Oracle-Sicherheit

  • Whitelist und Blacklist

  • Wirtschaftliche Auswirkungen

  • Batch-Übertragung

3.2.3 NFT-Sicherheit

  • Duplizierter Gegenstand

  • Verifizierung des Token-Empfängers

  • Sicherheit von Off-Chain-Metadaten

3.2.4 Zusätzliche Empfehlung

  • Gasoptimierung

  • Codequalität und Stil

::: Hinweis Die vorherigen Prüfpunkte sind die wichtigsten. Wir können während des Prüfprozesses weitere Prüfpunkte verwenden, je nach Funktionalität des Projekts. :::

Sign up for the latest updates