Bug WikiMemory & Resource ManagementNull pointer dereference
6 examples

Null pointer dereference

Null references accessed, causing runtime exceptions.

[ FAQ1 ]

What is null pointer dereference?

A null pointer dereference occurs when code tries to access data or methods through a pointer set to null (e.g., nullptr in C++ or null in Java). Dereferencing null pointers leads to undefined behavior, typically causing immediate runtime errors, crashes (like segmentation faults), or memory access violations. This common programming mistake results from missing or improperly implemented null checks, leaving applications vulnerable to instability, unexpected termination, and security issues.
[ FAQ2 ]

what happens when you dereference a null pointer

A null pointer dereference occurs when code tries to access data or methods through a pointer set to null (e.g., nullptr in C++ or null in Java). Dereferencing null pointers leads to undefined behavior, typically causing immediate runtime errors, crashes (like segmentation faults), or memory access violations. This common programming mistake results from missing or improperly implemented null checks, leaving applications vulnerable to instability, unexpected termination, and security issues.
diff block
+/*
+ * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/
+ *
+ * Copyright (c) 2002 - 2019 Bruce Mayhew
+ *
+ * This program is free software; you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program; if
+ * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Getting Source
+ * ==============
+ *
+ * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects.
+ */
+
+package org.owasp.webgoat.lessons.idor;
+
+import static org.owasp.webgoat.container.assignments.AttackResultBuilder.failed;
+import static org.owasp.webgoat.container.assignments.AttackResultBuilder.success;
+
+import org.owasp.webgoat.container.assignments.AssignmentEndpoint;
+import org.owasp.webgoat.container.assignments.AssignmentHints;
+import org.owasp.webgoat.container.assignments.AttackResult;
+import org.owasp.webgoat.container.session.LessonSession;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@AssignmentHints({
+ "idor.hints.otherProfile1",
+ "idor.hints.otherProfile2",
+ "idor.hints.otherProfile3",
+ "idor.hints.otherProfile4",
+ "idor.hints.otherProfile5",
+ "idor.hints.otherProfile6",
+ "idor.hints.otherProfile7",
+ "idor.hints.otherProfile8",
+ "idor.hints.otherProfile9"
+})
+public class IDOREditOtherProfile implements AssignmentEndpoint {
+
+ private final LessonSession userSessionData;
+
+ public IDOREditOtherProfile(LessonSession lessonSession) {
+ this.userSessionData = lessonSession;
+ }
+
+ @PutMapping(path = "/IDOR/profile/{userId}", consumes = "application/json")
+ @ResponseBody
+ public AttackResult completed(
+ @PathVariable("userId") String userId, @RequestBody UserProfile userSubmittedProfile) {
+
+ String authUserId = (String) userSessionData.getValue("idor-authenticated-user-id");
+ // this is where it starts ... accepting the user submitted ID and assuming it will be the same
+ // as the logged in userId and not checking for proper authorization
+ // Certain roles can sometimes edit others' profiles, but we shouldn't just assume that and let
+ // everyone, right?
+ // Except that this is a vulnerable app ... so we will
+ UserProfile currentUserProfile = new UserProfile(userId);
+ if (userSubmittedProfile.getUserId() != null
+ && !userSubmittedProfile.getUserId().equals(authUserId)) {
Greptile
greptile
logic: Potential null pointer dereference if userSubmittedProfile is null since null check is only on getUserId()
suggested fix
+ if (userSubmittedProfile != null && userSubmittedProfile.getUserId() != null
&& !userSubmittedProfile.getUserId().equals(authUserId)) {
diff block
@Override
protected ITemplateResource computeTemplateResource(IEngineConfiguration configuration, String ownerTemplate, String template, String resourceName, String characterEncoding, Map<String, Object> templateResolutionAttributes) {
- var templateName = resourceName.substring(PREFIX.length());;
+ var templateName = resourceName.substring(PREFIX.length());
byte[] resource = resources.get(templateName);
if (resource == null) {
try {
- resource = resourceLoader.getResource("classpath:/html/" + templateName + ".html").getInputStream().readAllBytes();
+ resource = resourceLoader.getResource("classpath:/" + templateName).getInputStream().readAllBytes();
Greptile
greptile
logic: Potential null pointer dereference if resource is null after IOException. The resource will be cached as null and cause NPE on line 80.
suggested fix
resource = resourceLoader.getResource("classpath:/" + templateName).getInputStream().readAllBytes();
+ } catch (IOException e) {
+ log.error("Unable to find lesson HTML: {}", template);
+ throw new IllegalStateException("Failed to load lesson template: " + templateName, e);
diff block
|| dir->isUnknown();
}
+bool
+Network::isClock(const Pin *pin) const
+{
+ const LibertyPort *port = libertyPort(pin);
+ return port && port->isClock();
+}
+
+bool
+Network::isRiseEdgeTriggered(const Pin *pin) const
+{
+ LibertyPort *port = libertyPort(pin);
+ if (!port) {
+ return false;
+ }
+ LibertyCell *cell = port->libertyCell();
+ const Sequential *seq = cell->outputPortSequential(port);
Greptile
greptile
logic: Potential null pointer dereference if libertyCell() returns null after port check.
suggested fix
LibertyCell *cell = port->libertyCell();
+ if (!cell) {
return false;
}
const Sequential *seq = cell->outputPortSequential(port);
diff block
+use libsqlite3_sys::*;
+use std::ffi::{CStr, CString};
+use std::os::raw::c_char;
+use std::ptr;
+
+use crate::impls::pages::utils::{FdbVfsError, SQLITE_OK, SQLITE_OPEN_CREATE, SQLITE_OPEN_READWRITE};
+
+/// Open a SQLite database with our FDB VFS
+pub fn open_sqlite_db(db_name: &str, vfs_name: &str) -> Result<*mut sqlite3, FdbVfsError> {
+ let mut db: *mut sqlite3 = ptr::null_mut();
+
+ tracing::debug!("Attempting to open database with FDB VFS: {}", vfs_name);
+
+ // Create C strings for SQLite API
+ let c_db_name = CString::new(db_name).expect("CString conversion failed");
+
+ // Create CString for the VFS name to keep it alive during the function call
+ let c_vfs_name = CString::new(vfs_name).expect("CString conversion failed");
+
+ // Open the database with our custom VFS
+ let result = unsafe {
+ // Use sqlite3_open_v2 to specify our VFS
+ sqlite3_open_v2(
+ c_db_name.as_ptr(),
+ &mut db,
+ SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
+ c_vfs_name.as_ptr(),
+ )
+ };
+
+ if result != SQLITE_OK {
+ let err_msg = get_sqlite_error(db);
+ unsafe { sqlite3_close(db) };
Greptile
greptile
logic: Potential null pointer dereference if db is null when getting the error message. Should check if db is null before calling get_sqlite_error and sqlite3_close.
diff block
+use std::str::FromStr;
+use std::sync::Arc;
+
+use base64::{engine::general_purpose, Engine};
+use chrono::{DateTime, Utc};
+use public_suffix::{EffectiveTLDProvider, DEFAULT_PROVIDER};
+use thiserror::Error;
+use url::Url;
+
+use crate::constants::{
+ COOKIELESS_DISTINCT_ID_PREFIX, IDENTIFIES_TTL_SECONDS, SALT_TTL_SECONDS, SESSION_INACTIVITY_MS,
+ SESSION_TTL_SECONDS, TIMEZONE_FALLBACK,
+};
+use crate::hash::{do_hash, HashError};
+use crate::salt_cache::{SaltCache, SaltCacheError};
+use common_redis::Client as RedisClient;
+
+#[derive(Debug, Error)]
+pub enum CookielessManagerError {
+ #[error("Salt cache error: {0}")]
+ SaltCacheError(#[from] SaltCacheError),
+
+ #[error("Hash error: {0}")]
+ HashError(#[from] HashError),
+
+ #[error("Invalid URL: {0}")]
+ UrlParseError(#[from] url::ParseError),
+
+ #[error("Cookieless mode is disabled")]
+ Disabled,
+
+ #[error("Missing required property: {0}")]
+ MissingProperty(String),
+
+ #[error("Invalid timestamp: {0}")]
+ InvalidTimestamp(String),
+
+ #[error("Chrono error: {0}")]
+ ChronoError(#[from] chrono::ParseError),
+
+ #[error("Timezone error: {0}")]
+ TimezoneError(String),
+
+ #[error("Invalid identify count: {0}")]
+ InvalidIdentifyCount(String),
+
+ #[error("Redis error: {0}")]
+ RedisError(String),
+}
+
+/// Configuration for the CookielessManager
+#[derive(Debug, Clone)]
+pub struct CookielessConfig {
+ /// Whether cookieless mode is disabled
+ pub disabled: bool,
+ /// Whether to force stateless mode
+ pub force_stateless_mode: bool,
+ /// TTL for identifies (in seconds)
+ pub identifies_ttl_seconds: u64,
+ /// TTL for sessions (in seconds)
+ pub session_ttl_seconds: u64,
+ /// TTL for salts (in seconds)
+ pub salt_ttl_seconds: u64,
+ /// Session inactivity timeout (in milliseconds)
+ pub session_inactivity_ms: u64,
+}
+
+impl Default for CookielessConfig {
+ fn default() -> Self {
+ Self {
+ disabled: false,
+ force_stateless_mode: false,
+ identifies_ttl_seconds: IDENTIFIES_TTL_SECONDS,
+ session_ttl_seconds: SESSION_TTL_SECONDS,
+ salt_ttl_seconds: SALT_TTL_SECONDS,
+ session_inactivity_ms: SESSION_INACTIVITY_MS,
+ }
+ }
+}
+
+/// Parameters for computing a hash
+#[derive(Debug, Clone)]
+pub struct HashParams<'a> {
+ /// Timestamp in milliseconds
+ pub timestamp_ms: u64,
+ /// Event timezone
+ pub event_time_zone: Option<&'a str>,
+ /// Team timezone
+ pub team_time_zone: &'a str,
+ /// Team ID
+ pub team_id: u64,
+ /// IP address
+ pub ip: &'a str,
+ /// Host
+ pub host: &'a str,
+ /// User agent
+ pub user_agent: &'a str,
+ /// Counter value
+ pub n: u64,
+ /// Additional data to include in the hash
+ pub hash_extra: &'a str,
+}
+
+/// Data for an event to be processed by the cookieless manager
+#[derive(Debug, Clone)]
+pub struct EventData<'a> {
+ /// IP address
+ pub ip: &'a str,
+ /// Timestamp in milliseconds
+ pub timestamp_ms: u64,
+ /// Host
+ pub host: &'a str,
+ /// User agent
+ pub user_agent: &'a str,
+ /// Event timezone (optional)
+ pub event_time_zone: Option<&'a str>,
+ /// Additional data to include in the hash (optional)
+ pub hash_extra: Option<&'a str>,
+ /// Team ID
+ pub team_id: u64,
+ /// Team timezone (optional)
+ pub team_time_zone: Option<&'a str>,
+}
+
+/// Manager for cookieless tracking
+pub struct CookielessManager {
+ /// Configuration for the manager
+ pub config: CookielessConfig,
+ /// Salt cache for retrieving and storing salts
+ salt_cache: SaltCache,
+ /// Redis client for direct access
+ redis_client: Arc<dyn RedisClient + Send + Sync>,
+}
+
+impl CookielessManager {
+ /// Create a new CookielessManager
+ pub fn new(config: CookielessConfig, redis_client: Arc<dyn RedisClient + Send + Sync>) -> Self {
+ let salt_cache = SaltCache::new(redis_client.clone(), Some(config.salt_ttl_seconds));
+
+ Self {
+ config,
+ salt_cache,
+ redis_client,
+ }
+ }
+
+ /// Get the salt for a specific day (YYYY-MM-DD format)
+ pub async fn get_salt_for_day(
+ &self,
+ yyyymmdd: &str,
+ ) -> Result<Vec<u8>, CookielessManagerError> {
+ Ok(self.salt_cache.get_salt_for_day(yyyymmdd).await?)
+ }
+
+ /// Clear the salt cache
+ pub fn clear_cache(&self) {
+ self.salt_cache.clear_cache();
+ }
+
+ /// Compute a cookieless distinct ID for an event
+ pub async fn compute_cookieless_distinct_id(
+ &self,
+ event_data: EventData<'_>,
+ ) -> Result<String, CookielessManagerError> {
+ // If cookieless mode is disabled, return an error
+ if self.config.disabled {
+ return Err(CookielessManagerError::Disabled);
+ }
+
+ // Validate required fields
+ if event_data.ip.is_empty() {
+ return Err(CookielessManagerError::MissingProperty("ip".to_string()));
+ }
+ if event_data.host.is_empty() {
+ return Err(CookielessManagerError::MissingProperty("host".to_string()));
+ }
+ if event_data.user_agent.is_empty() {
+ return Err(CookielessManagerError::MissingProperty(
+ "user_agent".to_string(),
+ ));
+ }
+
+ // Get the team timezone or use UTC as fallback
+ let team_time_zone = event_data.team_time_zone.unwrap_or(TIMEZONE_FALLBACK);
+
+ // First, compute the hash with n=0 to get the base hash
+ let hash_params = HashParams {
+ timestamp_ms: event_data.timestamp_ms,
+ event_time_zone: event_data.event_time_zone,
+ team_time_zone,
+ team_id: event_data.team_id,
+ ip: event_data.ip,
+ host: event_data.host,
+ user_agent: event_data.user_agent,
+ n: 0,
+ hash_extra: event_data.hash_extra.unwrap_or(""),
+ };
+
+ // Compute the base hash
+ let base_hash = self.do_hash_for_day(hash_params.clone()).await?;
+
+ // If we're in stateless mode, use the base hash directly
+ if self.config.force_stateless_mode {
+ return Ok(Self::hash_to_distinct_id(&base_hash));
+ }
+
+ // Get the number of identify events for this hash
+ let n = self
+ .get_identify_count(&base_hash, event_data.team_id)
+ .await?;
+
+ // If n is 0, we can use the base hash
+ if n == 0 {
+ return Ok(Self::hash_to_distinct_id(&base_hash));
+ }
+
+ // Otherwise, recompute the hash with the correct n value
+ let hash_params_with_n = HashParams { n, ..hash_params };
+
+ // Compute the final hash
+ let final_hash = self.do_hash_for_day(hash_params_with_n).await?;
+
+ // Convert the hash to a distinct ID
+ Ok(Self::hash_to_distinct_id(&final_hash))
+ }
+
+ /// Compute a hash for a specific day
+ pub async fn do_hash_for_day(
+ &self,
+ params: HashParams<'_>,
+ ) -> Result<Vec<u8>, CookielessManagerError> {
+ let yyyymmdd = to_yyyy_mm_dd_in_timezone_safe(
+ params.timestamp_ms,
+ params.event_time_zone,
+ params.team_time_zone,
+ )?;
+ let salt = self.get_salt_for_day(&yyyymmdd).await?;
+
+ // Extract the root domain from the host
+ let root_domain = extract_root_domain(params.host)?;
+
+ // Compute the hash
+ Ok(do_hash(
+ &salt,
+ params.team_id,
+ params.ip,
+ &root_domain,
+ params.user_agent,
+ params.n,
+ params.hash_extra,
+ )?)
+ }
+
+ /// Convert a hash to a distinct ID
+ pub fn hash_to_distinct_id(hash: &[u8]) -> String {
+ format!(
+ "{}_{}",
+ COOKIELESS_DISTINCT_ID_PREFIX,
+ general_purpose::STANDARD.encode(hash).trim_end_matches('=')
+ )
+ }
+
+ /// Get the number of identify events for a specific hash
+ /// This is used to ensure that a user that logs in and out doesn't collide with themselves
+ pub async fn get_identify_count(
+ &self,
+ hash: &[u8],
+ team_id: u64,
+ ) -> Result<u64, CookielessManagerError> {
+ // If we're in stateless mode, always return 0
+ if self.config.force_stateless_mode {
+ return Ok(0);
+ }
+
+ // Get the Redis key for the identify count
+ let redis_key = get_redis_identifies_key(hash, team_id);
+
+ // Try to get the count from Redis
+ match self.redis_client.get(redis_key).await {
+ Ok(count_str) => {
+ // Parse the count string to a u64
+ count_str
+ .parse::<u64>()
+ .map_err(|e| CookielessManagerError::InvalidIdentifyCount(e.to_string()))
+ }
+ Err(common_redis::CustomRedisError::NotFound) => {
+ // If the key doesn't exist, the count is 0
+ Ok(0)
+ }
+ Err(e) => {
+ // If there's a Redis error, propagate it
+ Err(CookielessManagerError::RedisError(e.to_string()))
+ }
+ }
+ }
+}
+
+/// Extract the root domain from a host
+fn extract_root_domain(host: &str) -> Result<String, CookielessManagerError> {
+ // If the host contains a protocol, extract just the host part
+ let host_str = if host.contains("://") {
+ // Parse the URL to extract just the host
+ let url = Url::parse(host)?;
+ url.host_str().unwrap_or(host).to_string()
Greptile
greptile
logic: Potential null pointer dereference. The unwrap_or() call assumes host is valid if URL parsing fails, but url.host_str() could return None
suggested fix
let url = Url::parse(host)?;
+ url.host_str().unwrap_or_else(|| host).to_string()
diff block
stream_type: StreamType,
stream_name: String,
alert_name: String,
+ folder_id: Option<String>,
) -> Result<(), anyhow::Error> {
let client = ORM_CLIENT.get_or_init(connect_to_orm).await;
let item_value: Alert = match table::get_by_name(
client,
&org,
- "default",
+ folder_id.unwrap_or("default".to_string()).as_str(),
Greptile
greptile
logic: Potential null pointer dereference if folder_id is None - unwrap_or should be used on the Option directly rather than calling unwrap_or on the String inside