diff --git a/aya/src/maps/mod.rs b/aya/src/maps/mod.rs
index 5dce668e..c0bcfec2 100644
--- a/aya/src/maps/mod.rs
+++ b/aya/src/maps/mod.rs
@@ -42,7 +42,7 @@ use std::{
     marker::PhantomData,
     mem,
     ops::Deref,
-    os::fd::{AsFd, AsRawFd, OwnedFd, RawFd},
+    os::fd::{AsFd as _, AsRawFd, IntoRawFd as _, OwnedFd, RawFd},
     path::Path,
     ptr,
     sync::Arc,
@@ -538,10 +538,12 @@ impl MapData {
         }
         let map_path = path.as_ref().join(name);
         let path_string = CString::new(map_path.to_str().unwrap()).unwrap();
-        let fd = bpf_get_object(&path_string).map_err(|(_, io_error)| MapError::SyscallError {
-            call: "BPF_OBJ_GET",
-            io_error,
-        })? as RawFd;
+        let fd = bpf_get_object(&path_string)
+            .map_err(|(_, io_error)| MapError::SyscallError {
+                call: "BPF_OBJ_GET",
+                io_error,
+            })?
+            .into_raw_fd();
 
         self.fd = Some(fd);
 
@@ -563,35 +565,37 @@ impl MapData {
         let fd = bpf_get_object(&path_string).map_err(|(_, io_error)| MapError::SyscallError {
             call: "BPF_OBJ_GET",
             io_error,
-        })? as RawFd;
-
-        let info = bpf_map_get_info_by_fd(fd).map_err(|io_error| MapError::SyscallError {
-            call: "BPF_MAP_GET_INFO_BY_FD",
-            io_error,
         })?;
 
+        let info =
+            bpf_map_get_info_by_fd(fd.as_fd()).map_err(|io_error| MapError::SyscallError {
+                call: "BPF_MAP_GET_INFO_BY_FD",
+                io_error,
+            })?;
+
         Ok(MapData {
             obj: parse_map_info(info, PinningType::ByName),
-            fd: Some(fd),
+            fd: Some(fd.into_raw_fd()),
             btf_fd: None,
             pinned: true,
         })
     }
 
-    /// Loads a map from a [`RawFd`].
+    /// Loads a map from an [`OwnedFd`].
     ///
     /// If loading from a BPF Filesystem (bpffs) you should use [`Map::from_pin`](crate::maps::MapData::from_pin).
     /// This API is intended for cases where you have received a valid BPF FD from some other means.
     /// For example, you received an FD over Unix Domain Socket.
-    pub fn from_fd(fd: RawFd) -> Result<MapData, MapError> {
-        let info = bpf_map_get_info_by_fd(fd).map_err(|io_error| MapError::SyscallError {
-            call: "BPF_OBJ_GET",
-            io_error,
-        })?;
+    pub fn from_fd(fd: OwnedFd) -> Result<MapData, MapError> {
+        let info =
+            bpf_map_get_info_by_fd(fd.as_fd()).map_err(|io_error| MapError::SyscallError {
+                call: "BPF_OBJ_GET",
+                io_error,
+            })?;
 
         Ok(MapData {
             obj: parse_map_info(info, PinningType::None),
-            fd: Some(fd),
+            fd: Some(fd.into_raw_fd()),
             btf_fd: None,
             pinned: false,
         })
diff --git a/aya/src/programs/cgroup_device.rs b/aya/src/programs/cgroup_device.rs
index e3580b28..526d61c2 100644
--- a/aya/src/programs/cgroup_device.rs
+++ b/aya/src/programs/cgroup_device.rs
@@ -1,7 +1,7 @@
 //! Cgroup device programs.
 
 use crate::util::KernelVersion;
-use std::os::fd::{AsRawFd, RawFd};
+use std::os::fd::AsRawFd;
 
 use crate::{
     generated::{bpf_attach_type::BPF_CGROUP_DEVICE, bpf_prog_type::BPF_PROG_TYPE_CGROUP_DEVICE},
@@ -70,7 +70,7 @@ impl CgroupDevice {
                     call: "bpf_link_create",
                     io_error,
                 },
-            )? as RawFd;
+            )?;
             self.data
                 .links
                 .insert(CgroupDeviceLink::new(CgroupDeviceLinkInner::Fd(
diff --git a/aya/src/programs/cgroup_skb.rs b/aya/src/programs/cgroup_skb.rs
index f67b56fe..ceea3eaa 100644
--- a/aya/src/programs/cgroup_skb.rs
+++ b/aya/src/programs/cgroup_skb.rs
@@ -1,11 +1,7 @@
 //! Cgroup skb programs.
 
 use crate::util::KernelVersion;
-use std::{
-    hash::Hash,
-    os::fd::{AsRawFd, RawFd},
-    path::Path,
-};
+use std::{hash::Hash, os::fd::AsRawFd, path::Path};
 
 use crate::{
     generated::{
@@ -105,7 +101,7 @@ impl CgroupSkb {
                     call: "bpf_link_create",
                     io_error,
                 },
-            )? as RawFd;
+            )?;
             self.data
                 .links
                 .insert(CgroupSkbLink::new(CgroupSkbLinkInner::Fd(FdLink::new(
diff --git a/aya/src/programs/cgroup_sock.rs b/aya/src/programs/cgroup_sock.rs
index 9d66f9aa..f99774bc 100644
--- a/aya/src/programs/cgroup_sock.rs
+++ b/aya/src/programs/cgroup_sock.rs
@@ -3,11 +3,7 @@
 pub use aya_obj::programs::CgroupSockAttachType;
 
 use crate::util::KernelVersion;
-use std::{
-    hash::Hash,
-    os::fd::{AsRawFd, RawFd},
-    path::Path,
-};
+use std::{hash::Hash, os::fd::AsRawFd, path::Path};
 
 use crate::{
     generated::bpf_prog_type::BPF_PROG_TYPE_CGROUP_SOCK,
@@ -80,7 +76,7 @@ impl CgroupSock {
                     call: "bpf_link_create",
                     io_error,
                 },
-            )? as RawFd;
+            )?;
             self.data
                 .links
                 .insert(CgroupSockLink::new(CgroupSockLinkInner::Fd(FdLink::new(
diff --git a/aya/src/programs/cgroup_sock_addr.rs b/aya/src/programs/cgroup_sock_addr.rs
index 045a40d2..e520beb7 100644
--- a/aya/src/programs/cgroup_sock_addr.rs
+++ b/aya/src/programs/cgroup_sock_addr.rs
@@ -3,11 +3,7 @@
 pub use aya_obj::programs::CgroupSockAddrAttachType;
 
 use crate::util::KernelVersion;
-use std::{
-    hash::Hash,
-    os::fd::{AsRawFd, RawFd},
-    path::Path,
-};
+use std::{hash::Hash, os::fd::AsRawFd, path::Path};
 
 use crate::{
     generated::bpf_prog_type::BPF_PROG_TYPE_CGROUP_SOCK_ADDR,
@@ -81,7 +77,7 @@ impl CgroupSockAddr {
                     call: "bpf_link_create",
                     io_error,
                 },
-            )? as RawFd;
+            )?;
             self.data
                 .links
                 .insert(CgroupSockAddrLink::new(CgroupSockAddrLinkInner::Fd(
diff --git a/aya/src/programs/cgroup_sockopt.rs b/aya/src/programs/cgroup_sockopt.rs
index 95b89dd7..407ed6e6 100644
--- a/aya/src/programs/cgroup_sockopt.rs
+++ b/aya/src/programs/cgroup_sockopt.rs
@@ -3,11 +3,7 @@
 pub use aya_obj::programs::CgroupSockoptAttachType;
 
 use crate::util::KernelVersion;
-use std::{
-    hash::Hash,
-    os::fd::{AsRawFd, RawFd},
-    path::Path,
-};
+use std::{hash::Hash, os::fd::AsRawFd, path::Path};
 
 use crate::{
     generated::bpf_prog_type::BPF_PROG_TYPE_CGROUP_SOCKOPT,
@@ -78,7 +74,7 @@ impl CgroupSockopt {
                     call: "bpf_link_create",
                     io_error,
                 },
-            )? as RawFd;
+            )?;
             self.data
                 .links
                 .insert(CgroupSockoptLink::new(CgroupSockoptLinkInner::Fd(
diff --git a/aya/src/programs/cgroup_sysctl.rs b/aya/src/programs/cgroup_sysctl.rs
index 3cd20195..06151c30 100644
--- a/aya/src/programs/cgroup_sysctl.rs
+++ b/aya/src/programs/cgroup_sysctl.rs
@@ -1,10 +1,7 @@
 //! Cgroup sysctl programs.
 
 use crate::util::KernelVersion;
-use std::{
-    hash::Hash,
-    os::fd::{AsRawFd, RawFd},
-};
+use std::{hash::Hash, os::fd::AsRawFd};
 
 use crate::{
     generated::{bpf_attach_type::BPF_CGROUP_SYSCTL, bpf_prog_type::BPF_PROG_TYPE_CGROUP_SYSCTL},
@@ -72,7 +69,7 @@ impl CgroupSysctl {
                     call: "bpf_link_create",
                     io_error,
                 },
-            )? as RawFd;
+            )?;
             self.data
                 .links
                 .insert(CgroupSysctlLink::new(CgroupSysctlLinkInner::Fd(
diff --git a/aya/src/programs/extension.rs b/aya/src/programs/extension.rs
index 682d14ad..ee8c0e2e 100644
--- a/aya/src/programs/extension.rs
+++ b/aya/src/programs/extension.rs
@@ -94,7 +94,7 @@ impl Extension {
             .map_err(|(_, io_error)| ProgramError::SyscallError {
                 call: "bpf_link_create",
                 io_error,
-            })? as RawFd;
+            })?;
         self.data
             .links
             .insert(ExtensionLink::new(FdLink::new(link_fd)))
@@ -124,7 +124,7 @@ impl Extension {
             .map_err(|(_, io_error)| ProgramError::SyscallError {
                 call: "bpf_link_create",
                 io_error,
-            })? as RawFd;
+            })?;
         self.data
             .links
             .insert(ExtensionLink::new(FdLink::new(link_fd)))
diff --git a/aya/src/programs/kprobe.rs b/aya/src/programs/kprobe.rs
index a61f084f..81b69618 100644
--- a/aya/src/programs/kprobe.rs
+++ b/aya/src/programs/kprobe.rs
@@ -1,5 +1,5 @@
 //! Kernel space probes.
-use std::{io, path::Path};
+use std::{io, os::fd::AsFd as _, path::Path};
 use thiserror::Error;
 
 use crate::{
@@ -137,12 +137,13 @@ impl TryFrom<FdLink> for KProbeLink {
     type Error = LinkError;
 
     fn try_from(fd_link: FdLink) -> Result<Self, Self::Error> {
-        let info =
-            bpf_link_get_info_by_fd(fd_link.fd).map_err(|io_error| LinkError::SyscallError {
+        let info = bpf_link_get_info_by_fd(fd_link.fd.as_fd()).map_err(|io_error| {
+            LinkError::SyscallError {
                 call: "BPF_OBJ_GET_INFO_BY_FD",
                 code: 0,
                 io_error,
-            })?;
+            }
+        })?;
         if info.type_ == (bpf_link_type::BPF_LINK_TYPE_KPROBE_MULTI as u32) {
             return Ok(KProbeLink::new(PerfLinkInner::FdLink(fd_link)));
         }
diff --git a/aya/src/programs/links.rs b/aya/src/programs/links.rs
index debe7b49..fdd37417 100644
--- a/aya/src/programs/links.rs
+++ b/aya/src/programs/links.rs
@@ -6,7 +6,7 @@ use std::{
     collections::{hash_map::Entry, HashMap},
     ffi::CString,
     io,
-    os::fd::RawFd,
+    os::fd::{AsRawFd as _, OwnedFd, RawFd},
     path::{Path, PathBuf},
 };
 
@@ -108,11 +108,11 @@ pub struct FdLinkId(pub(crate) RawFd);
 /// ```
 #[derive(Debug)]
 pub struct FdLink {
-    pub(crate) fd: RawFd,
+    pub(crate) fd: OwnedFd,
 }
 
 impl FdLink {
-    pub(crate) fn new(fd: RawFd) -> FdLink {
+    pub(crate) fn new(fd: OwnedFd) -> FdLink {
         FdLink { fd }
     }
 
@@ -152,9 +152,11 @@ impl FdLink {
                     error: e.to_string(),
                 }
             })?;
-        bpf_pin_object(self.fd, &path_string).map_err(|(_, io_error)| PinError::SyscallError {
-            name: "BPF_OBJ_PIN",
-            io_error,
+        bpf_pin_object(self.fd.as_raw_fd(), &path_string).map_err(|(_, io_error)| {
+            PinError::SyscallError {
+                name: "BPF_OBJ_PIN",
+                io_error,
+            }
         })?;
         Ok(PinnedLink::new(PathBuf::from(path.as_ref()), self))
     }
@@ -164,7 +166,7 @@ impl Link for FdLink {
     type Id = FdLinkId;
 
     fn id(&self) -> Self::Id {
-        FdLinkId(self.fd)
+        FdLinkId(self.fd.as_raw_fd())
     }
 
     fn detach(self) -> Result<(), ProgramError> {
@@ -183,12 +185,6 @@ impl From<PinnedLink> for FdLink {
     }
 }
 
-impl Drop for FdLink {
-    fn drop(&mut self) {
-        unsafe { close(self.fd) };
-    }
-}
-
 /// A pinned file descriptor link.
 ///
 /// This link has been pinned to the BPF filesystem. On drop, the file descriptor that backs
@@ -213,7 +209,7 @@ impl PinnedLink {
                 call: "BPF_OBJ_GET",
                 code,
                 io_error,
-            })? as RawFd;
+            })?;
         Ok(PinnedLink::new(
             path.as_ref().to_path_buf(),
             FdLink::new(fd),
@@ -354,7 +350,7 @@ pub enum LinkError {
 #[cfg(test)]
 mod tests {
     use assert_matches::assert_matches;
-    use std::{cell::RefCell, fs::File, mem, os::fd::AsRawFd, rc::Rc};
+    use std::{cell::RefCell, fs::File, rc::Rc};
     use tempfile::tempdir;
 
     use crate::{programs::ProgramError, sys::override_syscall};
@@ -492,10 +488,7 @@ mod tests {
     fn test_pin() {
         let dir = tempdir().unwrap();
         let f1 = File::create(dir.path().join("f1")).expect("unable to create file in tmpdir");
-        let fd_link = FdLink::new(f1.as_raw_fd());
-
-        // leak the fd, it will get closed when our pinned link is dropped
-        mem::forget(f1);
+        let fd_link = FdLink::new(f1.into());
 
         // override syscall to allow for pin to happen in our tmpdir
         override_syscall(|_| Ok(0));
diff --git a/aya/src/programs/mod.rs b/aya/src/programs/mod.rs
index 1ea52b7c..49a7be09 100644
--- a/aya/src/programs/mod.rs
+++ b/aya/src/programs/mod.rs
@@ -445,7 +445,7 @@ impl<T: Link> ProgramData<T> {
 
     pub(crate) fn from_bpf_prog_info(
         name: Option<String>,
-        fd: RawFd,
+        fd: OwnedFd,
         path: &Path,
         info: bpf_prog_info,
         verifier_log_level: VerifierLogLevel,
@@ -470,7 +470,7 @@ impl<T: Link> ProgramData<T> {
         Ok(ProgramData {
             name,
             obj: None,
-            fd: Some(fd),
+            fd: Some(fd.into_raw_fd()),
             links: LinkMap::new(),
             expected_attach_type: None,
             attach_btf_obj_fd,
@@ -493,11 +493,13 @@ impl<T: Link> ProgramData<T> {
             bpf_get_object(&path_string).map_err(|(_, io_error)| ProgramError::SyscallError {
                 call: "bpf_obj_get",
                 io_error,
-            })? as RawFd;
+            })?;
 
-        let info = bpf_prog_get_info_by_fd(fd).map_err(|io_error| ProgramError::SyscallError {
-            call: "bpf_prog_get_info_by_fd",
-            io_error,
+        let info = bpf_prog_get_info_by_fd(fd.as_raw_fd()).map_err(|io_error| {
+            ProgramError::SyscallError {
+                call: "bpf_prog_get_info_by_fd",
+                io_error,
+            }
         })?;
 
         let name = ProgramInfo(info).name_as_str().map(|s| s.to_string());
@@ -966,15 +968,15 @@ impl ProgramInfo {
             bpf_get_object(&path_string).map_err(|(_, io_error)| ProgramError::SyscallError {
                 call: "BPF_OBJ_GET",
                 io_error,
-            })? as RawFd;
+            })?;
 
-        let info = bpf_prog_get_info_by_fd(fd).map_err(|io_error| ProgramError::SyscallError {
-            call: "bpf_prog_get_info_by_fd",
-            io_error,
+        let info = bpf_prog_get_info_by_fd(fd.as_raw_fd()).map_err(|io_error| {
+            ProgramError::SyscallError {
+                call: "bpf_prog_get_info_by_fd",
+                io_error,
+            }
         })?;
-        unsafe {
-            libc::close(fd);
-        }
+
         Ok(ProgramInfo(info))
     }
 }
diff --git a/aya/src/programs/perf_attach.rs b/aya/src/programs/perf_attach.rs
index f2c7e032..1ac65a2a 100644
--- a/aya/src/programs/perf_attach.rs
+++ b/aya/src/programs/perf_attach.rs
@@ -77,7 +77,7 @@ pub(crate) fn perf_attach(prog_fd: RawFd, fd: OwnedFd) -> Result<PerfLinkInner,
                 call: "bpf_link_create",
                 io_error,
             },
-        )? as RawFd;
+        )?;
         Ok(PerfLinkInner::FdLink(FdLink::new(link_fd)))
     } else {
         perf_attach_either(prog_fd, fd, None, None)
diff --git a/aya/src/programs/perf_event.rs b/aya/src/programs/perf_event.rs
index 5d104499..e67ef2f6 100644
--- a/aya/src/programs/perf_event.rs
+++ b/aya/src/programs/perf_event.rs
@@ -1,5 +1,7 @@
 //! Perf event programs.
 
+use std::os::fd::AsFd as _;
+
 pub use crate::generated::{
     perf_hw_cache_id, perf_hw_cache_op_id, perf_hw_cache_op_result_id, perf_hw_id, perf_sw_ids,
 };
@@ -206,12 +208,13 @@ impl TryFrom<FdLink> for PerfEventLink {
     type Error = LinkError;
 
     fn try_from(fd_link: FdLink) -> Result<Self, Self::Error> {
-        let info =
-            bpf_link_get_info_by_fd(fd_link.fd).map_err(|io_error| LinkError::SyscallError {
+        let info = bpf_link_get_info_by_fd(fd_link.fd.as_fd()).map_err(|io_error| {
+            LinkError::SyscallError {
                 call: "BPF_OBJ_GET_INFO_BY_FD",
                 code: 0,
                 io_error,
-            })?;
+            }
+        })?;
         if info.type_ == (bpf_link_type::BPF_LINK_TYPE_PERF_EVENT as u32) {
             return Ok(PerfEventLink::new(PerfLinkInner::FdLink(fd_link)));
         }
diff --git a/aya/src/programs/sk_lookup.rs b/aya/src/programs/sk_lookup.rs
index 27d60b8f..90a381da 100644
--- a/aya/src/programs/sk_lookup.rs
+++ b/aya/src/programs/sk_lookup.rs
@@ -1,4 +1,4 @@
-use std::os::fd::{AsRawFd, RawFd};
+use std::os::fd::AsRawFd;
 
 use crate::{
     generated::{bpf_attach_type::BPF_SK_LOOKUP, bpf_prog_type::BPF_PROG_TYPE_SK_LOOKUP},
@@ -69,7 +69,7 @@ impl SkLookup {
                 call: "bpf_link_create",
                 io_error,
             },
-        )? as RawFd;
+        )?;
         self.data
             .links
             .insert(SkLookupLink::new(FdLink::new(link_fd)))
diff --git a/aya/src/programs/trace_point.rs b/aya/src/programs/trace_point.rs
index 8e4608c1..e264c2b9 100644
--- a/aya/src/programs/trace_point.rs
+++ b/aya/src/programs/trace_point.rs
@@ -1,5 +1,5 @@
 //! Tracepoint programs.
-use std::{fs, io, path::Path};
+use std::{fs, io, os::fd::AsFd as _, path::Path};
 use thiserror::Error;
 
 use crate::{
@@ -132,12 +132,13 @@ impl TryFrom<FdLink> for TracePointLink {
     type Error = LinkError;
 
     fn try_from(fd_link: FdLink) -> Result<Self, Self::Error> {
-        let info =
-            bpf_link_get_info_by_fd(fd_link.fd).map_err(|io_error| LinkError::SyscallError {
+        let info = bpf_link_get_info_by_fd(fd_link.fd.as_fd()).map_err(|io_error| {
+            LinkError::SyscallError {
                 call: "BPF_OBJ_GET_INFO_BY_FD",
                 code: 0,
                 io_error,
-            })?;
+            }
+        })?;
         if info.type_ == (bpf_link_type::BPF_LINK_TYPE_TRACING as u32) {
             return Ok(TracePointLink::new(PerfLinkInner::FdLink(fd_link)));
         }
diff --git a/aya/src/programs/uprobe.rs b/aya/src/programs/uprobe.rs
index bab82d4b..f9250b80 100644
--- a/aya/src/programs/uprobe.rs
+++ b/aya/src/programs/uprobe.rs
@@ -7,7 +7,7 @@ use std::{
     fs,
     io::{self, BufRead, Cursor, Read},
     mem,
-    os::raw::c_char,
+    os::{fd::AsFd as _, raw::c_char},
     path::{Path, PathBuf},
     sync::Arc,
 };
@@ -177,12 +177,13 @@ impl TryFrom<FdLink> for UProbeLink {
     type Error = LinkError;
 
     fn try_from(fd_link: FdLink) -> Result<Self, Self::Error> {
-        let info =
-            bpf_link_get_info_by_fd(fd_link.fd).map_err(|io_error| LinkError::SyscallError {
+        let info = bpf_link_get_info_by_fd(fd_link.fd.as_fd()).map_err(|io_error| {
+            LinkError::SyscallError {
                 call: "BPF_OBJ_GET_INFO_BY_FD",
                 code: 0,
                 io_error,
-            })?;
+            }
+        })?;
         if info.type_ == (bpf_link_type::BPF_LINK_TYPE_TRACING as u32) {
             return Ok(UProbeLink::new(PerfLinkInner::FdLink(fd_link)));
         }
diff --git a/aya/src/programs/utils.rs b/aya/src/programs/utils.rs
index 69fc616e..ba3cf641 100644
--- a/aya/src/programs/utils.rs
+++ b/aya/src/programs/utils.rs
@@ -1,5 +1,5 @@
 //! Common functions shared between multiple eBPF program types.
-use std::{ffi::CStr, io, os::fd::RawFd, path::Path};
+use std::{ffi::CStr, io, path::Path};
 
 use crate::{
     programs::{FdLink, Link, ProgramData, ProgramError},
@@ -18,7 +18,7 @@ pub(crate) fn attach_raw_tracepoint<T: Link + From<FdLink>>(
             call: "bpf_raw_tracepoint_open",
             io_error,
         }
-    })? as RawFd;
+    })?;
 
     program_data.links.insert(FdLink::new(pfd).into())
 }
diff --git a/aya/src/programs/xdp.rs b/aya/src/programs/xdp.rs
index 9372b281..866b3dac 100644
--- a/aya/src/programs/xdp.rs
+++ b/aya/src/programs/xdp.rs
@@ -3,7 +3,13 @@
 use crate::util::KernelVersion;
 use bitflags;
 use libc::if_nametoindex;
-use std::{convert::TryFrom, ffi::CString, hash::Hash, io, mem, os::fd::RawFd};
+use std::{
+    convert::TryFrom,
+    ffi::CString,
+    hash::Hash,
+    io,
+    os::fd::{AsFd as _, RawFd},
+};
 use thiserror::Error;
 
 use crate::{
@@ -131,7 +137,7 @@ impl Xdp {
                     call: "bpf_link_create",
                     io_error,
                 },
-            )? as RawFd;
+            )?;
             self.data
                 .links
                 .insert(XdpLink::new(XdpLinkInner::FdLink(FdLink::new(link_fd))))
@@ -169,17 +175,16 @@ impl Xdp {
     /// Ownership of the link will transfer to this program.
     pub fn attach_to_link(&mut self, link: XdpLink) -> Result<XdpLinkId, ProgramError> {
         let prog_fd = self.data.fd_or_err()?;
-        match link.inner() {
+        match link.into_inner() {
             XdpLinkInner::FdLink(fd_link) => {
                 let link_fd = fd_link.fd;
-                bpf_link_update(link_fd, prog_fd, None, 0).map_err(|(_, io_error)| {
+                bpf_link_update(link_fd.as_fd(), prog_fd, None, 0).map_err(|(_, io_error)| {
                     ProgramError::SyscallError {
                         call: "bpf_link_update",
                         io_error,
                     }
                 })?;
-                // dispose of link and avoid detach on drop
-                mem::forget(link);
+
                 self.data
                     .links
                     .insert(XdpLink::new(XdpLinkInner::FdLink(FdLink::new(link_fd))))
@@ -193,8 +198,7 @@ impl Xdp {
                     netlink_set_xdp_fd(if_index, prog_fd, Some(old_prog_fd), replace_flags.bits())
                         .map_err(|io_error| XdpError::NetlinkError { io_error })?;
                 }
-                // dispose of link and avoid detach on drop
-                mem::forget(link);
+
                 self.data
                     .links
                     .insert(XdpLink::new(XdpLinkInner::NlLink(NlLink {
@@ -279,12 +283,13 @@ impl TryFrom<FdLink> for XdpLink {
 
     fn try_from(fd_link: FdLink) -> Result<Self, Self::Error> {
         // unwrap of fd_link.fd will not panic since it's only None when being dropped.
-        let info =
-            bpf_link_get_info_by_fd(fd_link.fd).map_err(|io_error| LinkError::SyscallError {
+        let info = bpf_link_get_info_by_fd(fd_link.fd.as_fd()).map_err(|io_error| {
+            LinkError::SyscallError {
                 call: "BPF_OBJ_GET_INFO_BY_FD",
                 code: 0,
                 io_error,
-            })?;
+            }
+        })?;
         if info.type_ == (bpf_link_type::BPF_LINK_TYPE_XDP as u32) {
             return Ok(XdpLink::new(XdpLinkInner::FdLink(fd_link)));
         }
diff --git a/aya/src/sys/bpf.rs b/aya/src/sys/bpf.rs
index 9f7e11c6..ecfa55f2 100644
--- a/aya/src/sys/bpf.rs
+++ b/aya/src/sys/bpf.rs
@@ -101,11 +101,12 @@ pub(crate) fn bpf_pin_object(fd: RawFd, path: &CStr) -> SysResult<c_long> {
     sys_bpf(bpf_cmd::BPF_OBJ_PIN, &attr)
 }
 
-pub(crate) fn bpf_get_object(path: &CStr) -> SysResult<c_long> {
+pub(crate) fn bpf_get_object(path: &CStr) -> SysResult<OwnedFd> {
     let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
     let u = unsafe { &mut attr.__bindgen_anon_4 };
     u.pathname = path.as_ptr() as u64;
-    sys_bpf(bpf_cmd::BPF_OBJ_GET, &attr)
+    // SAFETY: BPF_OBJ_GET returns a new file descriptor.
+    unsafe { fd_sys_bpf(bpf_cmd::BPF_OBJ_GET, &attr) }
 }
 
 pub(crate) struct BpfLoadProgramAttrs<'a> {
@@ -370,7 +371,7 @@ pub(crate) fn bpf_link_create(
     attach_type: bpf_attach_type,
     btf_id: Option<u32>,
     flags: u32,
-) -> SysResult<c_long> {
+) -> SysResult<OwnedFd> {
     let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
 
     attr.link_create.__bindgen_anon_1.prog_fd = prog_fd as u32;
@@ -381,19 +382,20 @@ pub(crate) fn bpf_link_create(
         attr.link_create.__bindgen_anon_3.target_btf_id = btf_id;
     }
 
-    sys_bpf(bpf_cmd::BPF_LINK_CREATE, &attr)
+    // SAFETY: BPF_LINK_CREATE returns a new file descriptor.
+    unsafe { fd_sys_bpf(bpf_cmd::BPF_LINK_CREATE, &attr) }
 }
 
 // since kernel 5.7
 pub(crate) fn bpf_link_update(
-    link_fd: RawFd,
+    link_fd: BorrowedFd<'_>,
     new_prog_fd: RawFd,
     old_prog_fd: Option<RawFd>,
     flags: u32,
 ) -> SysResult<c_long> {
     let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
 
-    attr.link_update.link_fd = link_fd as u32;
+    attr.link_update.link_fd = link_fd.as_raw_fd() as u32;
     attr.link_update.__bindgen_anon_1.new_prog_fd = new_prog_fd as u32;
     if let Some(fd) = old_prog_fd {
         attr.link_update.__bindgen_anon_2.old_prog_fd = fd as u32;
@@ -483,12 +485,12 @@ pub(crate) fn bpf_prog_get_info_by_fd(prog_fd: RawFd) -> Result<bpf_prog_info, i
     }
 }
 
-pub(crate) fn bpf_map_get_info_by_fd(prog_fd: RawFd) -> Result<bpf_map_info, io::Error> {
+pub(crate) fn bpf_map_get_info_by_fd(prog_fd: BorrowedFd<'_>) -> Result<bpf_map_info, io::Error> {
     let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
     // info gets entirely populated by the kernel
     let info = MaybeUninit::zeroed();
 
-    attr.info.bpf_fd = prog_fd as u32;
+    attr.info.bpf_fd = prog_fd.as_raw_fd() as u32;
     attr.info.info = info.as_ptr() as *const _ as u64;
     attr.info.info_len = mem::size_of::<bpf_map_info>() as u32;
 
@@ -498,12 +500,12 @@ pub(crate) fn bpf_map_get_info_by_fd(prog_fd: RawFd) -> Result<bpf_map_info, io:
     }
 }
 
-pub(crate) fn bpf_link_get_info_by_fd(link_fd: RawFd) -> Result<bpf_link_info, io::Error> {
+pub(crate) fn bpf_link_get_info_by_fd(link_fd: BorrowedFd<'_>) -> Result<bpf_link_info, io::Error> {
     let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
     // info gets entirely populated by the kernel
     let info = unsafe { MaybeUninit::zeroed().assume_init() };
 
-    attr.info.bpf_fd = link_fd as u32;
+    attr.info.bpf_fd = link_fd.as_raw_fd() as u32;
     attr.info.info = &info as *const _ as u64;
     attr.info.info_len = mem::size_of::<bpf_link_info>() as u32;
 
@@ -532,7 +534,7 @@ pub(crate) fn btf_obj_get_info_by_fd(
     }
 }
 
-pub(crate) fn bpf_raw_tracepoint_open(name: Option<&CStr>, prog_fd: RawFd) -> SysResult<c_long> {
+pub(crate) fn bpf_raw_tracepoint_open(name: Option<&CStr>, prog_fd: RawFd) -> SysResult<OwnedFd> {
     let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
 
     attr.raw_tracepoint.name = match name {
@@ -541,7 +543,8 @@ pub(crate) fn bpf_raw_tracepoint_open(name: Option<&CStr>, prog_fd: RawFd) -> Sy
     };
     attr.raw_tracepoint.prog_fd = prog_fd as u32;
 
-    sys_bpf(bpf_cmd::BPF_RAW_TRACEPOINT_OPEN, &attr)
+    // SAFETY: BPF_RAW_TRACEPOINT_OPEN returns a new file descriptor.
+    unsafe { fd_sys_bpf(bpf_cmd::BPF_RAW_TRACEPOINT_OPEN, &attr) }
 }
 
 pub(crate) fn bpf_load_btf(
diff --git a/xtask/public-api/aya.txt b/xtask/public-api/aya.txt
index a32ec8a5..98c7eadf 100644
--- a/xtask/public-api/aya.txt
+++ b/xtask/public-api/aya.txt
@@ -1236,7 +1236,7 @@ pub aya::maps::MapData::pinned: bool
 impl aya::maps::MapData
 pub fn aya::maps::MapData::create(&mut self, name: &str) -> core::result::Result<std::os::fd::raw::RawFd, aya::maps::MapError>
 pub fn aya::maps::MapData::fd(&self) -> core::option::Option<aya::maps::MapFd>
-pub fn aya::maps::MapData::from_fd(fd: std::os::fd::raw::RawFd) -> core::result::Result<aya::maps::MapData, aya::maps::MapError>
+pub fn aya::maps::MapData::from_fd(fd: std::os::fd::owned::OwnedFd) -> core::result::Result<aya::maps::MapData, aya::maps::MapError>
 pub fn aya::maps::MapData::from_pin<P: core::convert::AsRef<std::path::Path>>(path: P) -> core::result::Result<aya::maps::MapData, aya::maps::MapError>
 impl core::clone::Clone for aya::maps::MapData
 pub fn aya::maps::MapData::clone(&self) -> aya::maps::MapData
@@ -3102,8 +3102,6 @@ pub fn aya::programs::links::FdLink::try_from(value: aya::programs::uprobe::UPro
 impl core::convert::TryFrom<aya::programs::xdp::XdpLink> for aya::programs::links::FdLink
 pub type aya::programs::links::FdLink::Error = aya::programs::links::LinkError
 pub fn aya::programs::links::FdLink::try_from(value: aya::programs::xdp::XdpLink) -> core::result::Result<Self, Self::Error>
-impl core::ops::drop::Drop for aya::programs::links::FdLink
-pub fn aya::programs::links::FdLink::drop(&mut self)
 impl core::fmt::Debug for aya::programs::links::FdLink
 pub fn aya::programs::links::FdLink::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
 impl core::marker::Send for aya::programs::links::FdLink