diff --git a/INSTALL b/INSTALL
index 672eb40640..c47d510748 100644
--- a/INSTALL
+++ b/INSTALL
@@ -181,18 +181,6 @@ if 'CFLAGS' is specified it must enable optimization.  For example:
      read-only global offset table (GOT), at the cost of slightly
      increased program load times.
 
-'--enable-pt_chown'
-     The file 'pt_chown' is a helper binary for 'grantpt' (*note
-     Pseudo-Terminals: Allocation.) that is installed setuid root to fix
-     up pseudo-terminal ownership.  It is not built by default because
-     systems using the Linux kernel are commonly built with the 'devpts'
-     filesystem enabled and mounted at '/dev/pts', which manages
-     pseudo-terminal ownership automatically.  By using
-     '--enable-pt_chown', you may build 'pt_chown' and install it setuid
-     and owned by 'root'.  The use of 'pt_chown' introduces additional
-     security risks to the system and you should enable it only if you
-     understand and accept those risks.
-
 '--disable-werror'
      By default, the GNU C Library is built with '-Werror'.  If you wish
      to build without this option (for example, if building with a newer
@@ -404,13 +392,6 @@ may not want to run.  'nscd' caches name service lookups; it can
 dramatically improve performance with NIS+, and may help with DNS as
 well.
 
-   One auxiliary program, '/usr/libexec/pt_chown', is installed setuid
-'root' if the '--enable-pt_chown' configuration option is used.  This
-program is invoked by the 'grantpt' function; it sets the permissions on
-a pseudoterminal so it can be used by the calling process.  If you are
-using a Linux kernel with the 'devpts' filesystem enabled and mounted at
-'/dev/pts', you don't need this program.
-
    After installation you should configure the timezone and install
 locales for your system.  The time zone configuration ensures that your
 system time matches the time for your current timezone.  The locales
diff --git a/NEWS b/NEWS
index cc20102fda..58f2c12cb8 100644
--- a/NEWS
+++ b/NEWS
@@ -85,6 +85,10 @@ Deprecated and removed features, and other changes affecting compatibility:
   as all functions that call vscanf, vfscanf, or vsscanf are annotated with
   __attribute__ ((format (scanf, ...))).
 
+* The pt_chown program has been removed.  (It has not been installed by
+  default since glibc 2.18.)  Changing ownership of PTYs cannot be made safe
+  in userspace in a container environment.
+
 Changes to build and runtime requirements:
 
 * Python 3.4 or later is required to build the GNU C Library.
diff --git a/configure b/configure
index 101dfddf37..c5afaef4b2 100755
--- a/configure
+++ b/configure
@@ -671,7 +671,6 @@ libc_cv_ssp
 libc_cv_with_fp
 base_machine
 have_tunables
-build_pt_chown
 build_nscd
 build_obsolete_nsl
 link_obsolete_rpc
@@ -790,7 +789,6 @@ enable_obsolete_nsl
 enable_systemtap
 enable_build_nscd
 enable_nscd
-enable_pt_chown
 enable_tunables
 enable_mathvec
 enable_cet
@@ -1464,7 +1462,6 @@ Optional Features:
   --enable-systemtap      enable systemtap static probe points [default=no]
   --disable-build-nscd    disable building and installing the nscd daemon
   --disable-nscd          library functions will not contact the nscd daemon
-  --enable-pt_chown       Enable building and installing pt_chown
   --enable-tunables       Enable tunables support. Known values are 'yes',
                           'no' and 'valstring'
   --enable-mathvec        Enable building and installing mathvec [default
@@ -3731,19 +3728,6 @@ else
 fi
 
 
-# Check whether --enable-pt_chown was given.
-if test "${enable_pt_chown+set}" = set; then :
-  enableval=$enable_pt_chown; build_pt_chown=$enableval
-else
-  build_pt_chown=no
-fi
-
-
-if test "$build_pt_chown" = yes; then
-  $as_echo "#define HAVE_PT_CHOWN 1" >>confdefs.h
-
-fi
-
 # Check whether --enable-tunables was given.
 if test "${enable_tunables+set}" = set; then :
   enableval=$enable_tunables; have_tunables=$enableval
diff --git a/configure.ac b/configure.ac
index 46a74687a6..5369fae7b2 100644
--- a/configure.ac
+++ b/configure.ac
@@ -434,16 +434,6 @@ AC_ARG_ENABLE([nscd],
 	      [use_nscd=$enableval],
 	      [use_nscd=yes])
 
-AC_ARG_ENABLE([pt_chown],
-	      [AS_HELP_STRING([--enable-pt_chown],
-	       [Enable building and installing pt_chown])],
-	      [build_pt_chown=$enableval],
-	      [build_pt_chown=no])
-AC_SUBST(build_pt_chown)
-if test "$build_pt_chown" = yes; then
-  AC_DEFINE(HAVE_PT_CHOWN)
-fi
-
 AC_ARG_ENABLE([tunables],
 	      [AS_HELP_STRING([--enable-tunables],
 	       [Enable tunables support. Known values are 'yes', 'no' and 'valstring'])],
diff --git a/login/Makefile b/login/Makefile
index 92535f0aec..5c78b491f3 100644
--- a/login/Makefile
+++ b/login/Makefile
@@ -34,12 +34,6 @@ CFLAGS-grantpt.c += -DLIBEXECDIR='"$(libexecdir)"'
 
 others = utmpdump
 
-ifeq (yes,$(build-pt-chown))
-others += pt_chown
-others-pie = pt_chown
-install-others-programs = $(inst_libexecdir)/pt_chown
-endif
-
 subdir-dirs = programs
 vpath %.c programs
 
@@ -54,20 +48,3 @@ libutil-routines:= login login_tty logout logwtmp openpty forkpty
 include ../Rules
 
 CFLAGS-getpt.c += -fexceptions
-
-ifeq (yesyes,$(have-fpie)$(build-shared))
-pt_chown-cflags += $(pie-ccflag)
-endif
-ifeq (yes,$(have-libcap))
-libcap = -lcap
-endif
-CFLAGS-pt_chown.c += $(pt_chown-cflags)
-LDLIBS-pt_chown = $(libcap)
-ifeq (yesyes,$(have-fpie)$(build-shared))
-LDFLAGS-pt_chown = -Wl,-z,now
-endif
-
-# pt_chown needs to be setuid root.
-$(inst_libexecdir)/pt_chown: $(objpfx)pt_chown $(+force)
-	$(make-target-directory)
-	-$(INSTALL_PROGRAM) -m 4755 -o root $< $@
diff --git a/login/programs/pt_chown.c b/login/programs/pt_chown.c
deleted file mode 100644
index d44e4bf6ed..0000000000
--- a/login/programs/pt_chown.c
+++ /dev/null
@@ -1,215 +0,0 @@
-/* pt_chmod - helper program for `grantpt'.
-   Copyright (C) 1998-2019 Free Software Foundation, Inc.
-   This file is part of the GNU C Library.
-   Contributed by C. Scott Ananian <cananian@alumni.princeton.edu>, 1998.
-
-   The GNU C Library is free software; you can redistribute it and/or
-   modify it under the terms of the GNU Lesser General Public
-   License as published by the Free Software Foundation; either
-   version 2.1 of the License, or (at your option) any later version.
-
-   The GNU C Library 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
-   Lesser General Public License for more details.
-
-   You should have received a copy of the GNU Lesser General Public
-   License along with the GNU C Library; if not, see
-   <http://www.gnu.org/licenses/>.  */
-
-#include <argp.h>
-#include <errno.h>
-#include <error.h>
-#include <grp.h>
-#include <libintl.h>
-#include <locale.h>
-#include <signal.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#ifdef HAVE_LIBCAP
-# include <sys/capability.h>
-# include <sys/prctl.h>
-#endif
-
-#include "pty-private.h"
-
-/* Get libc version number.  */
-#include "../version.h"
-
-#define PACKAGE _libc_intl_domainname
-
-/* Name and version of program.  */
-static void print_version (FILE *stream, struct argp_state *state);
-void (*argp_program_version_hook) (FILE *, struct argp_state *) = print_version;
-
-/* Function to print some extra text in the help message.  */
-static char *more_help (int key, const char *text, void *input);
-
-/* Data structure to communicate with argp functions.  */
-static struct argp argp =
-{
-  NULL, NULL, NULL, NULL, NULL, more_help
-};
-
-
-/* Print the version information.  */
-static void
-print_version (FILE *stream, struct argp_state *state)
-{
-  fprintf (stream, "pt_chown %s%s\n", PKGVERSION, VERSION);
-  fprintf (stream, gettext ("\
-Copyright (C) %s Free Software Foundation, Inc.\n\
-This is free software; see the source for copying conditions.  There is NO\n\
-warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\
-"), "2019");
-}
-
-static char *
-more_help (int key, const char *text, void *input)
-{
-  char *cp;
-  char *tp;
-
-  switch (key)
-    {
-    case ARGP_KEY_HELP_PRE_DOC:
-      asprintf (&cp, gettext ("\
-Set the owner, group and access permission of the slave pseudo\
- terminal corresponding to the master pseudo terminal passed on\
- file descriptor `%d'.  This is the helper program for the\
- `grantpt' function.  It is not intended to be run directly from\
- the command line.\n"),
-		PTY_FILENO);
-      return cp;
-    case ARGP_KEY_HELP_EXTRA:
-      /* We print some extra information.  */
-      if (asprintf (&tp, gettext ("\
-For bug reporting instructions, please see:\n\
-%s.\n"), REPORT_BUGS_TO) < 0)
-	return NULL;
-      if (asprintf (&cp, gettext ("\
-The owner is set to the current user, the group is set to `%s',\
- and the access permission is set to `%o'.\n\n\
-%s"),
-		    TTY_GROUP, S_IRUSR|S_IWUSR|S_IWGRP, tp) < 0)
-	{
-	  free (tp);
-	  return NULL;
-	}
-      return cp;
-    default:
-      break;
-    }
-  return (char *) text;
-}
-
-static int
-do_pt_chown (void)
-{
-  char *pty;
-  struct stat64 st;
-  struct group *p;
-  gid_t gid;
-
-  /* Check that PTY_FILENO is a valid master pseudo terminal.  */
-  pty = ptsname (PTY_FILENO);
-  if (pty == NULL)
-    return errno == EBADF ? FAIL_EBADF : FAIL_EINVAL;
-
-  /* Check that the returned slave pseudo terminal is a
-     character device.  */
-  if (stat64 (pty, &st) < 0 || !S_ISCHR (st.st_mode))
-    return FAIL_EINVAL;
-
-  /* Get the group ID of the special `tty' group.  */
-  p = getgrnam (TTY_GROUP);
-  gid = p ? p->gr_gid : getgid ();
-
-  /* Set the owner to the real user ID, and the group to that special
-     group ID.  */
-  if (chown (pty, getuid (), gid) < 0)
-    return FAIL_EACCES;
-
-  /* Set the permission mode to readable and writable by the owner,
-     and writable by the group.  */
-  if ((st.st_mode & ACCESSPERMS) != (S_IRUSR|S_IWUSR|S_IWGRP)
-      && chmod (pty, S_IRUSR|S_IWUSR|S_IWGRP) < 0)
-    return FAIL_EACCES;
-
-  return 0;
-}
-
-
-int
-main (int argc, char *argv[])
-{
-  uid_t euid = geteuid ();
-  uid_t uid = getuid ();
-  int remaining;
-  sigset_t signalset;
-
-  /* Clear any signal mask from the parent process.  */
-  sigemptyset (&signalset);
-  sigprocmask (SIG_SETMASK, &signalset, NULL);
-
-  if (argc == 1 && euid == 0)
-    {
-#ifdef HAVE_LIBCAP
-  /* Drop privileges.  */
-      if (uid != euid)
-	{
-	  static const cap_value_t cap_list[] =
-	    { CAP_CHOWN, CAP_FOWNER	};
-# define ncap_list (sizeof (cap_list) / sizeof (cap_list[0]))
-	  cap_t caps = cap_init ();
-	  if (caps == NULL)
-	    return FAIL_ENOMEM;
-
-	  /* There is no reason why these should not work.  */
-	  cap_set_flag (caps, CAP_PERMITTED, ncap_list, cap_list, CAP_SET);
-	  cap_set_flag (caps, CAP_EFFECTIVE, ncap_list, cap_list, CAP_SET);
-
-	  int res = cap_set_proc (caps);
-
-	  cap_free (caps);
-
-	  if (__glibc_unlikely (res != 0))
-	    return FAIL_EXEC;
-	}
-#endif
-
-      /* Normal invocation of this program is with no arguments and
-	 with privileges.  */
-      return do_pt_chown ();
-    }
-
-  /* We aren't going to be using privileges, so drop them right now. */
-  setuid (uid);
-
-  /* Set locale via LC_ALL.  */
-  setlocale (LC_ALL, "");
-
-  /* Set the text message domain.  */
-  textdomain (PACKAGE);
-
-  /* parse and process arguments.  */
-  argp_parse (&argp, argc, argv, 0, &remaining, NULL);
-
-  if (remaining < argc)
-    {
-      /* We should not be called with any non-option parameters.  */
-      error (0, 0, gettext ("too many arguments"));
-      argp_help (&argp, stdout, ARGP_HELP_SEE | ARGP_HELP_EXIT_ERR,
-		 program_invocation_short_name);
-      return EXIT_FAILURE;
-    }
-
-  /* Check if we are properly installed.  */
-  if (euid != 0)
-    error (FAIL_EXEC, 0, gettext ("needs to be installed setuid `root'"));
-
-  return EXIT_SUCCESS;
-}
diff --git a/manual/install.texi b/manual/install.texi
index 2a87a2725a..49fcc7ac95 100644
--- a/manual/install.texi
+++ b/manual/install.texi
@@ -209,20 +209,6 @@ additional security hardening because it enables full RELRO and a
 read-only global offset table (GOT), at the cost of slightly increased
 program load times.
 
-@pindex pt_chown
-@findex grantpt
-@item --enable-pt_chown
-The file @file{pt_chown} is a helper binary for @code{grantpt}
-(@pxref{Allocation, Pseudo-Terminals}) that is installed setuid root to
-fix up pseudo-terminal ownership.  It is not built by default because
-systems using the Linux kernel are commonly built with the @code{devpts}
-filesystem enabled and mounted at @file{/dev/pts}, which manages
-pseudo-terminal ownership automatically.  By using
-@samp{--enable-pt_chown}, you may build @file{pt_chown} and install it
-setuid and owned by @code{root}.  The use of @file{pt_chown} introduces
-additional security risks to the system and you should enable it only if
-you understand and accept those risks.
-
 @item --disable-werror
 By default, @theglibc{} is built with @option{-Werror}.  If you wish
 to build without this option (for example, if building with a newer
@@ -442,13 +428,6 @@ may or may not want to run.  @code{nscd} caches name service lookups; it
 can dramatically improve performance with NIS+, and may help with DNS as
 well.
 
-One auxiliary program, @file{/usr/libexec/pt_chown}, is installed setuid
-@code{root} if the @samp{--enable-pt_chown} configuration option is used.
-This program is invoked by the @code{grantpt} function; it sets the
-permissions on a pseudoterminal so it can be used by the calling process.
-If you are using a Linux kernel with the @code{devpts} filesystem enabled
-and mounted at @file{/dev/pts}, you don't need this program.
-
 After installation you should configure the timezone and install locales
 for your system.  The time zone configuration ensures that your system
 time matches the time for your current timezone.  The locales ensure that
diff --git a/manual/terminal.texi b/manual/terminal.texi
index d830baacd7..7bb974e018 100644
--- a/manual/terminal.texi
+++ b/manual/terminal.texi
@@ -1989,16 +1989,6 @@ This function is a GNU extension.
 @c  getgrnam_r @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
 @c  getgid dup ok
 @c  chmod dup ok
-@c  fork dup @aculock
-@c  [child]
-@c   setrlimit
-@c   dup2
-@c   CLOSE_ALL_FDS
-@c   execle
-@c   _exit
-@c  waitpid dup ok
-@c  WIFEXITED dup ok
-@c  WEXITSTATUS dup ok
 @c  free dup @ascuheap @acsmem
 The @code{grantpt} function changes the ownership and access permission
 of the slave pseudo-terminal device corresponding to the master
diff --git a/sysdeps/generic/pty-private.h b/sysdeps/generic/pty-private.h
index 1315c01af9..7470ca0c6a 100644
--- a/sysdeps/generic/pty-private.h
+++ b/sysdeps/generic/pty-private.h
@@ -26,9 +26,6 @@
 /* The file descriptor connected to the master pseudo terminal.  */
 #define PTY_FILENO 3
 
-/* Path to the helper program that implements `grantpt' in user space.  */
-#define _PATH_PT_CHOWN LIBEXECDIR "/pt_chown"
-
 /* Test whether given TTY is really a Unix98 pseudo terminal.  */
 /* #define unix98_pseudo_p(Dev) ... */
 
diff --git a/sysdeps/unix/grantpt.c b/sysdeps/unix/grantpt.c
index e2e6df14c9..7143f54605 100644
--- a/sysdeps/unix/grantpt.c
+++ b/sysdeps/unix/grantpt.c
@@ -99,7 +99,6 @@ pts_name (int fd, char **pts, size_t buf_len, struct stat64 *stp)
 int
 grantpt (int fd)
 {
-  int retval = -1;
 #ifdef PATH_MAX
   char _buf[PATH_MAX];
 #else
@@ -132,7 +131,7 @@ grantpt (int fd)
   if (st.st_uid != uid)
     {
       if (__chown (buf, uid, st.st_gid) < 0)
-	goto helper;
+	goto cleanup;
     }
 
   static int tty_gid = -1;
@@ -155,103 +154,21 @@ grantpt (int fd)
     }
   gid_t gid = tty_gid == -1 ? __getgid () : tty_gid;
 
-#if HAVE_PT_CHOWN
-  /* Make sure the group of the device is that special group.  */
-  if (st.st_gid != gid)
-    {
-      if (__chown (buf, uid, gid) < 0)
-	goto helper;
-    }
-
-  /* Make sure the permission mode is set to readable and writable by
-     the owner, and writable by the group.  */
-  mode_t mode = S_IRUSR|S_IWUSR|S_IWGRP;
-#else
-  /* When built without pt_chown, we have delegated the creation of the
-     pty node with the right group and permission mode to the kernel, and
-     non-root users are unlikely to be able to change it. Therefore let's
-     consider that POSIX enforcement is the responsibility of the whole
-     system and not only the GNU libc. Thus accept different group or
-     permission mode.  */
-
   /* Make sure the permission is set to readable and writable by the
      owner.  For security reasons, make it writable by the group only
      when originally writable and when the group of the device is that
      special group.  */
   mode_t mode = S_IRUSR|S_IWUSR|
 	        ((st.st_gid == gid) ? (st.st_mode & S_IWGRP) : 0);
-#endif
 
+  int retval = 0;
   if ((st.st_mode & ACCESSPERMS) != mode)
     {
       if (__chmod (buf, mode) < 0)
-	goto helper;
+	goto cleanup;
     }
 
   retval = 0;
-  goto cleanup;
-
-  /* We have to use the helper program if it is available.  */
- helper:;
-
-#if HAVE_PT_CHOWN
-  pid_t pid = __fork ();
-  if (pid == -1)
-    goto cleanup;
-  else if (pid == 0)
-    {
-      /* Disable core dumps.  */
-      struct rlimit rl = { 0, 0 };
-      __setrlimit (RLIMIT_CORE, &rl);
-
-      /* We pass the master pseudo terminal as file descriptor PTY_FILENO.  */
-      if (fd != PTY_FILENO)
-	if (__dup2 (fd, PTY_FILENO) < 0)
-	  _exit (FAIL_EBADF);
-
-# ifdef CLOSE_ALL_FDS
-      CLOSE_ALL_FDS ();
-# endif
-
-      execle (_PATH_PT_CHOWN, __basename (_PATH_PT_CHOWN), NULL, NULL);
-      _exit (FAIL_EXEC);
-    }
-  else
-    {
-      int w;
-
-      if (__waitpid (pid, &w, 0) == -1)
-	goto cleanup;
-      if (!WIFEXITED (w))
-	__set_errno (ENOEXEC);
-      else
-	switch (WEXITSTATUS (w))
-	  {
-	  case 0:
-	    retval = 0;
-	    break;
-	  case FAIL_EBADF:
-	    __set_errno (EBADF);
-	    break;
-	  case FAIL_EINVAL:
-	    __set_errno (EINVAL);
-	    break;
-	  case FAIL_EACCES:
-	    __set_errno (EACCES);
-	    break;
-	  case FAIL_EXEC:
-	    __set_errno (ENOEXEC);
-	    break;
-	  case FAIL_ENOMEM:
-	    __set_errno (ENOMEM);
-	    break;
-
-	  default:
-	    assert(! "grantpt: internal error: invalid exit code from pt_chown");
-	  }
-    }
-#endif
-
  cleanup:
   if (buf != _buf)
     free (buf);
diff --git a/sysdeps/unix/sysv/linux/grantpt.c b/sysdeps/unix/sysv/linux/grantpt.c
deleted file mode 100644
index 2030e07fa6..0000000000
--- a/sysdeps/unix/sysv/linux/grantpt.c
+++ /dev/null
@@ -1,44 +0,0 @@
-#include <assert.h>
-#include <ctype.h>
-#include <dirent.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <paths.h>
-#include <stdlib.h>
-#include <unistd.h>
-
-#include <not-cancel.h>
-
-#include "pty-private.h"
-
-#if HAVE_PT_CHOWN
-/* Close all file descriptors except the one specified.  */
-static void
-close_all_fds (void)
-{
-  DIR *dir = __opendir ("/proc/self/fd");
-  if (dir != NULL)
-    {
-      struct dirent64 *d;
-      while ((d = __readdir64 (dir)) != NULL)
-	if (isdigit (d->d_name[0]))
-	  {
-	    char *endp;
-	    long int fd = strtol (d->d_name, &endp, 10);
-	    if (*endp == '\0' && fd != PTY_FILENO && fd != dirfd (dir))
-	      __close_nocancel_nostatus (fd);
-	  }
-
-      __closedir (dir);
-
-      int nullfd = __open_nocancel (_PATH_DEVNULL, O_RDONLY);
-      assert (nullfd == STDIN_FILENO);
-      nullfd = __open_nocancel (_PATH_DEVNULL, O_WRONLY);
-      assert (nullfd == STDOUT_FILENO);
-      __dup2 (STDOUT_FILENO, STDERR_FILENO);
-    }
-}
-# define CLOSE_ALL_FDS() close_all_fds()
-#endif
-
-#include <sysdeps/unix/grantpt.c>
